Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / pkgs / classes.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
17  * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
18  *
19  * For example:
20  *
21  *     Ext.define('Employee', {
22  *         extend: 'Ext.util.Observable',
23  *         constructor: function(config){
24  *             this.name = config.name;
25  *             this.addEvents({
26  *                 "fired" : true,
27  *                 "quit" : true
28  *             });
29  *
30  *             // Copy configured listeners into *this* object so that the base class's
31  *             // constructor will add them.
32  *             this.listeners = config.listeners;
33  *
34  *             // Call our superclass constructor to complete construction process.
35  *             this.callParent(arguments)
36  *         }
37  *     });
38  *
39  * This could then be used like this:
40  *
41  *     var newEmployee = new Employee({
42  *         name: employeeName,
43  *         listeners: {
44  *             quit: function() {
45  *                 // By default, "this" will be the object that fired the event.
46  *                 alert(this.name + " has quit!");
47  *             }
48  *         }
49  *     });
50  */
51 Ext.define('Ext.util.Observable', {
52
53     /* Begin Definitions */
54
55     requires: ['Ext.util.Event'],
56
57     statics: {
58         /**
59          * Removes **all** added captures from the Observable.
60          *
61          * @param {Ext.util.Observable} o The Observable to release
62          * @static
63          */
64         releaseCapture: function(o) {
65             o.fireEvent = this.prototype.fireEvent;
66         },
67
68         /**
69          * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
70          * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
71          * the event will not fire.
72          *
73          * @param {Ext.util.Observable} o The Observable to capture events from.
74          * @param {Function} fn The function to call when an event is fired.
75          * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
76          * the Observable firing the event.
77          * @static
78          */
79         capture: function(o, fn, scope) {
80             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
81         },
82
83         /**
84          * Sets observability on the passed class constructor.
85          *
86          * This makes any event fired on any instance of the passed class also fire a single event through
87          * the **class** allowing for central handling of events on many instances at once.
88          *
89          * Usage:
90          *
91          *     Ext.util.Observable.observe(Ext.data.Connection);
92          *     Ext.data.Connection.on('beforerequest', function(con, options) {
93          *         console.log('Ajax request made to ' + options.url);
94          *     });
95          *
96          * @param {Function} c The class constructor to make observable.
97          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
98          * @static
99          */
100         observe: function(cls, listeners) {
101             if (cls) {
102                 if (!cls.isObservable) {
103                     Ext.applyIf(cls, new this());
104                     this.capture(cls.prototype, cls.fireEvent, cls);
105                 }
106                 if (Ext.isObject(listeners)) {
107                     cls.on(listeners);
108                 }
109                 return cls;
110             }
111         }
112     },
113
114     /* End Definitions */
115
116     /**
117      * @cfg {Object} listeners
118      *
119      * A config object containing one or more event handlers to be added to this object during initialization. This
120      * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
121      * handlers at once.
122      *
123      * **DOM events from Ext JS {@link Ext.Component Components}**
124      *
125      * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
126      * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
127      * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
128      * child element of a Component, we need to specify the `element` option to identify the Component property to add a
129      * DOM listener to:
130      *
131      *     new Ext.panel.Panel({
132      *         width: 400,
133      *         height: 200,
134      *         dockedItems: [{
135      *             xtype: 'toolbar'
136      *         }],
137      *         listeners: {
138      *             click: {
139      *                 element: 'el', //bind to the underlying el property on the panel
140      *                 fn: function(){ console.log('click el'); }
141      *             },
142      *             dblclick: {
143      *                 element: 'body', //bind to the underlying body property on the panel
144      *                 fn: function(){ console.log('dblclick body'); }
145      *             }
146      *         }
147      *     });
148      */
149     // @private
150     isObservable: true,
151
152     constructor: function(config) {
153         var me = this;
154
155         Ext.apply(me, config);
156         if (me.listeners) {
157             me.on(me.listeners);
158             delete me.listeners;
159         }
160         me.events = me.events || {};
161
162         if (me.bubbleEvents) {
163             me.enableBubble(me.bubbleEvents);
164         }
165     },
166
167     // @private
168     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
169
170     /**
171      * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
172      * destroyed.
173      *
174      * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
175      * @param {Object/String} ename The event name, or an object containing event name properties.
176      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
177      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
178      * in which the handler function is executed.
179      * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
180      * {@link Ext.util.Observable#addListener addListener} options.
181      */
182     addManagedListener : function(item, ename, fn, scope, options) {
183         var me = this,
184             managedListeners = me.managedListeners = me.managedListeners || [],
185             config;
186
187         if (typeof ename !== 'string') {
188             options = ename;
189             for (ename in options) {
190                 if (options.hasOwnProperty(ename)) {
191                     config = options[ename];
192                     if (!me.eventOptionsRe.test(ename)) {
193                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
194                     }
195                 }
196             }
197         }
198         else {
199             managedListeners.push({
200                 item: item,
201                 ename: ename,
202                 fn: fn,
203                 scope: scope,
204                 options: options
205             });
206
207             item.on(ename, fn, scope, options);
208         }
209     },
210
211     /**
212      * Removes listeners that were added by the {@link #mon} method.
213      *
214      * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
215      * @param {Object/String} ename The event name, or an object containing event name properties.
216      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
217      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
218      * in which the handler function is executed.
219      */
220     removeManagedListener : function(item, ename, fn, scope) {
221         var me = this,
222             options,
223             config,
224             managedListeners,
225             length,
226             i;
227
228         if (typeof ename !== 'string') {
229             options = ename;
230             for (ename in options) {
231                 if (options.hasOwnProperty(ename)) {
232                     config = options[ename];
233                     if (!me.eventOptionsRe.test(ename)) {
234                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
235                     }
236                 }
237             }
238         }
239
240         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
241
242         for (i = 0, length = managedListeners.length; i < length; i++) {
243             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
244         }
245     },
246
247     /**
248      * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
249      * to {@link #addListener}).
250      *
251      * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
252      * calling {@link #enableBubble}.
253      *
254      * @param {String} eventName The name of the event to fire.
255      * @param {Object...} args Variable number of parameters are passed to handlers.
256      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
257      */
258     fireEvent: function(eventName) {
259         var name = eventName.toLowerCase(),
260             events = this.events,
261             event = events && events[name],
262             bubbles = event && event.bubble;
263
264         return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
265     },
266
267     /**
268      * Continue to fire event.
269      * @private
270      *
271      * @param {String} eventName
272      * @param {Array} args
273      * @param {Boolean} bubbles
274      */
275     continueFireEvent: function(eventName, args, bubbles) {
276         var target = this,
277             queue, event,
278             ret = true;
279
280         do {
281             if (target.eventsSuspended === true) {
282                 if ((queue = target.eventQueue)) {
283                     queue.push([eventName, args, bubbles]);
284                 }
285                 return ret;
286             } else {
287                 event = target.events[eventName];
288                 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
289                 // configure to bubble.
290                 if (event && event != true) {
291                     if ((ret = event.fire.apply(event, args)) === false) {
292                         break;
293                     }
294                 }
295             }
296         } while (bubbles && (target = target.getBubbleParent()));
297         return ret;
298     },
299
300     /**
301      * Gets the bubbling parent for an Observable
302      * @private
303      * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
304      */
305     getBubbleParent: function(){
306         var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
307         if (parent && parent.isObservable) {
308             return parent;
309         }
310         return null;
311     },
312
313     /**
314      * Appends an event handler to this object.
315      *
316      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
317      * event names.
318      * @param {Function} fn The method the event invokes.  Will be called with arguments given to
319      * {@link #fireEvent} plus the `options` parameter described below.
320      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
321      * omitted, defaults to the object which fired the event.**
322      * @param {Object} [options] An object containing handler configuration.
323      *
324      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
325      *
326      * This object may contain any of the following properties:
327      *
328      * - **scope** : Object
329      *
330      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
331      *   which fired the event.**
332      *
333      * - **delay** : Number
334      *
335      *   The number of milliseconds to delay the invocation of the handler after the event fires.
336      *
337      * - **single** : Boolean
338      *
339      *   True to add a handler to handle just the next firing of the event, and then remove itself.
340      *
341      * - **buffer** : Number
342      *
343      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
344      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
345      *   handler is scheduled in its place.
346      *
347      * - **target** : Observable
348      *
349      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
350      *   child Observable.
351      *
352      * - **element** : String
353      *
354      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
355      *   property which references an element to add a listener to.
356      *
357      *   This option is useful during Component construction to add DOM event listeners to elements of
358      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
359      *   For example, to add a click listener to a Panel's body:
360      *
361      *       new Ext.panel.Panel({
362      *           title: 'The title',
363      *           listeners: {
364      *               click: this.handlePanelClick,
365      *               element: 'body'
366      *           }
367      *       });
368      *
369      * **Combining Options**
370      *
371      * Using the options argument, it is possible to combine different types of listeners:
372      *
373      * A delayed, one-time listener.
374      *
375      *     myPanel.on('hide', this.handleClick, this, {
376      *         single: true,
377      *         delay: 100
378      *     });
379      *
380      * **Attaching multiple handlers in 1 call**
381      *
382      * The method also allows for a single argument to be passed which is a config object containing properties which
383      * specify multiple events. For example:
384      *
385      *     myGridPanel.on({
386      *         cellClick: this.onCellClick,
387      *         mouseover: this.onMouseOver,
388      *         mouseout: this.onMouseOut,
389      *         scope: this // Important. Ensure "this" is correct during handler execution
390      *     });
391      *
392      * One can also specify options for each event handler separately:
393      *
394      *     myGridPanel.on({
395      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
396      *         mouseover: {fn: panel.onMouseOver, scope: panel}
397      *     });
398      *
399      */
400     addListener: function(ename, fn, scope, options) {
401         var me = this,
402             config,
403             event;
404
405         if (typeof ename !== 'string') {
406             options = ename;
407             for (ename in options) {
408                 if (options.hasOwnProperty(ename)) {
409                     config = options[ename];
410                     if (!me.eventOptionsRe.test(ename)) {
411                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
412                     }
413                 }
414             }
415         }
416         else {
417             ename = ename.toLowerCase();
418             me.events[ename] = me.events[ename] || true;
419             event = me.events[ename] || true;
420             if (Ext.isBoolean(event)) {
421                 me.events[ename] = event = new Ext.util.Event(me, ename);
422             }
423             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
424         }
425     },
426
427     /**
428      * Removes an event handler.
429      *
430      * @param {String} eventName The type of event the handler was associated with.
431      * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
432      * {@link #addListener} call.**
433      * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
434      * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
435      */
436     removeListener: function(ename, fn, scope) {
437         var me = this,
438             config,
439             event,
440             options;
441
442         if (typeof ename !== 'string') {
443             options = ename;
444             for (ename in options) {
445                 if (options.hasOwnProperty(ename)) {
446                     config = options[ename];
447                     if (!me.eventOptionsRe.test(ename)) {
448                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
449                     }
450                 }
451             }
452         } else {
453             ename = ename.toLowerCase();
454             event = me.events[ename];
455             if (event && event.isEvent) {
456                 event.removeListener(fn, scope);
457             }
458         }
459     },
460
461     /**
462      * Removes all listeners for this object including the managed listeners
463      */
464     clearListeners: function() {
465         var events = this.events,
466             event,
467             key;
468
469         for (key in events) {
470             if (events.hasOwnProperty(key)) {
471                 event = events[key];
472                 if (event.isEvent) {
473                     event.clearListeners();
474                 }
475             }
476         }
477
478         this.clearManagedListeners();
479     },
480
481     //<debug>
482     purgeListeners : function() {
483         if (Ext.global.console) {
484             Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
485         }
486         return this.clearListeners.apply(this, arguments);
487     },
488     //</debug>
489
490     /**
491      * Removes all managed listeners for this object.
492      */
493     clearManagedListeners : function() {
494         var managedListeners = this.managedListeners || [],
495             i = 0,
496             len = managedListeners.length;
497
498         for (; i < len; i++) {
499             this.removeManagedListenerItem(true, managedListeners[i]);
500         }
501
502         this.managedListeners = [];
503     },
504
505     /**
506      * Remove a single managed listener item
507      * @private
508      * @param {Boolean} isClear True if this is being called during a clear
509      * @param {Object} managedListener The managed listener item
510      * See removeManagedListener for other args
511      */
512     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
513         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
514             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
515             if (!isClear) {
516                 Ext.Array.remove(this.managedListeners, managedListener);
517             }
518         }
519     },
520
521     //<debug>
522     purgeManagedListeners : function() {
523         if (Ext.global.console) {
524             Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
525         }
526         return this.clearManagedListeners.apply(this, arguments);
527     },
528     //</debug>
529
530     /**
531      * Adds the specified events to the list of events which this Observable may fire.
532      *
533      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
534      * event name string if multiple event names are being passed as separate parameters. Usage:
535      *
536      *     this.addEvents({
537      *         storeloaded: true,
538      *         storecleared: true
539      *     });
540      *
541      * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
542      * parameters. Usage:
543      *
544      *     this.addEvents('storeloaded', 'storecleared');
545      *
546      */
547     addEvents: function(o) {
548         var me = this,
549             args,
550             len,
551             i;
552
553             me.events = me.events || {};
554         if (Ext.isString(o)) {
555             args = arguments;
556             i = args.length;
557
558             while (i--) {
559                 me.events[args[i]] = me.events[args[i]] || true;
560             }
561         } else {
562             Ext.applyIf(me.events, o);
563         }
564     },
565
566     /**
567      * Checks to see if this object has any listeners for a specified event
568      *
569      * @param {String} eventName The name of the event to check for
570      * @return {Boolean} True if the event is being listened for, else false
571      */
572     hasListener: function(ename) {
573         var event = this.events[ename.toLowerCase()];
574         return event && event.isEvent === true && event.listeners.length > 0;
575     },
576
577     /**
578      * Suspends the firing of all events. (see {@link #resumeEvents})
579      *
580      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
581      * after the {@link #resumeEvents} call instead of discarding all suspended events.
582      */
583     suspendEvents: function(queueSuspended) {
584         this.eventsSuspended = true;
585         if (queueSuspended && !this.eventQueue) {
586             this.eventQueue = [];
587         }
588     },
589
590     /**
591      * Resumes firing events (see {@link #suspendEvents}).
592      *
593      * If events were suspended using the `queueSuspended` parameter, then all events fired
594      * during event suspension will be sent to any listeners now.
595      */
596     resumeEvents: function() {
597         var me = this,
598             queued = me.eventQueue;
599
600         me.eventsSuspended = false;
601         delete me.eventQueue;
602
603         if (queued) {
604             Ext.each(queued, function(e) {
605                 me.continueFireEvent.apply(me, e);
606             });
607         }
608     },
609
610     /**
611      * Relays selected events from the specified Observable as if the events were fired by `this`.
612      *
613      * @param {Object} origin The Observable whose events this object is to relay.
614      * @param {String[]} events Array of event names to relay.
615      * @param {String} prefix
616      */
617     relayEvents : function(origin, events, prefix) {
618         prefix = prefix || '';
619         var me = this,
620             len = events.length,
621             i = 0,
622             oldName,
623             newName;
624
625         for (; i < len; i++) {
626             oldName = events[i].substr(prefix.length);
627             newName = prefix + oldName;
628             me.events[newName] = me.events[newName] || true;
629             origin.on(oldName, me.createRelayer(newName));
630         }
631     },
632
633     /**
634      * @private
635      * Creates an event handling function which refires the event from this object as the passed event name.
636      * @param newName
637      * @returns {Function}
638      */
639     createRelayer: function(newName){
640         var me = this;
641         return function(){
642             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
643         };
644     },
645
646     /**
647      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
648      * present. There is no implementation in the Observable base class.
649      *
650      * This is commonly used by Ext.Components to bubble events to owner Containers.
651      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
652      * Component's immediate owner. But if a known target is required, this can be overridden to access the
653      * required target more quickly.
654      *
655      * Example:
656      *
657      *     Ext.override(Ext.form.field.Base, {
658      *         //  Add functionality to Field's initComponent to enable the change event to bubble
659      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
660      *             this.enableBubble('change');
661      *         }),
662      *
663      *         //  We know that we want Field's events to bubble directly to the FormPanel.
664      *         getBubbleTarget : function() {
665      *             if (!this.formPanel) {
666      *                 this.formPanel = this.findParentByType('form');
667      *             }
668      *             return this.formPanel;
669      *         }
670      *     });
671      *
672      *     var myForm = new Ext.formPanel({
673      *         title: 'User Details',
674      *         items: [{
675      *             ...
676      *         }],
677      *         listeners: {
678      *             change: function() {
679      *                 // Title goes red if form has been modified.
680      *                 myForm.header.setStyle('color', 'red');
681      *             }
682      *         }
683      *     });
684      *
685      * @param {String/String[]} events The event name to bubble, or an Array of event names.
686      */
687     enableBubble: function(events) {
688         var me = this;
689         if (!Ext.isEmpty(events)) {
690             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
691             Ext.each(events,
692             function(ename) {
693                 ename = ename.toLowerCase();
694                 var ce = me.events[ename] || true;
695                 if (Ext.isBoolean(ce)) {
696                     ce = new Ext.util.Event(me, ename);
697                     me.events[ename] = ce;
698                 }
699                 ce.bubble = true;
700             });
701         }
702     }
703 }, function() {
704
705     this.createAlias({
706         /**
707          * @method
708          * Shorthand for {@link #addListener}.
709          * @alias Ext.util.Observable#addListener
710          */
711         on: 'addListener',
712         /**
713          * @method
714          * Shorthand for {@link #removeListener}.
715          * @alias Ext.util.Observable#removeListener
716          */
717         un: 'removeListener',
718         /**
719          * @method
720          * Shorthand for {@link #addManagedListener}.
721          * @alias Ext.util.Observable#addManagedListener
722          */
723         mon: 'addManagedListener',
724         /**
725          * @method
726          * Shorthand for {@link #removeManagedListener}.
727          * @alias Ext.util.Observable#removeManagedListener
728          */
729         mun: 'removeManagedListener'
730     });
731
732     //deprecated, will be removed in 5.0
733     this.observeClass = this.observe;
734
735     Ext.apply(Ext.util.Observable.prototype, function(){
736         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
737         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
738         // private
739         function getMethodEvent(method){
740             var e = (this.methodEvents = this.methodEvents || {})[method],
741                 returnValue,
742                 v,
743                 cancel,
744                 obj = this;
745
746             if (!e) {
747                 this.methodEvents[method] = e = {};
748                 e.originalFn = this[method];
749                 e.methodName = method;
750                 e.before = [];
751                 e.after = [];
752
753                 var makeCall = function(fn, scope, args){
754                     if((v = fn.apply(scope || obj, args)) !== undefined){
755                         if (typeof v == 'object') {
756                             if(v.returnValue !== undefined){
757                                 returnValue = v.returnValue;
758                             }else{
759                                 returnValue = v;
760                             }
761                             cancel = !!v.cancel;
762                         }
763                         else
764                             if (v === false) {
765                                 cancel = true;
766                             }
767                             else {
768                                 returnValue = v;
769                             }
770                     }
771                 };
772
773                 this[method] = function(){
774                     var args = Array.prototype.slice.call(arguments, 0),
775                         b, i, len;
776                     returnValue = v = undefined;
777                     cancel = false;
778
779                     for(i = 0, len = e.before.length; i < len; i++){
780                         b = e.before[i];
781                         makeCall(b.fn, b.scope, args);
782                         if (cancel) {
783                             return returnValue;
784                         }
785                     }
786
787                     if((v = e.originalFn.apply(obj, args)) !== undefined){
788                         returnValue = v;
789                     }
790
791                     for(i = 0, len = e.after.length; i < len; i++){
792                         b = e.after[i];
793                         makeCall(b.fn, b.scope, args);
794                         if (cancel) {
795                             return returnValue;
796                         }
797                     }
798                     return returnValue;
799                 };
800             }
801             return e;
802         }
803
804         return {
805             // these are considered experimental
806             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
807             // adds an 'interceptor' called before the original method
808             beforeMethod : function(method, fn, scope){
809                 getMethodEvent.call(this, method).before.push({
810                     fn: fn,
811                     scope: scope
812                 });
813             },
814
815             // adds a 'sequence' called after the original method
816             afterMethod : function(method, fn, scope){
817                 getMethodEvent.call(this, method).after.push({
818                     fn: fn,
819                     scope: scope
820                 });
821             },
822
823             removeMethodListener: function(method, fn, scope){
824                 var e = this.getMethodEvent(method),
825                     i, len;
826                 for(i = 0, len = e.before.length; i < len; i++){
827                     if(e.before[i].fn == fn && e.before[i].scope == scope){
828                         Ext.Array.erase(e.before, i, 1);
829                         return;
830                     }
831                 }
832                 for(i = 0, len = e.after.length; i < len; i++){
833                     if(e.after[i].fn == fn && e.after[i].scope == scope){
834                         Ext.Array.erase(e.after, i, 1);
835                         return;
836                     }
837                 }
838             },
839
840             toggleEventLogging: function(toggle) {
841                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
842                     if (Ext.isDefined(Ext.global.console)) {
843                         Ext.global.console.log(en, arguments);
844                     }
845                 });
846             }
847         };
848     }());
849 });
850
851 /**
852  * @class Ext.util.Animate
853  * This animation class is a mixin.
854  * 
855  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
856  * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement}, 
857  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
858  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
859  * opacity (color, paddings, and margins can not be animated).
860  * 
861  * ## Animation Basics
862  * 
863  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
864  * you wish to animate. Easing and duration are defaulted values specified below.
865  * Easing describes how the intermediate values used during a transition will be calculated. 
866  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
867  * You may use the defaults for easing and duration, but you must always set a 
868  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
869  * 
870  * Popular element 'to' configurations are:
871  * 
872  *  - opacity
873  *  - x
874  *  - y
875  *  - color
876  *  - height
877  *  - width 
878  * 
879  * Popular sprite 'to' configurations are:
880  * 
881  *  - translation
882  *  - path
883  *  - scale
884  *  - stroke
885  *  - rotation
886  * 
887  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
888  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
889  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
890  * 
891  * For example, a simple animation to fade out an element with a default easing and duration:
892  * 
893  *     var p1 = Ext.get('myElementId');
894  * 
895  *     p1.animate({
896  *         to: {
897  *             opacity: 0
898  *         }
899  *     });
900  * 
901  * To make this animation fade out in a tenth of a second:
902  * 
903  *     var p1 = Ext.get('myElementId');
904  * 
905  *     p1.animate({
906  *        duration: 100,
907  *         to: {
908  *             opacity: 0
909  *         }
910  *     });
911  * 
912  * ## Animation Queues
913  * 
914  * By default all animations are added to a queue which allows for animation via a chain-style API.
915  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
916  * 
917  *     p1.animate({
918  *         to: {
919  *             x: 500
920  *         }
921  *     }).animate({
922  *         to: {
923  *             y: 150
924  *         }
925  *     }).animate({
926  *         to: {
927  *             backgroundColor: '#f00'  //red
928  *         }
929  *     }).animate({
930  *         to: {
931  *             opacity: 0
932  *         }
933  *     });
934  * 
935  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
936  * subsequent animations for the specified target will be run concurrently (at the same time).
937  * 
938  *     p1.syncFx();  //this will make all animations run at the same time
939  * 
940  *     p1.animate({
941  *         to: {
942  *             x: 500
943  *         }
944  *     }).animate({
945  *         to: {
946  *             y: 150
947  *         }
948  *     }).animate({
949  *         to: {
950  *             backgroundColor: '#f00'  //red
951  *         }
952  *     }).animate({
953  *         to: {
954  *             opacity: 0
955  *         }
956  *     });
957  * 
958  * This works the same as:
959  * 
960  *     p1.animate({
961  *         to: {
962  *             x: 500,
963  *             y: 150,
964  *             backgroundColor: '#f00'  //red
965  *             opacity: 0
966  *         }
967  *     });
968  * 
969  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
970  * currently running animations and clear any queued animations. 
971  * 
972  * ## Animation Keyframes
973  *
974  * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the 
975  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
976  * The previous example can be written with the following syntax:
977  * 
978  *     p1.animate({
979  *         duration: 1000,  //one second total
980  *         keyframes: {
981  *             25: {     //from 0 to 250ms (25%)
982  *                 x: 0
983  *             },
984  *             50: {   //from 250ms to 500ms (50%)
985  *                 y: 0
986  *             },
987  *             75: {  //from 500ms to 750ms (75%)
988  *                 backgroundColor: '#f00'  //red
989  *             },
990  *             100: {  //from 750ms to 1sec
991  *                 opacity: 0
992  *             }
993  *         }
994  *     });
995  * 
996  * ## Animation Events
997  * 
998  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate}, 
999  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
1000  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
1001  * fires for each keyframe in your animation.
1002  * 
1003  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
1004  *    
1005  *     startAnimate: function() {
1006  *         var p1 = Ext.get('myElementId');
1007  *         p1.animate({
1008  *            duration: 100,
1009  *             to: {
1010  *                 opacity: 0
1011  *             },
1012  *             listeners: {
1013  *                 beforeanimate:  function() {
1014  *                     // Execute my custom method before the animation
1015  *                     this.myBeforeAnimateFn();
1016  *                 },
1017  *                 afteranimate: function() {
1018  *                     // Execute my custom method after the animation
1019  *                     this.myAfterAnimateFn();
1020  *                 },
1021  *                 scope: this
1022  *         });
1023  *     },
1024  *     myBeforeAnimateFn: function() {
1025  *       // My custom logic
1026  *     },
1027  *     myAfterAnimateFn: function() {
1028  *       // My custom logic
1029  *     }
1030  * 
1031  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
1032  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
1033  * method.  This method will return false if there are no active animations or return the currently 
1034  * running {@link Ext.fx.Anim} instance.
1035  * 
1036  * In this example, we're going to wait for the current animation to finish, then stop any other 
1037  * queued animations before we fade our element's opacity to 0:
1038  * 
1039  *     var curAnim = p1.getActiveAnimation();
1040  *     if (curAnim) {
1041  *         curAnim.on('afteranimate', function() {
1042  *             p1.stopAnimation();
1043  *             p1.animate({
1044  *                 to: {
1045  *                     opacity: 0
1046  *                 }
1047  *             });
1048  *         });
1049  *     }
1050  * 
1051  * @docauthor Jamie Avins <jamie@sencha.com>
1052  */
1053 Ext.define('Ext.util.Animate', {
1054
1055     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
1056
1057     /**
1058      * <p>Perform custom animation on this object.<p>
1059      * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
1060      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
1061      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
1062      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
1063      * <p>Properties include<ul>
1064      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
1065      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
1066      * ths object being animated. See the sections below on Element and Component animation.<div></li>
1067      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
1068      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
1069      * <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>
1070      * <li>ease</li>
1071      * <li>easeIn</li>
1072      * <li>easeOut</li>
1073      * <li>easeInOut</li>
1074      * <li>backIn</li>
1075      * <li>backOut</li>
1076      * <li>elasticIn</li>
1077      * <li>elasticOut</li>
1078      * <li>bounceIn</li>
1079      * <li>bounceOut</li>
1080      * </ul></code></div></li>
1081      * <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.
1082      * 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>
1083      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
1084      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
1085      * </ul></p>
1086      * <h3>Animating an {@link Ext.Element Element}</h3>
1087      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
1088      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
1089      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
1090      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
1091      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
1092      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
1093      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
1094      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
1095      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
1096      * <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>
1097      * </ul>
1098      * <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
1099      * 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
1100      * directly animate certain properties of Components.</b></p>
1101      * <h3>Animating a {@link Ext.Component Component}</h3>
1102      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
1103      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
1104      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
1105      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
1106      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
1107      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
1108      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
1109      * <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
1110      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
1111      * </ul>
1112      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
1113      * <pre><code>
1114 myWindow = Ext.create('Ext.window.Window', {
1115     title: 'Test Component animation',
1116     width: 500,
1117     height: 300,
1118     layout: {
1119         type: 'hbox',
1120         align: 'stretch'
1121     },
1122     items: [{
1123         title: 'Left: 33%',
1124         margins: '5 0 5 5',
1125         flex: 1
1126     }, {
1127         title: 'Left: 66%',
1128         margins: '5 5 5 5',
1129         flex: 2
1130     }]
1131 });
1132 myWindow.show();
1133 myWindow.header.el.on('click', function() {
1134     myWindow.animate({
1135         to: {
1136             width: (myWindow.getWidth() == 500) ? 700 : 500,
1137             height: (myWindow.getHeight() == 300) ? 400 : 300,
1138         }
1139     });
1140 });
1141 </code></pre>
1142      * <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
1143      * 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>
1144      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
1145      * @return {Object} this
1146      */
1147     animate: function(animObj) {
1148         var me = this;
1149         if (Ext.fx.Manager.hasFxBlock(me.id)) {
1150             return me;
1151         }
1152         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
1153         return this;
1154     },
1155
1156     // @private - process the passed fx configuration.
1157     anim: function(config) {
1158         if (!Ext.isObject(config)) {
1159             return (config) ? {} : false;
1160         }
1161
1162         var me = this;
1163
1164         if (config.stopAnimation) {
1165             me.stopAnimation();
1166         }
1167
1168         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
1169
1170         return Ext.apply({
1171             target: me,
1172             paused: true
1173         }, config);
1174     },
1175
1176     /**
1177      * @deprecated 4.0 Replaced by {@link #stopAnimation}
1178      * Stops any running effects and clears this object's internal effects queue if it contains
1179      * any additional effects that haven't started yet.
1180      * @return {Ext.Element} The Element
1181      * @method
1182      */
1183     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
1184
1185     /**
1186      * Stops any running effects and clears this object's internal effects queue if it contains
1187      * any additional effects that haven't started yet.
1188      * @return {Ext.Element} The Element
1189      */
1190     stopAnimation: function() {
1191         Ext.fx.Manager.stopAnimation(this.id);
1192         return this;
1193     },
1194
1195     /**
1196      * Ensures that all effects queued after syncFx is called on this object are
1197      * run concurrently.  This is the opposite of {@link #sequenceFx}.
1198      * @return {Object} this
1199      */
1200     syncFx: function() {
1201         Ext.fx.Manager.setFxDefaults(this.id, {
1202             concurrent: true
1203         });
1204         return this;
1205     },
1206
1207     /**
1208      * Ensures that all effects queued after sequenceFx is called on this object are
1209      * run in sequence.  This is the opposite of {@link #syncFx}.
1210      * @return {Object} this
1211      */
1212     sequenceFx: function() {
1213         Ext.fx.Manager.setFxDefaults(this.id, {
1214             concurrent: false
1215         });
1216         return this;
1217     },
1218
1219     /**
1220      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
1221      * @alias Ext.util.Animate#getActiveAnimation
1222      * @method
1223      */
1224     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
1225
1226     /**
1227      * Returns the current animation if this object has any effects actively running or queued, else returns false.
1228      * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
1229      */
1230     getActiveAnimation: function() {
1231         return Ext.fx.Manager.getActiveAnimation(this.id);
1232     }
1233 }, function(){
1234     // Apply Animate mixin manually until Element is defined in the proper 4.x way
1235     Ext.applyIf(Ext.Element.prototype, this.prototype);
1236     // We need to call this again so the animation methods get copied over to CE
1237     Ext.CompositeElementLite.importElementMethods();
1238 });
1239 /**
1240  * @class Ext.state.Provider
1241  * <p>Abstract base class for state provider implementations. The provider is responsible
1242  * for setting values  and extracting values to/from the underlying storage source. The 
1243  * storage source can vary and the details should be implemented in a subclass. For example
1244  * a provider could use a server side database or the browser localstorage where supported.</p>
1245  *
1246  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
1247  * dates and defines the Provider interface. By default these methods put the value and the
1248  * type information into a delimited string that can be stored. These should be overridden in 
1249  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
1250  */
1251 Ext.define('Ext.state.Provider', {
1252     mixins: {
1253         observable: 'Ext.util.Observable'
1254     },
1255     
1256     /**
1257      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
1258      * Defaults to <tt>'ext-'</tt>
1259      */
1260     prefix: 'ext-',
1261     
1262     constructor : function(config){
1263         config = config || {};
1264         var me = this;
1265         Ext.apply(me, config);
1266         /**
1267          * @event statechange
1268          * Fires when a state change occurs.
1269          * @param {Ext.state.Provider} this This state provider
1270          * @param {String} key The state key which was changed
1271          * @param {String} value The encoded value for the state
1272          */
1273         me.addEvents("statechange");
1274         me.state = {};
1275         me.mixins.observable.constructor.call(me);
1276     },
1277     
1278     /**
1279      * Returns the current value for a key
1280      * @param {String} name The key name
1281      * @param {Object} defaultValue A default value to return if the key's value is not found
1282      * @return {Object} The state data
1283      */
1284     get : function(name, defaultValue){
1285         return typeof this.state[name] == "undefined" ?
1286             defaultValue : this.state[name];
1287     },
1288
1289     /**
1290      * Clears a value from the state
1291      * @param {String} name The key name
1292      */
1293     clear : function(name){
1294         var me = this;
1295         delete me.state[name];
1296         me.fireEvent("statechange", me, name, null);
1297     },
1298
1299     /**
1300      * Sets the value for a key
1301      * @param {String} name The key name
1302      * @param {Object} value The value to set
1303      */
1304     set : function(name, value){
1305         var me = this;
1306         me.state[name] = value;
1307         me.fireEvent("statechange", me, name, value);
1308     },
1309
1310     /**
1311      * Decodes a string previously encoded with {@link #encodeValue}.
1312      * @param {String} value The value to decode
1313      * @return {Object} The decoded value
1314      */
1315     decodeValue : function(value){
1316
1317         // a -> Array
1318         // n -> Number
1319         // d -> Date
1320         // b -> Boolean
1321         // s -> String
1322         // o -> Object
1323         // -> Empty (null)
1324
1325         var me = this,
1326             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
1327             matches = re.exec(unescape(value)),
1328             all,
1329             type,
1330             value,
1331             keyValue;
1332             
1333         if(!matches || !matches[1]){
1334             return; // non state
1335         }
1336         
1337         type = matches[1];
1338         value = matches[2];
1339         switch (type) {
1340             case 'e':
1341                 return null;
1342             case 'n':
1343                 return parseFloat(value);
1344             case 'd':
1345                 return new Date(Date.parse(value));
1346             case 'b':
1347                 return (value == '1');
1348             case 'a':
1349                 all = [];
1350                 if(value != ''){
1351                     Ext.each(value.split('^'), function(val){
1352                         all.push(me.decodeValue(val));
1353                     }, me);
1354                 }
1355                 return all;
1356            case 'o':
1357                 all = {};
1358                 if(value != ''){
1359                     Ext.each(value.split('^'), function(val){
1360                         keyValue = val.split('=');
1361                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
1362                     }, me);
1363                 }
1364                 return all;
1365            default:
1366                 return value;
1367         }
1368     },
1369
1370     /**
1371      * Encodes a value including type information.  Decode with {@link #decodeValue}.
1372      * @param {Object} value The value to encode
1373      * @return {String} The encoded value
1374      */
1375     encodeValue : function(value){
1376         var flat = '',
1377             i = 0,
1378             enc,
1379             len,
1380             key;
1381             
1382         if (value == null) {
1383             return 'e:1';    
1384         } else if(typeof value == 'number') {
1385             enc = 'n:' + value;
1386         } else if(typeof value == 'boolean') {
1387             enc = 'b:' + (value ? '1' : '0');
1388         } else if(Ext.isDate(value)) {
1389             enc = 'd:' + value.toGMTString();
1390         } else if(Ext.isArray(value)) {
1391             for (len = value.length; i < len; i++) {
1392                 flat += this.encodeValue(value[i]);
1393                 if (i != len - 1) {
1394                     flat += '^';
1395                 }
1396             }
1397             enc = 'a:' + flat;
1398         } else if (typeof value == 'object') {
1399             for (key in value) {
1400                 if (typeof value[key] != 'function' && value[key] !== undefined) {
1401                     flat += key + '=' + this.encodeValue(value[key]) + '^';
1402                 }
1403             }
1404             enc = 'o:' + flat.substring(0, flat.length-1);
1405         } else {
1406             enc = 's:' + value;
1407         }
1408         return escape(enc);
1409     }
1410 });
1411 /**
1412  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
1413  * Ext.container.Container on the document with a similar syntax to a CSS selector.
1414  *
1415  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
1416  *
1417  * - `component` or `.component`
1418  * - `gridpanel` or `.gridpanel`
1419  *
1420  * An itemId or id must be prefixed with a #
1421  *
1422  * - `#myContainer`
1423  *
1424  * Attributes must be wrapped in brackets
1425  *
1426  * - `component[autoScroll]`
1427  * - `panel[title="Test"]`
1428  *
1429  * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
1430  * the candidate Component will be included in the query:
1431  *
1432  *     var disabledFields = myFormPanel.query("{isDisabled()}");
1433  *
1434  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
1435  *
1436  *     // Function receives array and returns a filtered array.
1437  *     Ext.ComponentQuery.pseudos.invalid = function(items) {
1438  *         var i = 0, l = items.length, c, result = [];
1439  *         for (; i < l; i++) {
1440  *             if (!(c = items[i]).isValid()) {
1441  *                 result.push(c);
1442  *             }
1443  *         }
1444  *         return result;
1445  *     };
1446  *      
1447  *     var invalidFields = myFormPanel.query('field:invalid');
1448  *     if (invalidFields.length) {
1449  *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
1450  *         for (var i = 0, l = invalidFields.length; i < l; i++) {
1451  *             invalidFields[i].getEl().frame("red");
1452  *         }
1453  *     }
1454  *
1455  * Default pseudos include:
1456  *
1457  * - not
1458  * - last
1459  *
1460  * Queries return an array of components.
1461  * Here are some example queries.
1462  *
1463  *     // retrieve all Ext.Panels in the document by xtype
1464  *     var panelsArray = Ext.ComponentQuery.query('panel');
1465  *
1466  *     // retrieve all Ext.Panels within the container with an id myCt
1467  *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
1468  *
1469  *     // retrieve all direct children which are Ext.Panels within myCt
1470  *     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
1471  *
1472  *     // retrieve all grids and trees
1473  *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
1474  *
1475  * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
1476  * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
1477  * {@link Ext.Component#up}.
1478  */
1479 Ext.define('Ext.ComponentQuery', {
1480     singleton: true,
1481     uses: ['Ext.ComponentManager']
1482 }, function() {
1483
1484     var cq = this,
1485
1486         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
1487         // as a member on each item in the passed array.
1488         filterFnPattern = [
1489             'var r = [],',
1490                 'i = 0,',
1491                 'it = items,',
1492                 'l = it.length,',
1493                 'c;',
1494             'for (; i < l; i++) {',
1495                 'c = it[i];',
1496                 'if (c.{0}) {',
1497                    'r.push(c);',
1498                 '}',
1499             '}',
1500             'return r;'
1501         ].join(''),
1502
1503         filterItems = function(items, operation) {
1504             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
1505             // The operation's method loops over each item in the candidate array and
1506             // returns an array of items which match its criteria
1507             return operation.method.apply(this, [ items ].concat(operation.args));
1508         },
1509
1510         getItems = function(items, mode) {
1511             var result = [],
1512                 i = 0,
1513                 length = items.length,
1514                 candidate,
1515                 deep = mode !== '>';
1516                 
1517             for (; i < length; i++) {
1518                 candidate = items[i];
1519                 if (candidate.getRefItems) {
1520                     result = result.concat(candidate.getRefItems(deep));
1521                 }
1522             }
1523             return result;
1524         },
1525
1526         getAncestors = function(items) {
1527             var result = [],
1528                 i = 0,
1529                 length = items.length,
1530                 candidate;
1531             for (; i < length; i++) {
1532                 candidate = items[i];
1533                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
1534                     result.push(candidate);
1535                 }
1536             }
1537             return result;
1538         },
1539
1540         // Filters the passed candidate array and returns only items which match the passed xtype
1541         filterByXType = function(items, xtype, shallow) {
1542             if (xtype === '*') {
1543                 return items.slice();
1544             }
1545             else {
1546                 var result = [],
1547                     i = 0,
1548                     length = items.length,
1549                     candidate;
1550                 for (; i < length; i++) {
1551                     candidate = items[i];
1552                     if (candidate.isXType(xtype, shallow)) {
1553                         result.push(candidate);
1554                     }
1555                 }
1556                 return result;
1557             }
1558         },
1559
1560         // Filters the passed candidate array and returns only items which have the passed className
1561         filterByClassName = function(items, className) {
1562             var EA = Ext.Array,
1563                 result = [],
1564                 i = 0,
1565                 length = items.length,
1566                 candidate;
1567             for (; i < length; i++) {
1568                 candidate = items[i];
1569                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
1570                     result.push(candidate);
1571                 }
1572             }
1573             return result;
1574         },
1575
1576         // Filters the passed candidate array and returns only items which have the specified property match
1577         filterByAttribute = function(items, property, operator, value) {
1578             var result = [],
1579                 i = 0,
1580                 length = items.length,
1581                 candidate;
1582             for (; i < length; i++) {
1583                 candidate = items[i];
1584                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
1585                     result.push(candidate);
1586                 }
1587             }
1588             return result;
1589         },
1590
1591         // Filters the passed candidate array and returns only items which have the specified itemId or id
1592         filterById = function(items, id) {
1593             var result = [],
1594                 i = 0,
1595                 length = items.length,
1596                 candidate;
1597             for (; i < length; i++) {
1598                 candidate = items[i];
1599                 if (candidate.getItemId() === id) {
1600                     result.push(candidate);
1601                 }
1602             }
1603             return result;
1604         },
1605
1606         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
1607         filterByPseudo = function(items, name, value) {
1608             return cq.pseudos[name](items, value);
1609         },
1610
1611         // Determines leading mode
1612         // > for direct child, and ^ to switch to ownerCt axis
1613         modeRe = /^(\s?([>\^])\s?|\s|$)/,
1614
1615         // Matches a token with possibly (true|false) appended for the "shallow" parameter
1616         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
1617
1618         matchers = [{
1619             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
1620             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
1621             method: filterByXType
1622         },{
1623             // checks for [attribute=value]
1624             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
1625             method: filterByAttribute
1626         }, {
1627             // checks for #cmpItemId
1628             re: /^#([\w\-]+)/,
1629             method: filterById
1630         }, {
1631             // checks for :<pseudo_class>(<selector>)
1632             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
1633             method: filterByPseudo
1634         }, {
1635             // checks for {<member_expression>}
1636             re: /^(?:\{([^\}]+)\})/,
1637             method: filterFnPattern
1638         }];
1639
1640     // @class Ext.ComponentQuery.Query
1641     // This internal class is completely hidden in documentation.
1642     cq.Query = Ext.extend(Object, {
1643         constructor: function(cfg) {
1644             cfg = cfg || {};
1645             Ext.apply(this, cfg);
1646         },
1647
1648         // Executes this Query upon the selected root.
1649         // The root provides the initial source of candidate Component matches which are progressively
1650         // filtered by iterating through this Query's operations cache.
1651         // If no root is provided, all registered Components are searched via the ComponentManager.
1652         // root may be a Container who's descendant Components are filtered
1653         // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
1654         // docked items within a Panel.
1655         // root may be an array of candidate Components to filter using this Query.
1656         execute : function(root) {
1657             var operations = this.operations,
1658                 i = 0,
1659                 length = operations.length,
1660                 operation,
1661                 workingItems;
1662
1663             // no root, use all Components in the document
1664             if (!root) {
1665                 workingItems = Ext.ComponentManager.all.getArray();
1666             }
1667             // Root is a candidate Array
1668             else if (Ext.isArray(root)) {
1669                 workingItems = root;
1670             }
1671
1672             // We are going to loop over our operations and take care of them
1673             // one by one.
1674             for (; i < length; i++) {
1675                 operation = operations[i];
1676
1677                 // The mode operation requires some custom handling.
1678                 // All other operations essentially filter down our current
1679                 // working items, while mode replaces our current working
1680                 // items by getting children from each one of our current
1681                 // working items. The type of mode determines the type of
1682                 // children we get. (e.g. > only gets direct children)
1683                 if (operation.mode === '^') {
1684                     workingItems = getAncestors(workingItems || [root]);
1685                 }
1686                 else if (operation.mode) {
1687                     workingItems = getItems(workingItems || [root], operation.mode);
1688                 }
1689                 else {
1690                     workingItems = filterItems(workingItems || getItems([root]), operation);
1691                 }
1692
1693                 // If this is the last operation, it means our current working
1694                 // items are the final matched items. Thus return them!
1695                 if (i === length -1) {
1696                     return workingItems;
1697                 }
1698             }
1699             return [];
1700         },
1701
1702         is: function(component) {
1703             var operations = this.operations,
1704                 components = Ext.isArray(component) ? component : [component],
1705                 originalLength = components.length,
1706                 lastOperation = operations[operations.length-1],
1707                 ln, i;
1708
1709             components = filterItems(components, lastOperation);
1710             if (components.length === originalLength) {
1711                 if (operations.length > 1) {
1712                     for (i = 0, ln = components.length; i < ln; i++) {
1713                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
1714                             return false;
1715                         }
1716                     }
1717                 }
1718                 return true;
1719             }
1720             return false;
1721         }
1722     });
1723
1724     Ext.apply(this, {
1725
1726         // private cache of selectors and matching ComponentQuery.Query objects
1727         cache: {},
1728
1729         // private cache of pseudo class filter functions
1730         pseudos: {
1731             not: function(components, selector){
1732                 var CQ = Ext.ComponentQuery,
1733                     i = 0,
1734                     length = components.length,
1735                     results = [],
1736                     index = -1,
1737                     component;
1738                 
1739                 for(; i < length; ++i) {
1740                     component = components[i];
1741                     if (!CQ.is(component, selector)) {
1742                         results[++index] = component;
1743                     }
1744                 }
1745                 return results;
1746             },
1747             last: function(components) {
1748                 return components[components.length - 1];
1749             }
1750         },
1751
1752         /**
1753          * Returns an array of matched Components from within the passed root object.
1754          *
1755          * This method filters returned Components in a similar way to how CSS selector based DOM
1756          * queries work using a textual selector string.
1757          *
1758          * See class summary for details.
1759          *
1760          * @param {String} selector The selector string to filter returned Components
1761          * @param {Ext.container.Container} root The Container within which to perform the query.
1762          * If omitted, all Components within the document are included in the search.
1763          * 
1764          * This parameter may also be an array of Components to filter according to the selector.</p>
1765          * @returns {Ext.Component[]} The matched Components.
1766          * 
1767          * @member Ext.ComponentQuery
1768          */
1769         query: function(selector, root) {
1770             var selectors = selector.split(','),
1771                 length = selectors.length,
1772                 i = 0,
1773                 results = [],
1774                 noDupResults = [], 
1775                 dupMatcher = {}, 
1776                 query, resultsLn, cmp;
1777
1778             for (; i < length; i++) {
1779                 selector = Ext.String.trim(selectors[i]);
1780                 query = this.cache[selector];
1781                 if (!query) {
1782                     this.cache[selector] = query = this.parse(selector);
1783                 }
1784                 results = results.concat(query.execute(root));
1785             }
1786
1787             // multiple selectors, potential to find duplicates
1788             // lets filter them out.
1789             if (length > 1) {
1790                 resultsLn = results.length;
1791                 for (i = 0; i < resultsLn; i++) {
1792                     cmp = results[i];
1793                     if (!dupMatcher[cmp.id]) {
1794                         noDupResults.push(cmp);
1795                         dupMatcher[cmp.id] = true;
1796                     }
1797                 }
1798                 results = noDupResults;
1799             }
1800             return results;
1801         },
1802
1803         /**
1804          * Tests whether the passed Component matches the selector string.
1805          * @param {Ext.Component} component The Component to test
1806          * @param {String} selector The selector string to test against.
1807          * @return {Boolean} True if the Component matches the selector.
1808          * @member Ext.ComponentQuery
1809          */
1810         is: function(component, selector) {
1811             if (!selector) {
1812                 return true;
1813             }
1814             var query = this.cache[selector];
1815             if (!query) {
1816                 this.cache[selector] = query = this.parse(selector);
1817             }
1818             return query.is(component);
1819         },
1820
1821         parse: function(selector) {
1822             var operations = [],
1823                 length = matchers.length,
1824                 lastSelector,
1825                 tokenMatch,
1826                 matchedChar,
1827                 modeMatch,
1828                 selectorMatch,
1829                 i, matcher, method;
1830
1831             // We are going to parse the beginning of the selector over and
1832             // over again, slicing off the selector any portions we converted into an
1833             // operation, until it is an empty string.
1834             while (selector && lastSelector !== selector) {
1835                 lastSelector = selector;
1836
1837                 // First we check if we are dealing with a token like #, * or an xtype
1838                 tokenMatch = selector.match(tokenRe);
1839
1840                 if (tokenMatch) {
1841                     matchedChar = tokenMatch[1];
1842
1843                     // If the token is prefixed with a # we push a filterById operation to our stack
1844                     if (matchedChar === '#') {
1845                         operations.push({
1846                             method: filterById,
1847                             args: [Ext.String.trim(tokenMatch[2])]
1848                         });
1849                     }
1850                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
1851                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
1852                     else if (matchedChar === '.') {
1853                         operations.push({
1854                             method: filterByClassName,
1855                             args: [Ext.String.trim(tokenMatch[2])]
1856                         });
1857                     }
1858                     // If the token is a * or an xtype string, we push a filterByXType
1859                     // operation to the stack.
1860                     else {
1861                         operations.push({
1862                             method: filterByXType,
1863                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
1864                         });
1865                     }
1866
1867                     // Now we slice of the part we just converted into an operation
1868                     selector = selector.replace(tokenMatch[0], '');
1869                 }
1870
1871                 // If the next part of the query is not a space or > or ^, it means we
1872                 // are going to check for more things that our current selection
1873                 // has to comply to.
1874                 while (!(modeMatch = selector.match(modeRe))) {
1875                     // Lets loop over each type of matcher and execute it
1876                     // on our current selector.
1877                     for (i = 0; selector && i < length; i++) {
1878                         matcher = matchers[i];
1879                         selectorMatch = selector.match(matcher.re);
1880                         method = matcher.method;
1881
1882                         // If we have a match, add an operation with the method
1883                         // associated with this matcher, and pass the regular
1884                         // expression matches are arguments to the operation.
1885                         if (selectorMatch) {
1886                             operations.push({
1887                                 method: Ext.isString(matcher.method)
1888                                     // Turn a string method into a function by formatting the string with our selector matche expression
1889                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
1890                                     // Every expression may be different in different selectors.
1891                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
1892                                     : matcher.method,
1893                                 args: selectorMatch.slice(1)
1894                             });
1895                             selector = selector.replace(selectorMatch[0], '');
1896                             break; // Break on match
1897                         }
1898                         //<debug>
1899                         // Exhausted all matches: It's an error
1900                         if (i === (length - 1)) {
1901                             Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
1902                         }
1903                         //</debug>
1904                     }
1905                 }
1906
1907                 // Now we are going to check for a mode change. This means a space
1908                 // or a > to determine if we are going to select all the children
1909                 // of the currently matched items, or a ^ if we are going to use the
1910                 // ownerCt axis as the candidate source.
1911                 if (modeMatch[1]) { // Assignment, and test for truthiness!
1912                     operations.push({
1913                         mode: modeMatch[2]||modeMatch[1]
1914                     });
1915                     selector = selector.replace(modeMatch[0], '');
1916                 }
1917             }
1918
1919             //  Now that we have all our operations in an array, we are going
1920             // to create a new Query using these operations.
1921             return new cq.Query({
1922                 operations: operations
1923             });
1924         }
1925     });
1926 });
1927 /**
1928  * @class Ext.util.HashMap
1929  * <p>
1930  * Represents a collection of a set of key and value pairs. Each key in the HashMap
1931  * must be unique, the same key cannot exist twice. Access to items is provided via
1932  * the key only. Sample usage:
1933  * <pre><code>
1934 var map = new Ext.util.HashMap();
1935 map.add('key1', 1);
1936 map.add('key2', 2);
1937 map.add('key3', 3);
1938
1939 map.each(function(key, value, length){
1940     console.log(key, value, length);
1941 });
1942  * </code></pre>
1943  * </p>
1944  *
1945  * <p>The HashMap is an unordered class,
1946  * there is no guarantee when iterating over the items that they will be in any particular
1947  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
1948  * </p>
1949  */
1950 Ext.define('Ext.util.HashMap', {
1951     mixins: {
1952         observable: 'Ext.util.Observable'
1953     },
1954
1955     /**
1956      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
1957      * A default is provided that returns the <b>id</b> property on the object. This function is only used
1958      * if the add method is called with a single argument.
1959      */
1960
1961     /**
1962      * Creates new HashMap.
1963      * @param {Object} config (optional) Config object.
1964      */
1965     constructor: function(config) {
1966         config = config || {};
1967         
1968         var me = this,
1969             keyFn = config.keyFn;
1970
1971         me.addEvents(
1972             /**
1973              * @event add
1974              * Fires when a new item is added to the hash
1975              * @param {Ext.util.HashMap} this.
1976              * @param {String} key The key of the added item.
1977              * @param {Object} value The value of the added item.
1978              */
1979             'add',
1980             /**
1981              * @event clear
1982              * Fires when the hash is cleared.
1983              * @param {Ext.util.HashMap} this.
1984              */
1985             'clear',
1986             /**
1987              * @event remove
1988              * Fires when an item is removed from the hash.
1989              * @param {Ext.util.HashMap} this.
1990              * @param {String} key The key of the removed item.
1991              * @param {Object} value The value of the removed item.
1992              */
1993             'remove',
1994             /**
1995              * @event replace
1996              * Fires when an item is replaced in the hash.
1997              * @param {Ext.util.HashMap} this.
1998              * @param {String} key The key of the replaced item.
1999              * @param {Object} value The new value for the item.
2000              * @param {Object} old The old value for the item.
2001              */
2002             'replace'
2003         );
2004
2005         me.mixins.observable.constructor.call(me, config);
2006         me.clear(true);
2007         
2008         if (keyFn) {
2009             me.getKey = keyFn;
2010         }
2011     },
2012
2013     /**
2014      * Gets the number of items in the hash.
2015      * @return {Number} The number of items in the hash.
2016      */
2017     getCount: function() {
2018         return this.length;
2019     },
2020
2021     /**
2022      * Implementation for being able to extract the key from an object if only
2023      * a single argument is passed.
2024      * @private
2025      * @param {String} key The key
2026      * @param {Object} value The value
2027      * @return {Array} [key, value]
2028      */
2029     getData: function(key, value) {
2030         // if we have no value, it means we need to get the key from the object
2031         if (value === undefined) {
2032             value = key;
2033             key = this.getKey(value);
2034         }
2035
2036         return [key, value];
2037     },
2038
2039     /**
2040      * Extracts the key from an object. This is a default implementation, it may be overridden
2041      * @param {Object} o The object to get the key from
2042      * @return {String} The key to use.
2043      */
2044     getKey: function(o) {
2045         return o.id;
2046     },
2047
2048     /**
2049      * Adds an item to the collection. Fires the {@link #add} event when complete.
2050      * @param {String} key <p>The key to associate with the item, or the new item.</p>
2051      * <p>If a {@link #getKey} implementation was specified for this HashMap,
2052      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
2053      * the HashMap will be able to <i>derive</i> the key for the new item.
2054      * In this case just pass the new item in this parameter.</p>
2055      * @param {Object} o The item to add.
2056      * @return {Object} The item added.
2057      */
2058     add: function(key, value) {
2059         var me = this,
2060             data;
2061
2062         if (arguments.length === 1) {
2063             value = key;
2064             key = me.getKey(value);
2065         }
2066
2067         if (me.containsKey(key)) {
2068             return me.replace(key, value);
2069         }
2070
2071         data = me.getData(key, value);
2072         key = data[0];
2073         value = data[1];
2074         me.map[key] = value;
2075         ++me.length;
2076         me.fireEvent('add', me, key, value);
2077         return value;
2078     },
2079
2080     /**
2081      * Replaces an item in the hash. If the key doesn't exist, the
2082      * {@link #add} method will be used.
2083      * @param {String} key The key of the item.
2084      * @param {Object} value The new value for the item.
2085      * @return {Object} The new value of the item.
2086      */
2087     replace: function(key, value) {
2088         var me = this,
2089             map = me.map,
2090             old;
2091
2092         if (!me.containsKey(key)) {
2093             me.add(key, value);
2094         }
2095         old = map[key];
2096         map[key] = value;
2097         me.fireEvent('replace', me, key, value, old);
2098         return value;
2099     },
2100
2101     /**
2102      * Remove an item from the hash.
2103      * @param {Object} o The value of the item to remove.
2104      * @return {Boolean} True if the item was successfully removed.
2105      */
2106     remove: function(o) {
2107         var key = this.findKey(o);
2108         if (key !== undefined) {
2109             return this.removeAtKey(key);
2110         }
2111         return false;
2112     },
2113
2114     /**
2115      * Remove an item from the hash.
2116      * @param {String} key The key to remove.
2117      * @return {Boolean} True if the item was successfully removed.
2118      */
2119     removeAtKey: function(key) {
2120         var me = this,
2121             value;
2122
2123         if (me.containsKey(key)) {
2124             value = me.map[key];
2125             delete me.map[key];
2126             --me.length;
2127             me.fireEvent('remove', me, key, value);
2128             return true;
2129         }
2130         return false;
2131     },
2132
2133     /**
2134      * Retrieves an item with a particular key.
2135      * @param {String} key The key to lookup.
2136      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
2137      */
2138     get: function(key) {
2139         return this.map[key];
2140     },
2141
2142     /**
2143      * Removes all items from the hash.
2144      * @return {Ext.util.HashMap} this
2145      */
2146     clear: function(/* private */ initial) {
2147         var me = this;
2148         me.map = {};
2149         me.length = 0;
2150         if (initial !== true) {
2151             me.fireEvent('clear', me);
2152         }
2153         return me;
2154     },
2155
2156     /**
2157      * Checks whether a key exists in the hash.
2158      * @param {String} key The key to check for.
2159      * @return {Boolean} True if they key exists in the hash.
2160      */
2161     containsKey: function(key) {
2162         return this.map[key] !== undefined;
2163     },
2164
2165     /**
2166      * Checks whether a value exists in the hash.
2167      * @param {Object} value The value to check for.
2168      * @return {Boolean} True if the value exists in the dictionary.
2169      */
2170     contains: function(value) {
2171         return this.containsKey(this.findKey(value));
2172     },
2173
2174     /**
2175      * Return all of the keys in the hash.
2176      * @return {Array} An array of keys.
2177      */
2178     getKeys: function() {
2179         return this.getArray(true);
2180     },
2181
2182     /**
2183      * Return all of the values in the hash.
2184      * @return {Array} An array of values.
2185      */
2186     getValues: function() {
2187         return this.getArray(false);
2188     },
2189
2190     /**
2191      * Gets either the keys/values in an array from the hash.
2192      * @private
2193      * @param {Boolean} isKey True to extract the keys, otherwise, the value
2194      * @return {Array} An array of either keys/values from the hash.
2195      */
2196     getArray: function(isKey) {
2197         var arr = [],
2198             key,
2199             map = this.map;
2200         for (key in map) {
2201             if (map.hasOwnProperty(key)) {
2202                 arr.push(isKey ? key: map[key]);
2203             }
2204         }
2205         return arr;
2206     },
2207
2208     /**
2209      * Executes the specified function once for each item in the hash.
2210      * Returning false from the function will cease iteration.
2211      *
2212      * The paramaters passed to the function are:
2213      * <div class="mdetail-params"><ul>
2214      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
2215      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
2216      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
2217      * </ul></div>
2218      * @param {Function} fn The function to execute.
2219      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
2220      * @return {Ext.util.HashMap} this
2221      */
2222     each: function(fn, scope) {
2223         // copy items so they may be removed during iteration.
2224         var items = Ext.apply({}, this.map),
2225             key,
2226             length = this.length;
2227
2228         scope = scope || this;
2229         for (key in items) {
2230             if (items.hasOwnProperty(key)) {
2231                 if (fn.call(scope, key, items[key], length) === false) {
2232                     break;
2233                 }
2234             }
2235         }
2236         return this;
2237     },
2238
2239     /**
2240      * Performs a shallow copy on this hash.
2241      * @return {Ext.util.HashMap} The new hash object.
2242      */
2243     clone: function() {
2244         var hash = new this.self(),
2245             map = this.map,
2246             key;
2247
2248         hash.suspendEvents();
2249         for (key in map) {
2250             if (map.hasOwnProperty(key)) {
2251                 hash.add(key, map[key]);
2252             }
2253         }
2254         hash.resumeEvents();
2255         return hash;
2256     },
2257
2258     /**
2259      * @private
2260      * Find the key for a value.
2261      * @param {Object} value The value to find.
2262      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
2263      */
2264     findKey: function(value) {
2265         var key,
2266             map = this.map;
2267
2268         for (key in map) {
2269             if (map.hasOwnProperty(key) && map[key] === value) {
2270                 return key;
2271             }
2272         }
2273         return undefined;
2274     }
2275 });
2276
2277 /**
2278  * @class Ext.state.Manager
2279  * This is the global state manager. By default all components that are "state aware" check this class
2280  * for state information if you don't pass them a custom state provider. In order for this class
2281  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
2282  <pre><code>
2283 // in your initialization function
2284 init : function(){
2285    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
2286    var win = new Window(...);
2287    win.restoreState();
2288 }
2289  </code></pre>
2290  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
2291  * there is a common interface that can be used without needing to refer to a specific provider instance
2292  * in every component.
2293  * @singleton
2294  * @docauthor Evan Trimboli <evan@sencha.com>
2295  */
2296 Ext.define('Ext.state.Manager', {
2297     singleton: true,
2298     requires: ['Ext.state.Provider'],
2299     constructor: function() {
2300         this.provider = Ext.create('Ext.state.Provider');
2301     },
2302     
2303     
2304     /**
2305      * Configures the default state provider for your application
2306      * @param {Ext.state.Provider} stateProvider The state provider to set
2307      */
2308     setProvider : function(stateProvider){
2309         this.provider = stateProvider;
2310     },
2311
2312     /**
2313      * Returns the current value for a key
2314      * @param {String} name The key name
2315      * @param {Object} defaultValue The default value to return if the key lookup does not match
2316      * @return {Object} The state data
2317      */
2318     get : function(key, defaultValue){
2319         return this.provider.get(key, defaultValue);
2320     },
2321
2322     /**
2323      * Sets the value for a key
2324      * @param {String} name The key name
2325      * @param {Object} value The state data
2326      */
2327      set : function(key, value){
2328         this.provider.set(key, value);
2329     },
2330
2331     /**
2332      * Clears a value from the state
2333      * @param {String} name The key name
2334      */
2335     clear : function(key){
2336         this.provider.clear(key);
2337     },
2338
2339     /**
2340      * Gets the currently configured state provider
2341      * @return {Ext.state.Provider} The state provider
2342      */
2343     getProvider : function(){
2344         return this.provider;
2345     }
2346 });
2347 /**
2348  * @class Ext.state.Stateful
2349  * A mixin for being able to save the state of an object to an underlying
2350  * {@link Ext.state.Provider}.
2351  */
2352 Ext.define('Ext.state.Stateful', {
2353
2354     /* Begin Definitions */
2355
2356    mixins: {
2357         observable: 'Ext.util.Observable'
2358     },
2359
2360     requires: ['Ext.state.Manager'],
2361
2362     /* End Definitions */
2363
2364     /**
2365      * @cfg {Boolean} stateful
2366      * <p>A flag which causes the object to attempt to restore the state of
2367      * internal properties from a saved state on startup. The object must have
2368      * a <code>{@link #stateId}</code> for state to be managed.
2369      * Auto-generated ids are not guaranteed to be stable across page loads and
2370      * cannot be relied upon to save and restore the same state for a object.<p>
2371      * <p>For state saving to work, the state manager's provider must have been
2372      * set to an implementation of {@link Ext.state.Provider} which overrides the
2373      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
2374      * methods to save and recall name/value pairs. A built-in implementation,
2375      * {@link Ext.state.CookieProvider} is available.</p>
2376      * <p>To set the state provider for the current page:</p>
2377      * <pre><code>
2378 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
2379     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
2380 }));
2381      * </code></pre>
2382      * <p>A stateful object attempts to save state when one of the events
2383      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
2384      * <p>To save state, a stateful object first serializes its state by
2385      * calling <b><code>{@link #getState}</code></b>. By default, this function does
2386      * nothing. The developer must provide an implementation which returns an
2387      * object hash which represents the restorable state of the object.</p>
2388      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
2389      * which uses the configured {@link Ext.state.Provider} to save the object
2390      * keyed by the <code>{@link #stateId}</code>.</p>
2391      * <p>During construction, a stateful object attempts to <i>restore</i>
2392      * its state by calling {@link Ext.state.Manager#get} passing the
2393      * <code>{@link #stateId}</code></p>
2394      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
2395      * The default implementation of <code>{@link #applyState}</code> simply copies
2396      * properties into the object, but a developer may override this to support
2397      * more behaviour.</p>
2398      * <p>You can perform extra processing on state save and restore by attaching
2399      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
2400      * {@link #beforestatesave} and {@link #statesave} events.</p>
2401      */
2402     stateful: true,
2403
2404     /**
2405      * @cfg {String} stateId
2406      * The unique id for this object to use for state management purposes.
2407      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
2408      */
2409
2410     /**
2411      * @cfg {String[]} stateEvents
2412      * <p>An array of events that, when fired, should trigger this object to
2413      * save its state. Defaults to none. <code>stateEvents</code> may be any type
2414      * of event supported by this object, including browser or custom events
2415      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
2416      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
2417      * restoring object state.</p>
2418      */
2419
2420     /**
2421      * @cfg {Number} saveDelay
2422      * A buffer to be applied if many state events are fired within a short period.
2423      */
2424     saveDelay: 100,
2425
2426     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
2427
2428     constructor: function(config) {
2429         var me = this;
2430
2431         config = config || {};
2432         if (Ext.isDefined(config.stateful)) {
2433             me.stateful = config.stateful;
2434         }
2435         if (Ext.isDefined(config.saveDelay)) {
2436             me.saveDelay = config.saveDelay;
2437         }
2438         me.stateId = me.stateId || config.stateId;
2439
2440         if (!me.stateEvents) {
2441             me.stateEvents = [];
2442         }
2443         if (config.stateEvents) {
2444             me.stateEvents.concat(config.stateEvents);
2445         }
2446         this.addEvents(
2447             /**
2448              * @event beforestaterestore
2449              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
2450              * @param {Ext.state.Stateful} this
2451              * @param {Object} state The hash of state values returned from the StateProvider. If this
2452              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
2453              * that simply copies property values into this object. The method maybe overriden to
2454              * provide custom state restoration.
2455              */
2456             'beforestaterestore',
2457
2458             /**
2459              * @event staterestore
2460              * Fires after the state of the object is restored.
2461              * @param {Ext.state.Stateful} this
2462              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
2463              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
2464              * object. The method maybe overriden to provide custom state restoration.
2465              */
2466             'staterestore',
2467
2468             /**
2469              * @event beforestatesave
2470              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
2471              * @param {Ext.state.Stateful} this
2472              * @param {Object} state The hash of state values. This is determined by calling
2473              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
2474              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
2475              * has a null implementation.
2476              */
2477             'beforestatesave',
2478
2479             /**
2480              * @event statesave
2481              * Fires after the state of the object is saved to the configured state provider.
2482              * @param {Ext.state.Stateful} this
2483              * @param {Object} state The hash of state values. This is determined by calling
2484              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
2485              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
2486              * has a null implementation.
2487              */
2488             'statesave'
2489         );
2490         me.mixins.observable.constructor.call(me);
2491         if (me.stateful !== false) {
2492             me.initStateEvents();
2493             me.initState();
2494         }
2495     },
2496
2497     /**
2498      * Initializes any state events for this object.
2499      * @private
2500      */
2501     initStateEvents: function() {
2502         this.addStateEvents(this.stateEvents);
2503     },
2504
2505     /**
2506      * Add events that will trigger the state to be saved.
2507      * @param {String/String[]} events The event name or an array of event names.
2508      */
2509     addStateEvents: function(events){
2510         if (!Ext.isArray(events)) {
2511             events = [events];
2512         }
2513
2514         var me = this,
2515             i = 0,
2516             len = events.length;
2517
2518         for (; i < len; ++i) {
2519             me.on(events[i], me.onStateChange, me);
2520         }
2521     },
2522
2523     /**
2524      * This method is called when any of the {@link #stateEvents} are fired.
2525      * @private
2526      */
2527     onStateChange: function(){
2528         var me = this,
2529             delay = me.saveDelay;
2530
2531         if (delay > 0) {
2532             if (!me.stateTask) {
2533                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
2534             }
2535             me.stateTask.delay(me.saveDelay);
2536         } else {
2537             me.saveState();
2538         }
2539     },
2540
2541     /**
2542      * Saves the state of the object to the persistence store.
2543      * @private
2544      */
2545     saveState: function() {
2546         var me = this,
2547             id,
2548             state;
2549
2550         if (me.stateful !== false) {
2551             id = me.getStateId();
2552             if (id) {
2553                 state = me.getState();
2554                 if (me.fireEvent('beforestatesave', me, state) !== false) {
2555                     Ext.state.Manager.set(id, state);
2556                     me.fireEvent('statesave', me, state);
2557                 }
2558             }
2559         }
2560     },
2561
2562     /**
2563      * Gets the current state of the object. By default this function returns null,
2564      * it should be overridden in subclasses to implement methods for getting the state.
2565      * @return {Object} The current state
2566      */
2567     getState: function(){
2568         return null;
2569     },
2570
2571     /**
2572      * Applies the state to the object. This should be overridden in subclasses to do
2573      * more complex state operations. By default it applies the state properties onto
2574      * the current object.
2575      * @param {Object} state The state
2576      */
2577     applyState: function(state) {
2578         if (state) {
2579             Ext.apply(this, state);
2580         }
2581     },
2582
2583     /**
2584      * Gets the state id for this object.
2585      * @return {String} The state id, null if not found.
2586      */
2587     getStateId: function() {
2588         var me = this,
2589             id = me.stateId;
2590
2591         if (!id) {
2592             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
2593         }
2594         return id;
2595     },
2596
2597     /**
2598      * Initializes the state of the object upon construction.
2599      * @private
2600      */
2601     initState: function(){
2602         var me = this,
2603             id = me.getStateId(),
2604             state;
2605
2606         if (me.stateful !== false) {
2607             if (id) {
2608                 state = Ext.state.Manager.get(id);
2609                 if (state) {
2610                     state = Ext.apply({}, state);
2611                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
2612                         me.applyState(state);
2613                         me.fireEvent('staterestore', me, state);
2614                     }
2615                 }
2616             }
2617         }
2618     },
2619
2620     /**
2621      * Conditionally saves a single property from this object to the given state object.
2622      * The idea is to only save state which has changed from the initial state so that
2623      * current software settings do not override future software settings. Only those
2624      * values that are user-changed state should be saved.
2625      *
2626      * @param {String} propName The name of the property to save.
2627      * @param {Object} state The state object in to which to save the property.
2628      * @param {String} stateName (optional) The name to use for the property in state.
2629      * @return {Boolean} True if the property was saved, false if not.
2630      */
2631     savePropToState: function (propName, state, stateName) {
2632         var me = this,
2633             value = me[propName],
2634             config = me.initialConfig;
2635
2636         if (me.hasOwnProperty(propName)) {
2637             if (!config || config[propName] !== value) {
2638                 if (state) {
2639                     state[stateName || propName] = value;
2640                 }
2641                 return true;
2642             }
2643         }
2644         return false;
2645     },
2646
2647     savePropsToState: function (propNames, state) {
2648         var me = this;
2649         Ext.each(propNames, function (propName) {
2650             me.savePropToState(propName, state);
2651         });
2652         return state;
2653     },
2654
2655     /**
2656      * Destroys this stateful object.
2657      */
2658     destroy: function(){
2659         var task = this.stateTask;
2660         if (task) {
2661             task.cancel();
2662         }
2663         this.clearListeners();
2664
2665     }
2666
2667 });
2668
2669 /**
2670  * Base Manager class
2671  */
2672 Ext.define('Ext.AbstractManager', {
2673
2674     /* Begin Definitions */
2675
2676     requires: ['Ext.util.HashMap'],
2677
2678     /* End Definitions */
2679
2680     typeName: 'type',
2681
2682     constructor: function(config) {
2683         Ext.apply(this, config || {});
2684
2685         /**
2686          * @property {Ext.util.HashMap} all
2687          * Contains all of the items currently managed
2688          */
2689         this.all = Ext.create('Ext.util.HashMap');
2690
2691         this.types = {};
2692     },
2693
2694     /**
2695      * Returns an item by id.
2696      * For additional details see {@link Ext.util.HashMap#get}.
2697      * @param {String} id The id of the item
2698      * @return {Object} The item, undefined if not found.
2699      */
2700     get : function(id) {
2701         return this.all.get(id);
2702     },
2703
2704     /**
2705      * Registers an item to be managed
2706      * @param {Object} item The item to register
2707      */
2708     register: function(item) {
2709         //<debug>
2710         var all = this.all,
2711             key = all.getKey(item);
2712             
2713         if (all.containsKey(key)) {
2714             Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
2715         }
2716         //</debug>
2717         this.all.add(item);
2718     },
2719
2720     /**
2721      * Unregisters an item by removing it from this manager
2722      * @param {Object} item The item to unregister
2723      */
2724     unregister: function(item) {
2725         this.all.remove(item);
2726     },
2727
2728     /**
2729      * Registers a new item constructor, keyed by a type key.
2730      * @param {String} type The mnemonic string by which the class may be looked up.
2731      * @param {Function} cls The new instance class.
2732      */
2733     registerType : function(type, cls) {
2734         this.types[type] = cls;
2735         cls[this.typeName] = type;
2736     },
2737
2738     /**
2739      * Checks if an item type is registered.
2740      * @param {String} type The mnemonic string by which the class may be looked up
2741      * @return {Boolean} Whether the type is registered.
2742      */
2743     isRegistered : function(type){
2744         return this.types[type] !== undefined;
2745     },
2746
2747     /**
2748      * Creates and returns an instance of whatever this manager manages, based on the supplied type and
2749      * config object.
2750      * @param {Object} config The config object
2751      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
2752      * @return {Object} The instance of whatever this manager is managing
2753      */
2754     create: function(config, defaultType) {
2755         var type        = config[this.typeName] || config.type || defaultType,
2756             Constructor = this.types[type];
2757
2758         //<debug>
2759         if (Constructor === undefined) {
2760             Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
2761         }
2762         //</debug>
2763
2764         return new Constructor(config);
2765     },
2766
2767     /**
2768      * Registers a function that will be called when an item with the specified id is added to the manager.
2769      * This will happen on instantiation.
2770      * @param {String} id The item id
2771      * @param {Function} fn The callback function. Called with a single parameter, the item.
2772      * @param {Object} scope The scope (this reference) in which the callback is executed.
2773      * Defaults to the item.
2774      */
2775     onAvailable : function(id, fn, scope){
2776         var all = this.all,
2777             item;
2778         
2779         if (all.containsKey(id)) {
2780             item = all.get(id);
2781             fn.call(scope || item, item);
2782         } else {
2783             all.on('add', function(map, key, item){
2784                 if (key == id) {
2785                     fn.call(scope || item, item);
2786                     all.un('add', fn, scope);
2787                 }
2788             });
2789         }
2790     },
2791     
2792     /**
2793      * Executes the specified function once for each item in the collection.
2794      * @param {Function} fn The function to execute.
2795      * @param {String} fn.key The key of the item
2796      * @param {Number} fn.value The value of the item
2797      * @param {Number} fn.length The total number of items in the collection
2798      * @param {Boolean} fn.return False to cease iteration.
2799      * @param {Object} scope The scope to execute in. Defaults to `this`.
2800      */
2801     each: function(fn, scope){
2802         this.all.each(fn, scope || this);    
2803     },
2804     
2805     /**
2806      * Gets the number of items in the collection.
2807      * @return {Number} The number of items in the collection.
2808      */
2809     getCount: function(){
2810         return this.all.getCount();
2811     }
2812 });
2813
2814 /**
2815  * @class Ext.ComponentManager
2816  * @extends Ext.AbstractManager
2817  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
2818  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
2819  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
2820  * <p>This object also provides a registry of available Component <i>classes</i>
2821  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
2822  * The <code>xtype</code> provides a way to avoid instantiating child Components
2823  * when creating a full, nested config object for a complete Ext page.</p>
2824  * <p>A child Component may be specified simply as a <i>config object</i>
2825  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
2826  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
2827  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
2828  * @singleton
2829  */
2830 Ext.define('Ext.ComponentManager', {
2831     extend: 'Ext.AbstractManager',
2832     alternateClassName: 'Ext.ComponentMgr',
2833     
2834     singleton: true,
2835     
2836     typeName: 'xtype',
2837     
2838     /**
2839      * Creates a new Component from the specified config object using the
2840      * config object's xtype to determine the class to instantiate.
2841      * @param {Object} config A configuration object for the Component you wish to create.
2842      * @param {Function} defaultType (optional) The constructor to provide the default Component type if
2843      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
2844      * @return {Ext.Component} The newly instantiated Component.
2845      */
2846     create: function(component, defaultType){
2847         if (component instanceof Ext.AbstractComponent) {
2848             return component;
2849         }
2850         else if (Ext.isString(component)) {
2851             return Ext.createByAlias('widget.' + component);
2852         }
2853         else {
2854             var type = component.xtype || defaultType,
2855                 config = component;
2856             
2857             return Ext.createByAlias('widget.' + type, config);
2858         }
2859     },
2860
2861     registerType: function(type, cls) {
2862         this.types[type] = cls;
2863         cls[this.typeName] = type;
2864         cls.prototype[this.typeName] = type;
2865     }
2866 });
2867 /**
2868  * An abstract base class which provides shared methods for Components across the Sencha product line.
2869  *
2870  * Please refer to sub class's documentation
2871  * @private
2872  */
2873 Ext.define('Ext.AbstractComponent', {
2874
2875     /* Begin Definitions */
2876     requires: [
2877         'Ext.ComponentQuery',
2878         'Ext.ComponentManager'
2879     ],
2880
2881     mixins: {
2882         observable: 'Ext.util.Observable',
2883         animate: 'Ext.util.Animate',
2884         state: 'Ext.state.Stateful'
2885     },
2886
2887     // The "uses" property specifies class which are used in an instantiated AbstractComponent.
2888     // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
2889     uses: [
2890         'Ext.PluginManager',
2891         'Ext.ComponentManager',
2892         'Ext.Element',
2893         'Ext.DomHelper',
2894         'Ext.XTemplate',
2895         'Ext.ComponentQuery',
2896         'Ext.ComponentLoader',
2897         'Ext.EventManager',
2898         'Ext.layout.Layout',
2899         'Ext.layout.component.Auto',
2900         'Ext.LoadMask',
2901         'Ext.ZIndexManager'
2902     ],
2903
2904     statics: {
2905         AUTO_ID: 1000
2906     },
2907
2908     /* End Definitions */
2909
2910     isComponent: true,
2911
2912     getAutoId: function() {
2913         return ++Ext.AbstractComponent.AUTO_ID;
2914     },
2915
2916
2917     /**
2918      * @cfg {String} id
2919      * The **unique id of this component instance.**
2920      *
2921      * It should not be necessary to use this configuration except for singleton objects in your application. Components
2922      * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
2923      *
2924      * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
2925      * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
2926      * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
2927      * its descendant Components by selector.
2928      *
2929      * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
2930      * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
2931      * component uniquely, and also to select sub-elements using this component's id as the parent.
2932      *
2933      * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
2934      *
2935      * **Note**: to access the container of a Component see `{@link #ownerCt}`.
2936      *
2937      * Defaults to an {@link #getId auto-assigned id}.
2938      */
2939
2940     /**
2941      * @cfg {String} itemId
2942      * An itemId can be used as an alternative way to get a reference to a component when no object reference is
2943      * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
2944      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
2945      * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
2946      * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
2947      * which requires a **unique** `{@link #id}`.
2948      *
2949      *     var c = new Ext.panel.Panel({ //
2950      *         {@link Ext.Component#height height}: 300,
2951      *         {@link #renderTo}: document.body,
2952      *         {@link Ext.container.Container#layout layout}: 'auto',
2953      *         {@link Ext.container.Container#items items}: [
2954      *             {
2955      *                 itemId: 'p1',
2956      *                 {@link Ext.panel.Panel#title title}: 'Panel 1',
2957      *                 {@link Ext.Component#height height}: 150
2958      *             },
2959      *             {
2960      *                 itemId: 'p2',
2961      *                 {@link Ext.panel.Panel#title title}: 'Panel 2',
2962      *                 {@link Ext.Component#height height}: 150
2963      *             }
2964      *         ]
2965      *     })
2966      *     p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
2967      *     p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
2968      *
2969      * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
2970      * `{@link Ext.container.Container#child}`.
2971      *
2972      * **Note**: to access the container of an item see {@link #ownerCt}.
2973      */
2974
2975     /**
2976      * @property {Ext.Container} ownerCt
2977      * This Component's owner {@link Ext.container.Container Container} (is set automatically
2978      * when this Component is added to a Container). Read-only.
2979      *
2980      * **Note**: to access items within the Container see {@link #itemId}.
2981      */
2982
2983     /**
2984      * @property {Boolean} layoutManagedWidth
2985      * @private
2986      * Flag set by the container layout to which this Component is added.
2987      * If the layout manages this Component's width, it sets the value to 1.
2988      * If it does NOT manage the width, it sets it to 2.
2989      * If the layout MAY affect the width, but only if the owning Container has a fixed width, this is set to 0.
2990      */
2991
2992     /**
2993      * @property {Boolean} layoutManagedHeight
2994      * @private
2995      * Flag set by the container layout to which this Component is added.
2996      * If the layout manages this Component's height, it sets the value to 1.
2997      * If it does NOT manage the height, it sets it to 2.
2998      * If the layout MAY affect the height, but only if the owning Container has a fixed height, this is set to 0.
2999      */
3000
3001     /**
3002      * @cfg {String/Object} autoEl
3003      * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
3004      * encapsulate this Component.
3005      *
3006      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
3007      * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
3008      * complex DOM structure specified by their own {@link #renderTpl}s.
3009      *
3010      * This is intended to allow the developer to create application-specific utility Components encapsulated by
3011      * different DOM elements. Example usage:
3012      *
3013      *     {
3014      *         xtype: 'component',
3015      *         autoEl: {
3016      *             tag: 'img',
3017      *             src: 'http://www.example.com/example.jpg'
3018      *         }
3019      *     }, {
3020      *         xtype: 'component',
3021      *         autoEl: {
3022      *             tag: 'blockquote',
3023      *             html: 'autoEl is cool!'
3024      *         }
3025      *     }, {
3026      *         xtype: 'container',
3027      *         autoEl: 'ul',
3028      *         cls: 'ux-unordered-list',
3029      *         items: {
3030      *             xtype: 'component',
3031      *             autoEl: 'li',
3032      *             html: 'First list item'
3033      *         }
3034      *     }
3035      */
3036
3037     /**
3038      * @cfg {Ext.XTemplate/String/String[]} renderTpl
3039      * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
3040      * {@link #getEl Element}.
3041      *
3042      * You do not normally need to specify this. For the base classes {@link Ext.Component} and
3043      * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
3044      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
3045      * classes which use a more complex DOM structure, provide their own template definitions.
3046      *
3047      * This is intended to allow the developer to create application-specific utility Components with customized
3048      * internal structure.
3049      *
3050      * Upon rendering, any created child elements may be automatically imported into object properties using the
3051      * {@link #renderSelectors} and {@link #childEls} options.
3052      */
3053     renderTpl: null,
3054
3055     /**
3056      * @cfg {Object} renderData
3057      *
3058      * The data used by {@link #renderTpl} in addition to the following property values of the component:
3059      *
3060      * - id
3061      * - ui
3062      * - uiCls
3063      * - baseCls
3064      * - componentCls
3065      * - frame
3066      *
3067      * See {@link #renderSelectors} and {@link #childEls} for usage examples.
3068      */
3069
3070     /**
3071      * @cfg {Object} renderSelectors
3072      * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
3073      * created by the render process.
3074      *
3075      * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
3076      * and the found Elements are added as properties to the Component using the `renderSelector` property name.
3077      *
3078      * For example, a Component which renderes a title and description into its element:
3079      *
3080      *     Ext.create('Ext.Component', {
3081      *         renderTo: Ext.getBody(),
3082      *         renderTpl: [
3083      *             '<h1 class="title">{title}</h1>',
3084      *             '<p>{desc}</p>'
3085      *         ],
3086      *         renderData: {
3087      *             title: "Error",
3088      *             desc: "Something went wrong"
3089      *         },
3090      *         renderSelectors: {
3091      *             titleEl: 'h1.title',
3092      *             descEl: 'p'
3093      *         },
3094      *         listeners: {
3095      *             afterrender: function(cmp){
3096      *                 // After rendering the component will have a titleEl and descEl properties
3097      *                 cmp.titleEl.setStyle({color: "red"});
3098      *             }
3099      *         }
3100      *     });
3101      *
3102      * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
3103      * Component after render), see {@link #childEls} and {@link #addChildEls}.
3104      */
3105
3106     /**
3107      * @cfg {Object[]} childEls
3108      * An array describing the child elements of the Component. Each member of the array
3109      * is an object with these properties:
3110      *
3111      * - `name` - The property name on the Component for the child element.
3112      * - `itemId` - The id to combine with the Component's id that is the id of the child element.
3113      * - `id` - The id of the child element.
3114      *
3115      * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
3116      *
3117      * For example, a Component which renders a title and body text:
3118      *
3119      *     Ext.create('Ext.Component', {
3120      *         renderTo: Ext.getBody(),
3121      *         renderTpl: [
3122      *             '<h1 id="{id}-title">{title}</h1>',
3123      *             '<p>{msg}</p>',
3124      *         ],
3125      *         renderData: {
3126      *             title: "Error",
3127      *             msg: "Something went wrong"
3128      *         },
3129      *         childEls: ["title"],
3130      *         listeners: {
3131      *             afterrender: function(cmp){
3132      *                 // After rendering the component will have a title property
3133      *                 cmp.title.setStyle({color: "red"});
3134      *             }
3135      *         }
3136      *     });
3137      *
3138      * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
3139      */
3140
3141     /**
3142      * @cfg {String/HTMLElement/Ext.Element} renderTo
3143      * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
3144      *
3145      * **Notes:**
3146      *
3147      * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
3148      * It is the responsibility of the {@link Ext.container.Container Container}'s
3149      * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
3150      *
3151      * When using this config, a call to render() is not required.
3152      *
3153      * See `{@link #render}` also.
3154      */
3155
3156     /**
3157      * @cfg {Boolean} frame
3158      * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
3159      * graphical rounded frame around the Component content.
3160      *
3161      * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
3162      * Explorer prior to version 9 which do not support rounded corners natively.
3163      *
3164      * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
3165      */
3166
3167     /**
3168      * @property {Object} frameSize
3169      * Read-only property indicating the width of any framing elements which were added within the encapsulating element
3170      * to provide graphical, rounded borders. See the {@link #frame} config.
3171      *
3172      * This is an object containing the frame width in pixels for all four sides of the Component containing the
3173      * following properties:
3174      *
3175      * @property {Number} frameSize.top The width of the top framing element in pixels.
3176      * @property {Number} frameSize.right The width of the right framing element in pixels.
3177      * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
3178      * @property {Number} frameSize.left The width of the left framing element in pixels.
3179      */
3180
3181     /**
3182      * @cfg {String/Object} componentLayout
3183      * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
3184      * manager which sizes a Component's internal structure in response to the Component being sized.
3185      *
3186      * Generally, developers will not use this configuration as all provided Components which need their internal
3187      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
3188      *
3189      * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
3190      * class which simply sizes the Component's encapsulating element to the height and width specified in the
3191      * {@link #setSize} method.
3192      */
3193
3194     /**
3195      * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
3196      * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
3197      * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
3198      */
3199
3200     /**
3201      * @cfg {Object} data
3202      * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
3203      */
3204
3205     /**
3206      * @cfg {String} xtype
3207      * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
3208      * shortcut to the full componet name. For example, the component `Ext.button.Button` has an xtype of `button`.
3209      *
3210      * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
3211      * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
3212      *
3213      *     Ext.define('PressMeButton', {
3214      *         extend: 'Ext.button.Button',
3215      *         alias: 'widget.pressmebutton',
3216      *         text: 'Press Me'
3217      *     })
3218      *
3219      * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
3220      * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
3221      * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
3222      * until they are actually needed. In complex, nested layouts containing many Components, this can make a
3223      * noticeable improvement in performance.
3224      *
3225      *     // Explicit creation of contained Components:
3226      *     var panel = new Ext.Panel({
3227      *        ...
3228      *        items: [
3229      *           Ext.create('Ext.button.Button', {
3230      *              text: 'OK'
3231      *           })
3232      *        ]
3233      *     };
3234      *
3235      *     // Implicit creation using xtype:
3236      *     var panel = new Ext.Panel({
3237      *        ...
3238      *        items: [{
3239      *           xtype: 'button',
3240      *           text: 'OK'
3241      *        }]
3242      *     };
3243      *
3244      * In the first example, the button will always be created immediately during the panel's initialization. With
3245      * many added Components, this approach could potentially slow the rendering of the page. In the second example,
3246      * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
3247      * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
3248      * will never consume any resources whatsoever.
3249      */
3250
3251     /**
3252      * @cfg {String} tplWriteMode
3253      * The Ext.(X)Template method to use when updating the content area of the Component.
3254      * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
3255      */
3256     tplWriteMode: 'overwrite',
3257
3258     /**
3259      * @cfg {String} [baseCls='x-component']
3260      * The base CSS class to apply to this components's element. This will also be prepended to elements within this
3261      * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
3262      * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
3263      * componentCls to add specific styling for this component.
3264      */
3265     baseCls: Ext.baseCSSPrefix + 'component',
3266
3267     /**
3268      * @cfg {String} componentCls
3269      * CSS Class to be added to a components root level element to give distinction to it via styling.
3270      */
3271
3272     /**
3273      * @cfg {String} [cls='']
3274      * An optional extra CSS class that will be added to this component's Element. This can be useful
3275      * for adding customized styles to the component or any of its children using standard CSS rules.
3276      */
3277
3278     /**
3279      * @cfg {String} [overCls='']
3280      * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
3281      * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
3282      * component or any of its children using standard CSS rules.
3283      */
3284
3285     /**
3286      * @cfg {String} [disabledCls='x-item-disabled']
3287      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
3288      */
3289     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
3290
3291     /**
3292      * @cfg {String/String[]} ui
3293      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
3294      */
3295     ui: 'default',
3296
3297     /**
3298      * @cfg {String[]} uiCls
3299      * An array of of classNames which are currently applied to this component
3300      * @private
3301      */
3302     uiCls: [],
3303
3304     /**
3305      * @cfg {String} style
3306      * A custom style specification to be applied to this component's Element. Should be a valid argument to
3307      * {@link Ext.Element#applyStyles}.
3308      *
3309      *     new Ext.panel.Panel({
3310      *         title: 'Some Title',
3311      *         renderTo: Ext.getBody(),
3312      *         width: 400, height: 300,
3313      *         layout: 'form',
3314      *         items: [{
3315      *             xtype: 'textarea',
3316      *             style: {
3317      *                 width: '95%',
3318      *                 marginBottom: '10px'
3319      *             }
3320      *         },
3321      *         new Ext.button.Button({
3322      *             text: 'Send',
3323      *             minWidth: '100',
3324      *             style: {
3325      *                 marginBottom: '10px'
3326      *             }
3327      *         })
3328      *         ]
3329      *     });
3330      */
3331
3332     /**
3333      * @cfg {Number} width
3334      * The width of this component in pixels.
3335      */
3336
3337     /**
3338      * @cfg {Number} height
3339      * The height of this component in pixels.
3340      */
3341
3342     /**
3343      * @cfg {Number/String} border
3344      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
3345      * be a CSS style specification for each style, for example: '10 5 3 10'.
3346      */
3347
3348     /**
3349      * @cfg {Number/String} padding
3350      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
3351      * can be a CSS style specification for each style, for example: '10 5 3 10'.
3352      */
3353
3354     /**
3355      * @cfg {Number/String} margin
3356      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
3357      * be a CSS style specification for each style, for example: '10 5 3 10'.
3358      */
3359
3360     /**
3361      * @cfg {Boolean} hidden
3362      * True to hide the component.
3363      */
3364     hidden: false,
3365
3366     /**
3367      * @cfg {Boolean} disabled
3368      * True to disable the component.
3369      */
3370     disabled: false,
3371
3372     /**
3373      * @cfg {Boolean} [draggable=false]
3374      * Allows the component to be dragged.
3375      */
3376
3377     /**
3378      * @property {Boolean} draggable
3379      * Read-only property indicating whether or not the component can be dragged
3380      */
3381     draggable: false,
3382
3383     /**
3384      * @cfg {Boolean} floating
3385      * Create the Component as a floating and use absolute positioning.
3386      *
3387      * 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
3388      * by the global {@link Ext.WindowManager WindowManager}.
3389      *
3390      * 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
3391      * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
3392      *
3393      * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
3394      */
3395     floating: false,
3396
3397     /**
3398      * @cfg {String} hideMode
3399      * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
3400      *
3401      *   - `'display'` : The Component will be hidden using the `display: none` style.
3402      *   - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
3403      *   - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
3404      *     This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
3405      *     Component having zero dimensions.
3406      */
3407     hideMode: 'display',
3408
3409     /**
3410      * @cfg {String} contentEl
3411      * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
3412      *
3413      * This config option is used to take an existing HTML element and place it in the layout element of a new component
3414      * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
3415      *
3416      * **Notes:**
3417      *
3418      * The specified HTML element is appended to the layout element of the component _after any configured
3419      * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
3420      * the {@link #render} event is fired.
3421      *
3422      * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
3423      * scheme that the Component may use. It is just HTML. Layouts operate on child
3424      * **`{@link Ext.container.Container#items items}`**.
3425      *
3426      * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
3427      * is rendered to the panel.
3428      */
3429
3430     /**
3431      * @cfg {String/Object} [html='']
3432      * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
3433      * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
3434      * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
3435      * is appended.
3436      */
3437
3438     /**
3439      * @cfg {Boolean} styleHtmlContent
3440      * True to automatically style the html inside the content target of this component (body for panels).
3441      */
3442     styleHtmlContent: false,
3443
3444     /**
3445      * @cfg {String} [styleHtmlCls='x-html']
3446      * The class that is added to the content target when you set styleHtmlContent to true.
3447      */
3448     styleHtmlCls: Ext.baseCSSPrefix + 'html',
3449
3450     /**
3451      * @cfg {Number} minHeight
3452      * The minimum value in pixels which this Component will set its height to.
3453      *
3454      * **Warning:** This will override any size management applied by layout managers.
3455      */
3456     /**
3457      * @cfg {Number} minWidth
3458      * The minimum value in pixels which this Component will set its width to.
3459      *
3460      * **Warning:** This will override any size management applied by layout managers.
3461      */
3462     /**
3463      * @cfg {Number} maxHeight
3464      * The maximum value in pixels which this Component will set its height to.
3465      *
3466      * **Warning:** This will override any size management applied by layout managers.
3467      */
3468     /**
3469      * @cfg {Number} maxWidth
3470      * The maximum value in pixels which this Component will set its width to.
3471      *
3472      * **Warning:** This will override any size management applied by layout managers.
3473      */
3474
3475     /**
3476      * @cfg {Ext.ComponentLoader/Object} loader
3477      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
3478      */
3479
3480     /**
3481      * @cfg {Boolean} autoShow
3482      * True to automatically show the component upon creation. This config option may only be used for
3483      * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
3484      */
3485     autoShow: false,
3486
3487     /**
3488      * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
3489      * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
3490      * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
3491      * upon first _{@link #show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
3492      *
3493      * Specify as `true` to have this Component render to the document body upon first show.
3494      *
3495      * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
3496      * show.
3497      *
3498      * **This defaults to `true` for the {@link Ext.window.Window Window} class.**
3499      */
3500     autoRender: false,
3501
3502     needsLayout: false,
3503
3504     // @private
3505     allowDomMove: true,
3506
3507     /**
3508      * @cfg {Object/Object[]} plugins
3509      * An object or array of objects that will provide custom functionality for this component. The only requirement for
3510      * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
3511      * is created, if any plugins are available, the component will call the init method on each plugin, passing a
3512      * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
3513      * its functionality.
3514      */
3515
3516     /**
3517      * @property {Boolean} rendered
3518      * Read-only property indicating whether or not the component has been rendered.
3519      */
3520     rendered: false,
3521
3522     /**
3523      * @property {Number} componentLayoutCounter
3524      * @private
3525      * The number of component layout calls made on this object.
3526      */
3527     componentLayoutCounter: 0,
3528
3529     weight: 0,
3530
3531     trimRe: /^\s+|\s+$/g,
3532     spacesRe: /\s+/,
3533
3534
3535     /**
3536      * @property {Boolean} maskOnDisable
3537      * This is an internal flag that you use when creating custom components. By default this is set to true which means
3538      * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
3539      * override this property to false since they want to implement custom disable logic.
3540      */
3541     maskOnDisable: true,
3542
3543     /**
3544      * Creates new Component.
3545      * @param {Object} config  (optional) Config object.
3546      */
3547     constructor : function(config) {
3548         var me = this,
3549             i, len;
3550
3551         config = config || {};
3552         me.initialConfig = config;
3553         Ext.apply(me, config);
3554
3555         me.addEvents(
3556             /**
3557              * @event beforeactivate
3558              * Fires before a Component has been visually activated. Returning false from an event listener can prevent
3559              * the activate from occurring.
3560              * @param {Ext.Component} this
3561              */
3562             'beforeactivate',
3563             /**
3564              * @event activate
3565              * Fires after a Component has been visually activated.
3566              * @param {Ext.Component} this
3567              */
3568             'activate',
3569             /**
3570              * @event beforedeactivate
3571              * Fires before a Component has been visually deactivated. Returning false from an event listener can
3572              * prevent the deactivate from occurring.
3573              * @param {Ext.Component} this
3574              */
3575             'beforedeactivate',
3576             /**
3577              * @event deactivate
3578              * Fires after a Component has been visually deactivated.
3579              * @param {Ext.Component} this
3580              */
3581             'deactivate',
3582             /**
3583              * @event added
3584              * Fires after a Component had been added to a Container.
3585              * @param {Ext.Component} this
3586              * @param {Ext.container.Container} container Parent Container
3587              * @param {Number} pos position of Component
3588              */
3589             'added',
3590             /**
3591              * @event disable
3592              * Fires after the component is disabled.
3593              * @param {Ext.Component} this
3594              */
3595             'disable',
3596             /**
3597              * @event enable
3598              * Fires after the component is enabled.
3599              * @param {Ext.Component} this
3600              */
3601             'enable',
3602             /**
3603              * @event beforeshow
3604              * Fires before the component is shown when calling the {@link #show} method. Return false from an event
3605              * handler to stop the show.
3606              * @param {Ext.Component} this
3607              */
3608             'beforeshow',
3609             /**
3610              * @event show
3611              * Fires after the component is shown when calling the {@link #show} method.
3612              * @param {Ext.Component} this
3613              */
3614             'show',
3615             /**
3616              * @event beforehide
3617              * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
3618              * handler to stop the hide.
3619              * @param {Ext.Component} this
3620              */
3621             'beforehide',
3622             /**
3623              * @event hide
3624              * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
3625              * method.
3626              * @param {Ext.Component} this
3627              */
3628             'hide',
3629             /**
3630              * @event removed
3631              * Fires when a component is removed from an Ext.container.Container
3632              * @param {Ext.Component} this
3633              * @param {Ext.container.Container} ownerCt Container which holds the component
3634              */
3635             'removed',
3636             /**
3637              * @event beforerender
3638              * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
3639              * {@link #render}.
3640              * @param {Ext.Component} this
3641              */
3642             'beforerender',
3643             /**
3644              * @event render
3645              * Fires after the component markup is {@link #rendered}.
3646              * @param {Ext.Component} this
3647              */
3648             'render',
3649             /**
3650              * @event afterrender
3651              * Fires after the component rendering is finished.
3652              *
3653              * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
3654              * afterRender method defined for the Component.
3655              * @param {Ext.Component} this
3656              */
3657             'afterrender',
3658             /**
3659              * @event beforedestroy
3660              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the
3661              * {@link #destroy}.
3662              * @param {Ext.Component} this
3663              */
3664             'beforedestroy',
3665             /**
3666              * @event destroy
3667              * Fires after the component is {@link #destroy}ed.
3668              * @param {Ext.Component} this
3669              */
3670             'destroy',
3671             /**
3672              * @event resize
3673              * Fires after the component is resized.
3674              * @param {Ext.Component} this
3675              * @param {Number} adjWidth The box-adjusted width that was set
3676              * @param {Number} adjHeight The box-adjusted height that was set
3677              */
3678             'resize',
3679             /**
3680              * @event move
3681              * Fires after the component is moved.
3682              * @param {Ext.Component} this
3683              * @param {Number} x The new x position
3684              * @param {Number} y The new y position
3685              */
3686             'move'
3687         );
3688
3689         me.getId();
3690
3691         me.mons = [];
3692         me.additionalCls = [];
3693         me.renderData = me.renderData || {};
3694         me.renderSelectors = me.renderSelectors || {};
3695
3696         if (me.plugins) {
3697             me.plugins = [].concat(me.plugins);
3698             me.constructPlugins();
3699         }
3700
3701         me.initComponent();
3702
3703         // ititComponent gets a chance to change the id property before registering
3704         Ext.ComponentManager.register(me);
3705
3706         // Dont pass the config so that it is not applied to 'this' again
3707         me.mixins.observable.constructor.call(me);
3708         me.mixins.state.constructor.call(me, config);
3709
3710         // Save state on resize.
3711         this.addStateEvents('resize');
3712
3713         // Move this into Observable?
3714         if (me.plugins) {
3715             me.plugins = [].concat(me.plugins);
3716             for (i = 0, len = me.plugins.length; i < len; i++) {
3717                 me.plugins[i] = me.initPlugin(me.plugins[i]);
3718             }
3719         }
3720
3721         me.loader = me.getLoader();
3722
3723         if (me.renderTo) {
3724             me.render(me.renderTo);
3725             // EXTJSIV-1935 - should be a way to do afterShow or something, but that
3726             // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
3727             // implications to afterRender so we cannot do that.
3728         }
3729
3730         if (me.autoShow) {
3731             me.show();
3732         }
3733
3734         //<debug>
3735         if (Ext.isDefined(me.disabledClass)) {
3736             if (Ext.isDefined(Ext.global.console)) {
3737                 Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
3738             }
3739             me.disabledCls = me.disabledClass;
3740             delete me.disabledClass;
3741         }
3742         //</debug>
3743     },
3744
3745     initComponent: function () {
3746         // This is called again here to allow derived classes to add plugin configs to the
3747         // plugins array before calling down to this, the base initComponent.
3748         this.constructPlugins();
3749     },
3750
3751     /**
3752      * The supplied default state gathering method for the AbstractComponent class.
3753      *
3754      * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
3755      * state.
3756      *
3757      * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
3758      * to the result if this basic state is to be saved.
3759      *
3760      * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
3761      * configured for the document.
3762      *
3763      * @return {Object}
3764      */
3765     getState: function() {
3766         var me = this,
3767             layout = me.ownerCt ? (me.shadowOwnerCt || me.ownerCt).getLayout() : null,
3768             state = {
3769                 collapsed: me.collapsed
3770             },
3771             width = me.width,
3772             height = me.height,
3773             cm = me.collapseMemento,
3774             anchors;
3775
3776         // If a Panel-local collapse has taken place, use remembered values as the dimensions.
3777         // TODO: remove this coupling with Panel's privates! All collapse/expand logic should be refactored into one place.
3778         if (me.collapsed && cm) {
3779             if (Ext.isDefined(cm.data.width)) {
3780                 width = cm.width;
3781             }
3782             if (Ext.isDefined(cm.data.height)) {
3783                 height = cm.height;
3784             }
3785         }
3786
3787         // If we have flex, only store the perpendicular dimension.
3788         if (layout && me.flex) {
3789             state.flex = me.flex;
3790             if (layout.perpendicularPrefix) {
3791                 state[layout.perpendicularPrefix] = me['get' + layout.perpendicularPrefixCap]();
3792             } else {
3793                 //<debug>
3794                 if (Ext.isDefined(Ext.global.console)) {
3795                     Ext.global.console.warn('Ext.Component: Specified a flex value on a component not inside a Box layout');
3796                 }
3797                 //</debug>
3798             }
3799         }
3800         // If we have anchor, only store dimensions which are *not* being anchored
3801         else if (layout && me.anchor) {
3802             state.anchor = me.anchor;
3803             anchors = me.anchor.split(' ').concat(null);
3804             if (!anchors[0]) {
3805                 if (me.width) {
3806                     state.width = width;
3807                 }
3808             }
3809             if (!anchors[1]) {
3810                 if (me.height) {
3811                     state.height = height;
3812                 }
3813             }
3814         }
3815         // Store dimensions.
3816         else {
3817             if (me.width) {
3818                 state.width = width;
3819             }
3820             if (me.height) {
3821                 state.height = height;
3822             }
3823         }
3824
3825         // Don't save dimensions if they are unchanged from the original configuration.
3826         if (state.width == me.initialConfig.width) {
3827             delete state.width;
3828         }
3829         if (state.height == me.initialConfig.height) {
3830             delete state.height;
3831         }
3832
3833         // If a Box layout was managing the perpendicular dimension, don't save that dimension
3834         if (layout && layout.align && (layout.align.indexOf('stretch') !== -1)) {
3835             delete state[layout.perpendicularPrefix];
3836         }
3837         return state;
3838     },
3839
3840     show: Ext.emptyFn,
3841
3842     animate: function(animObj) {
3843         var me = this,
3844             to;
3845
3846         animObj = animObj || {};
3847         to = animObj.to || {};
3848
3849         if (Ext.fx.Manager.hasFxBlock(me.id)) {
3850             return me;
3851         }
3852         // Special processing for animating Component dimensions.
3853         if (!animObj.dynamic && (to.height || to.width)) {
3854             var curWidth = me.getWidth(),
3855                 w = curWidth,
3856                 curHeight = me.getHeight(),
3857                 h = curHeight,
3858                 needsResize = false;
3859
3860             if (to.height && to.height > curHeight) {
3861                 h = to.height;
3862                 needsResize = true;
3863             }
3864             if (to.width && to.width > curWidth) {
3865                 w = to.width;
3866                 needsResize = true;
3867             }
3868
3869             // If any dimensions are being increased, we must resize the internal structure
3870             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
3871             // The animation will then progressively reveal the larger content.
3872             if (needsResize) {
3873                 var clearWidth = !Ext.isNumber(me.width),
3874                     clearHeight = !Ext.isNumber(me.height);
3875
3876                 me.componentLayout.childrenChanged = true;
3877                 me.setSize(w, h, me.ownerCt);
3878                 me.el.setSize(curWidth, curHeight);
3879                 if (clearWidth) {
3880                     delete me.width;
3881                 }
3882                 if (clearHeight) {
3883                     delete me.height;
3884                 }
3885             }
3886         }
3887         return me.mixins.animate.animate.apply(me, arguments);
3888     },
3889
3890     /**
3891      * This method finds the topmost active layout who's processing will eventually determine the size and position of
3892      * this Component.
3893      *
3894      * This method is useful when dynamically adding Components into Containers, and some processing must take place
3895      * after the final sizing and positioning of the Component has been performed.
3896      *
3897      * @return {Ext.Component}
3898      */
3899     findLayoutController: function() {
3900         return this.findParentBy(function(c) {
3901             // Return true if we are at the root of the Container tree
3902             // or this Container's layout is busy but the next one up is not.
3903             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
3904         });
3905     },
3906
3907     onShow : function() {
3908         // Layout if needed
3909         var needsLayout = this.needsLayout;
3910         if (Ext.isObject(needsLayout)) {
3911             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
3912         }
3913     },
3914
3915     constructPlugin: function(plugin) {
3916         if (plugin.ptype && typeof plugin.init != 'function') {
3917             plugin.cmp = this;
3918             plugin = Ext.PluginManager.create(plugin);
3919         }
3920         else if (typeof plugin == 'string') {
3921             plugin = Ext.PluginManager.create({
3922                 ptype: plugin,
3923                 cmp: this
3924             });
3925         }
3926         return plugin;
3927     },
3928
3929     /**
3930      * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
3931      * appropriate instances.
3932      */
3933     constructPlugins: function() {
3934         var me = this,
3935             plugins = me.plugins,
3936             i, len;
3937
3938         if (plugins) {
3939             for (i = 0, len = plugins.length; i < len; i++) {
3940                 // this just returns already-constructed plugin instances...
3941                 plugins[i] = me.constructPlugin(plugins[i]);
3942             }
3943         }
3944     },
3945
3946     // @private
3947     initPlugin : function(plugin) {
3948         plugin.init(this);
3949
3950         return plugin;
3951     },
3952
3953     /**
3954      * Handles autoRender. Floating Components may have an ownerCt. If they are asking to be constrained, constrain them
3955      * within that ownerCt, and have their z-index managed locally. Floating Components are always rendered to
3956      * document.body
3957      */
3958     doAutoRender: function() {
3959         var me = this;
3960         if (me.floating) {
3961             me.render(document.body);
3962         } else {
3963             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
3964         }
3965     },
3966
3967     // @private
3968     render : function(container, position) {
3969         var me = this;
3970
3971         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
3972
3973             // Flag set during the render process.
3974             // It can be used to inhibit event-driven layout calls during the render phase
3975             me.rendering = true;
3976
3977             // If this.el is defined, we want to make sure we are dealing with
3978             // an Ext Element.
3979             if (me.el) {
3980                 me.el = Ext.get(me.el);
3981             }
3982
3983             // Perform render-time processing for floating Components
3984             if (me.floating) {
3985                 me.onFloatRender();
3986             }
3987
3988             container = me.initContainer(container);
3989
3990             me.onRender(container, position);
3991
3992             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
3993             // This means DISPLAY, VISIBILITY or OFFSETS.
3994             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
3995
3996             if (me.overCls) {
3997                 me.el.hover(me.addOverCls, me.removeOverCls, me);
3998             }
3999
4000             me.fireEvent('render', me);
4001
4002             me.initContent();
4003
4004             me.afterRender(container);
4005             me.fireEvent('afterrender', me);
4006
4007             me.initEvents();
4008
4009             if (me.hidden) {
4010                 // Hiding during the render process should not perform any ancillary
4011                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
4012                 // So just make the element hidden according to the configured hideMode
4013                 me.el.hide();
4014             }
4015
4016             if (me.disabled) {
4017                 // pass silent so the event doesn't fire the first time.
4018                 me.disable(true);
4019             }
4020
4021             // Delete the flag once the rendering is done.
4022             delete me.rendering;
4023         }
4024         return me;
4025     },
4026
4027     // @private
4028     onRender : function(container, position) {
4029         var me = this,
4030             el = me.el,
4031             styles = me.initStyles(),
4032             renderTpl, renderData, i;
4033
4034         position = me.getInsertPosition(position);
4035
4036         if (!el) {
4037             if (position) {
4038                 el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
4039             }
4040             else {
4041                 el = Ext.DomHelper.append(container, me.getElConfig(), true);
4042             }
4043         }
4044         else if (me.allowDomMove !== false) {
4045             if (position) {
4046                 container.dom.insertBefore(el.dom, position);
4047             } else {
4048                 container.dom.appendChild(el.dom);
4049             }
4050         }
4051
4052         if (Ext.scopeResetCSS && !me.ownerCt) {
4053             // If this component's el is the body element, we add the reset class to the html tag
4054             if (el.dom == Ext.getBody().dom) {
4055                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
4056             }
4057             else {
4058                 // Else we wrap this element in an element that adds the reset class.
4059                 me.resetEl = el.wrap({
4060                     cls: Ext.baseCSSPrefix + 'reset'
4061                 });
4062             }
4063         }
4064
4065         me.setUI(me.ui);
4066
4067         el.addCls(me.initCls());
4068         el.setStyle(styles);
4069
4070         // Here we check if the component has a height set through style or css.
4071         // If it does then we set the this.height to that value and it won't be
4072         // considered an auto height component
4073         // if (this.height === undefined) {
4074         //     var height = el.getHeight();
4075         //     // This hopefully means that the panel has an explicit height set in style or css
4076         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
4077         //         this.height = height;
4078         //     }
4079         // }
4080
4081         me.el = el;
4082
4083         me.initFrame();
4084
4085         renderTpl = me.initRenderTpl();
4086         if (renderTpl) {
4087             renderData = me.initRenderData();
4088             renderTpl.append(me.getTargetEl(), renderData);
4089         }
4090
4091         me.applyRenderSelectors();
4092
4093         me.rendered = true;
4094     },
4095
4096     // @private
4097     afterRender : function() {
4098         var me = this,
4099             pos,
4100             xy;
4101
4102         me.getComponentLayout();
4103
4104         // Set the size if a size is configured, or if this is the outermost Container.
4105         // Also, if this is a collapsed Panel, it needs an initial component layout
4106         // to lay out its header so that it can have a height determined.
4107         if (me.collapsed || (!me.ownerCt || (me.height || me.width))) {
4108             me.setSize(me.width, me.height);
4109         } else {
4110             // It is expected that child items be rendered before this method returns and
4111             // the afterrender event fires. Since we aren't going to do the layout now, we
4112             // must render the child items. This is handled implicitly above in the layout
4113             // caused by setSize.
4114             me.renderChildren();
4115         }
4116
4117         // For floaters, calculate x and y if they aren't defined by aligning
4118         // the sized element to the center of either the container or the ownerCt
4119         if (me.floating && (me.x === undefined || me.y === undefined)) {
4120             if (me.floatParent) {
4121                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
4122                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
4123             } else {
4124                 xy = me.el.getAlignToXY(me.container, 'c-c');
4125                 pos = me.container.translatePoints(xy[0], xy[1]);
4126             }
4127             me.x = me.x === undefined ? pos.left: me.x;
4128             me.y = me.y === undefined ? pos.top: me.y;
4129         }
4130
4131         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
4132             me.setPosition(me.x, me.y);
4133         }
4134
4135         if (me.styleHtmlContent) {
4136             me.getTargetEl().addCls(me.styleHtmlCls);
4137         }
4138     },
4139
4140     /**
4141      * @private
4142      * Called by Component#doAutoRender
4143      *
4144      * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
4145      *
4146      * Components added in ths way will not participate in any layout, but will be rendered
4147      * upon first show in the way that {@link Ext.window.Window Window}s are.
4148      */
4149     registerFloatingItem: function(cmp) {
4150         var me = this;
4151         if (!me.floatingItems) {
4152             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
4153         }
4154         me.floatingItems.register(cmp);
4155     },
4156
4157     renderChildren: function () {
4158         var me = this,
4159             layout = me.getComponentLayout();
4160
4161         me.suspendLayout = true;
4162         layout.renderChildren();
4163         delete me.suspendLayout;
4164     },
4165
4166     frameCls: Ext.baseCSSPrefix + 'frame',
4167
4168     frameIdRegex: /[-]frame\d+[TMB][LCR]$/,
4169
4170     frameElementCls: {
4171         tl: [],
4172         tc: [],
4173         tr: [],
4174         ml: [],
4175         mc: [],
4176         mr: [],
4177         bl: [],
4178         bc: [],
4179         br: []
4180     },
4181
4182     frameTpl: [
4183         '<tpl if="top">',
4184             '<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>',
4185                 '<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>',
4186                     '<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>',
4187                 '<tpl if="right"></div></tpl>',
4188             '<tpl if="left"></div></tpl>',
4189         '</tpl>',
4190         '<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>',
4191             '<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>',
4192                 '<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>',
4193             '<tpl if="right"></div></tpl>',
4194         '<tpl if="left"></div></tpl>',
4195         '<tpl if="bottom">',
4196             '<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>',
4197                 '<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>',
4198                     '<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>',
4199                 '<tpl if="right"></div></tpl>',
4200             '<tpl if="left"></div></tpl>',
4201         '</tpl>'
4202     ],
4203
4204     frameTableTpl: [
4205         '<table><tbody>',
4206             '<tpl if="top">',
4207                 '<tr>',
4208                     '<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>',
4209                     '<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>',
4210                     '<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>',
4211                 '</tr>',
4212             '</tpl>',
4213             '<tr>',
4214                 '<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>',
4215                 '<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>',
4216                 '<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>',
4217             '</tr>',
4218             '<tpl if="bottom">',
4219                 '<tr>',
4220                     '<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>',
4221                     '<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>',
4222                     '<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>',
4223                 '</tr>',
4224             '</tpl>',
4225         '</tbody></table>'
4226     ],
4227
4228     /**
4229      * @private
4230      */
4231     initFrame : function() {
4232         if (Ext.supports.CSS3BorderRadius) {
4233             return false;
4234         }
4235
4236         var me = this,
4237             frameInfo = me.getFrameInfo(),
4238             frameWidth = frameInfo.width,
4239             frameTpl = me.getFrameTpl(frameInfo.table),
4240             frameGenId;
4241
4242         if (me.frame) {
4243             // since we render id's into the markup and id's NEED to be unique, we have a
4244             // simple strategy for numbering their generations.
4245             me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
4246             frameGenId = me.id + '-frame' + frameGenId;
4247
4248             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
4249             frameTpl.insertFirst(me.el, Ext.apply({}, {
4250                 fgid:       frameGenId,
4251                 ui:         me.ui,
4252                 uiCls:      me.uiCls,
4253                 frameCls:   me.frameCls,
4254                 baseCls:    me.baseCls,
4255                 frameWidth: frameWidth,
4256                 top:        !!frameInfo.top,
4257                 left:       !!frameInfo.left,
4258                 right:      !!frameInfo.right,
4259                 bottom:     !!frameInfo.bottom
4260             }, me.getFramePositions(frameInfo)));
4261
4262             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
4263             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
4264
4265             // Clean out the childEls for the old frame elements (the majority of the els)
4266             me.removeChildEls(function (c) {
4267                 return c.id && me.frameIdRegex.test(c.id);
4268             });
4269
4270             // Add the childEls for each of the new frame elements
4271             Ext.each(['TL','TC','TR','ML','MC','MR','BL','BC','BR'], function (suffix) {
4272                 me.childEls.push({ name: 'frame' + suffix, id: frameGenId + suffix });
4273             });
4274         }
4275     },
4276
4277     updateFrame: function() {
4278         if (Ext.supports.CSS3BorderRadius) {
4279             return false;
4280         }
4281
4282         var me = this,
4283             wasTable = this.frameSize && this.frameSize.table,
4284             oldFrameTL = this.frameTL,
4285             oldFrameBL = this.frameBL,
4286             oldFrameML = this.frameML,
4287             oldFrameMC = this.frameMC,
4288             newMCClassName;
4289
4290         this.initFrame();
4291
4292         if (oldFrameMC) {
4293             if (me.frame) {
4294                 // Reapply render selectors
4295                 delete me.frameTL;
4296                 delete me.frameTC;
4297                 delete me.frameTR;
4298                 delete me.frameML;
4299                 delete me.frameMC;
4300                 delete me.frameMR;
4301                 delete me.frameBL;
4302                 delete me.frameBC;
4303                 delete me.frameBR;
4304                 this.applyRenderSelectors();
4305
4306                 // Store the class names set on the new mc
4307                 newMCClassName = this.frameMC.dom.className;
4308
4309                 // Replace the new mc with the old mc
4310                 oldFrameMC.insertAfter(this.frameMC);
4311                 this.frameMC.remove();
4312
4313                 // Restore the reference to the old frame mc as the framebody
4314                 this.frameBody = this.frameMC = oldFrameMC;
4315
4316                 // Apply the new mc classes to the old mc element
4317                 oldFrameMC.dom.className = newMCClassName;
4318
4319                 // Remove the old framing
4320                 if (wasTable) {
4321                     me.el.query('> table')[1].remove();
4322                 }
4323                 else {
4324                     if (oldFrameTL) {
4325                         oldFrameTL.remove();
4326                     }
4327                     if (oldFrameBL) {
4328                         oldFrameBL.remove();
4329                     }
4330                     oldFrameML.remove();
4331                 }
4332             }
4333             else {
4334                 // We were framed but not anymore. Move all content from the old frame to the body
4335
4336             }
4337         }
4338         else if (me.frame) {
4339             this.applyRenderSelectors();
4340         }
4341     },
4342
4343     getFrameInfo: function() {
4344         if (Ext.supports.CSS3BorderRadius) {
4345             return false;
4346         }
4347
4348         var me = this,
4349             left = me.el.getStyle('background-position-x'),
4350             top = me.el.getStyle('background-position-y'),
4351             info, frameInfo = false, max;
4352
4353         // Some browsers dont support background-position-x and y, so for those
4354         // browsers let's split background-position into two parts.
4355         if (!left && !top) {
4356             info = me.el.getStyle('background-position').split(' ');
4357             left = info[0];
4358             top = info[1];
4359         }
4360
4361         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
4362         // the background position of this.el from the css to indicate to IE that this component needs
4363         // framing. We parse it here and change the markup accordingly.
4364         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
4365             max = Math.max;
4366
4367             frameInfo = {
4368                 // Table markup starts with 110, div markup with 100.
4369                 table: left.substr(0, 3) == '110',
4370
4371                 // Determine if we are dealing with a horizontal or vertical component
4372                 vertical: top.substr(0, 3) == '110',
4373
4374                 // Get and parse the different border radius sizes
4375                 top:    max(left.substr(3, 2), left.substr(5, 2)),
4376                 right:  max(left.substr(5, 2), top.substr(3, 2)),
4377                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
4378                 left:   max(top.substr(5, 2), left.substr(3, 2))
4379             };
4380
4381             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
4382
4383             // Just to be sure we set the background image of the el to none.
4384             me.el.setStyle('background-image', 'none');
4385         }
4386
4387         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
4388         // This way IE can't figure out what sizes to use and thus framing can't work.
4389         if (me.frame === true && !frameInfo) {
4390             //<debug error>
4391             Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
4392                             "framing defined in the CSS template. In this case IE can't figure out what sizes " +
4393                             "to use and thus framing on this component will be disabled.");
4394             //</debug>
4395         }
4396
4397         me.frame = me.frame || !!frameInfo;
4398         me.frameSize = frameInfo || false;
4399
4400         return frameInfo;
4401     },
4402
4403     getFramePositions: function(frameInfo) {
4404         var me = this,
4405             frameWidth = frameInfo.width,
4406             dock = me.dock,
4407             positions, tc, bc, ml, mr;
4408
4409         if (frameInfo.vertical) {
4410             tc = '0 -' + (frameWidth * 0) + 'px';
4411             bc = '0 -' + (frameWidth * 1) + 'px';
4412
4413             if (dock && dock == "right") {
4414                 tc = 'right -' + (frameWidth * 0) + 'px';
4415                 bc = 'right -' + (frameWidth * 1) + 'px';
4416             }
4417
4418             positions = {
4419                 tl: '0 -' + (frameWidth * 0) + 'px',
4420                 tr: '0 -' + (frameWidth * 1) + 'px',
4421                 bl: '0 -' + (frameWidth * 2) + 'px',
4422                 br: '0 -' + (frameWidth * 3) + 'px',
4423
4424                 ml: '-' + (frameWidth * 1) + 'px 0',
4425                 mr: 'right 0',
4426
4427                 tc: tc,
4428                 bc: bc
4429             };
4430         } else {
4431             ml = '-' + (frameWidth * 0) + 'px 0';
4432             mr = 'right 0';
4433
4434             if (dock && dock == "bottom") {
4435                 ml = 'left bottom';
4436                 mr = 'right bottom';
4437             }
4438
4439             positions = {
4440                 tl: '0 -' + (frameWidth * 2) + 'px',
4441                 tr: 'right -' + (frameWidth * 3) + 'px',
4442                 bl: '0 -' + (frameWidth * 4) + 'px',
4443                 br: 'right -' + (frameWidth * 5) + 'px',
4444
4445                 ml: ml,
4446                 mr: mr,
4447
4448                 tc: '0 -' + (frameWidth * 0) + 'px',
4449                 bc: '0 -' + (frameWidth * 1) + 'px'
4450             };
4451         }
4452
4453         return positions;
4454     },
4455
4456     /**
4457      * @private
4458      */
4459     getFrameTpl : function(table) {
4460         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
4461     },
4462
4463     /**
4464      * Creates an array of class names from the configurations to add to this Component's `el` on render.
4465      *
4466      * Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.
4467      *
4468      * @return {String[]} An array of class names with which the Component's element will be rendered.
4469      * @private
4470      */
4471     initCls: function() {
4472         var me = this,
4473             cls = [];
4474
4475         cls.push(me.baseCls);
4476
4477         //<deprecated since=0.99>
4478         if (Ext.isDefined(me.cmpCls)) {
4479             if (Ext.isDefined(Ext.global.console)) {
4480                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
4481             }
4482             me.componentCls = me.cmpCls;
4483             delete me.cmpCls;
4484         }
4485         //</deprecated>
4486
4487         if (me.componentCls) {
4488             cls.push(me.componentCls);
4489         } else {
4490             me.componentCls = me.baseCls;
4491         }
4492         if (me.cls) {
4493             cls.push(me.cls);
4494             delete me.cls;
4495         }
4496
4497         return cls.concat(me.additionalCls);
4498     },
4499
4500     /**
4501      * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
4502      * uiCls set on the component and rename them so they include the new UI
4503      * @param {String} ui The new UI for the component
4504      */
4505     setUI: function(ui) {
4506         var me = this,
4507             oldUICls = Ext.Array.clone(me.uiCls),
4508             newUICls = [],
4509             classes = [],
4510             cls,
4511             i;
4512
4513         //loop through all exisiting uiCls and update the ui in them
4514         for (i = 0; i < oldUICls.length; i++) {
4515             cls = oldUICls[i];
4516
4517             classes = classes.concat(me.removeClsWithUI(cls, true));
4518             newUICls.push(cls);
4519         }
4520
4521         if (classes.length) {
4522             me.removeCls(classes);
4523         }
4524
4525         //remove the UI from the element
4526         me.removeUIFromElement();
4527
4528         //set the UI
4529         me.ui = ui;
4530
4531         //add the new UI to the elemend
4532         me.addUIToElement();
4533
4534         //loop through all exisiting uiCls and update the ui in them
4535         classes = [];
4536         for (i = 0; i < newUICls.length; i++) {
4537             cls = newUICls[i];
4538             classes = classes.concat(me.addClsWithUI(cls, true));
4539         }
4540
4541         if (classes.length) {
4542             me.addCls(classes);
4543         }
4544     },
4545
4546     /**
4547      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
4548      * component.
4549      * @param {String/String[]} cls A string or an array of strings to add to the uiCls
4550      * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
4551      */
4552     addClsWithUI: function(cls, skip) {
4553         var me = this,
4554             classes = [],
4555             i;
4556
4557         if (!Ext.isArray(cls)) {
4558             cls = [cls];
4559         }
4560
4561         for (i = 0; i < cls.length; i++) {
4562             if (cls[i] && !me.hasUICls(cls[i])) {
4563                 me.uiCls = Ext.Array.clone(me.uiCls);
4564                 me.uiCls.push(cls[i]);
4565
4566                 classes = classes.concat(me.addUIClsToElement(cls[i]));
4567             }
4568         }
4569
4570         if (skip !== true) {
4571             me.addCls(classes);
4572         }
4573
4574         return classes;
4575     },
4576
4577     /**
4578      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
4579      * elements of this component.
4580      * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
4581      */
4582     removeClsWithUI: function(cls, skip) {
4583         var me = this,
4584             classes = [],
4585             i;
4586
4587         if (!Ext.isArray(cls)) {
4588             cls = [cls];
4589         }
4590
4591         for (i = 0; i < cls.length; i++) {
4592             if (cls[i] && me.hasUICls(cls[i])) {
4593                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
4594
4595                 classes = classes.concat(me.removeUIClsFromElement(cls[i]));
4596             }
4597         }
4598
4599         if (skip !== true) {
4600             me.removeCls(classes);
4601         }
4602
4603         return classes;
4604     },
4605
4606     /**
4607      * Checks if there is currently a specified uiCls
4608      * @param {String} cls The cls to check
4609      */
4610     hasUICls: function(cls) {
4611         var me = this,
4612             uiCls = me.uiCls || [];
4613
4614         return Ext.Array.contains(uiCls, cls);
4615     },
4616
4617     /**
4618      * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
4619      * than just the components element.
4620      * @param {String} ui The UI to remove from the element
4621      */
4622     addUIClsToElement: function(cls, force) {
4623         var me = this,
4624             result = [],
4625             frameElementCls = me.frameElementCls;
4626
4627         result.push(Ext.baseCSSPrefix + cls);
4628         result.push(me.baseCls + '-' + cls);
4629         result.push(me.baseCls + '-' + me.ui + '-' + cls);
4630
4631         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
4632             // define each element of the frame
4633             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
4634                 classes, i, j, el;
4635
4636             // loop through each of them, and if they are defined add the ui
4637             for (i = 0; i < els.length; i++) {
4638                 el = me['frame' + els[i].toUpperCase()];
4639                 classes = [me.baseCls + '-' + me.ui + '-' + els[i], me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]];
4640                 if (el && el.dom) {
4641                     el.addCls(classes);
4642                 } else {
4643                     for (j = 0; j < classes.length; j++) {
4644                         if (Ext.Array.indexOf(frameElementCls[els[i]], classes[j]) == -1) {
4645                             frameElementCls[els[i]].push(classes[j]);
4646                         }
4647                     }
4648                 }
4649             }
4650         }
4651
4652         me.frameElementCls = frameElementCls;
4653
4654         return result;
4655     },
4656
4657     /**
4658      * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
4659      * will be: `this.baseCls + '-' + ui`
4660      * @param {String} ui The UI to add to the element
4661      */
4662     removeUIClsFromElement: function(cls, force) {
4663         var me = this,
4664             result = [],
4665             frameElementCls = me.frameElementCls;
4666
4667         result.push(Ext.baseCSSPrefix + cls);
4668         result.push(me.baseCls + '-' + cls);
4669         result.push(me.baseCls + '-' + me.ui + '-' + cls);
4670
4671         if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
4672             // define each element of the frame
4673             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
4674                 i, el;
4675             cls = me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i];
4676             // loop through each of them, and if they are defined add the ui
4677             for (i = 0; i < els.length; i++) {
4678                 el = me['frame' + els[i].toUpperCase()];
4679                 if (el && el.dom) {
4680                     el.removeCls(cls);
4681                 } else {
4682                     Ext.Array.remove(frameElementCls[els[i]], cls);
4683                 }
4684             }
4685         }
4686
4687         me.frameElementCls = frameElementCls;
4688
4689         return result;
4690     },
4691
4692     /**
4693      * Method which adds a specified UI to the components element.
4694      * @private
4695      */
4696     addUIToElement: function(force) {
4697         var me = this,
4698             frameElementCls = me.frameElementCls;
4699
4700         me.addCls(me.baseCls + '-' + me.ui);
4701
4702         if (me.frame && !Ext.supports.CSS3BorderRadius) {
4703             // define each element of the frame
4704             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
4705                 i, el, cls;
4706
4707             // loop through each of them, and if they are defined add the ui
4708             for (i = 0; i < els.length; i++) {
4709                 el = me['frame' + els[i].toUpperCase()];
4710                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
4711                 if (el) {
4712                     el.addCls(cls);
4713                 } else {
4714                     if (!Ext.Array.contains(frameElementCls[els[i]], cls)) {
4715                         frameElementCls[els[i]].push(cls);
4716                     }
4717                 }
4718             }
4719         }
4720     },
4721
4722     /**
4723      * Method which removes a specified UI from the components element.
4724      * @private
4725      */
4726     removeUIFromElement: function() {
4727         var me = this,
4728             frameElementCls = me.frameElementCls;
4729
4730         me.removeCls(me.baseCls + '-' + me.ui);
4731
4732         if (me.frame && !Ext.supports.CSS3BorderRadius) {
4733             // define each element of the frame
4734             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
4735                 i, j, el, cls;
4736
4737             // loop through each of them, and if they are defined add the ui
4738             for (i = 0; i < els.length; i++) {
4739                 el = me['frame' + els[i].toUpperCase()];
4740                 cls = me.baseCls + '-' + me.ui + '-' + els[i];
4741
4742                 if (el) {
4743                     el.removeCls(cls);
4744                 } else {
4745                     Ext.Array.remove(frameElementCls[els[i]], cls);
4746                 }
4747             }
4748         }
4749     },
4750
4751     getElConfig : function() {
4752         if (Ext.isString(this.autoEl)) {
4753             this.autoEl = {
4754                 tag: this.autoEl
4755             };
4756         }
4757
4758         var result = this.autoEl || {tag: 'div'};
4759         result.id = this.id;
4760         return result;
4761     },
4762
4763     /**
4764      * This function takes the position argument passed to onRender and returns a DOM element that you can use in the
4765      * insertBefore.
4766      * @param {String/Number/Ext.Element/HTMLElement} position Index, element id or element you want to put this
4767      * component before.
4768      * @return {HTMLElement} DOM element that you can use in the insertBefore
4769      */
4770     getInsertPosition: function(position) {
4771         // Convert the position to an element to insert before
4772         if (position !== undefined) {
4773             if (Ext.isNumber(position)) {
4774                 position = this.container.dom.childNodes[position];
4775             }
4776             else {
4777                 position = Ext.getDom(position);
4778             }
4779         }
4780
4781         return position;
4782     },
4783
4784     /**
4785      * Adds ctCls to container.
4786      * @return {Ext.Element} The initialized container
4787      * @private
4788      */
4789     initContainer: function(container) {
4790         var me = this;
4791
4792         // If you render a component specifying the el, we get the container
4793         // of the el, and make sure we dont move the el around in the dom
4794         // during the render
4795         if (!container && me.el) {
4796             container = me.el.dom.parentNode;
4797             me.allowDomMove = false;
4798         }
4799
4800         me.container = Ext.get(container);
4801
4802         if (me.ctCls) {
4803             me.container.addCls(me.ctCls);
4804         }
4805
4806         return me.container;
4807     },
4808
4809     /**
4810      * Initialized the renderData to be used when rendering the renderTpl.
4811      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
4812      * @private
4813      */
4814     initRenderData: function() {
4815         var me = this;
4816
4817         return Ext.applyIf(me.renderData, {
4818             id: me.id,
4819             ui: me.ui,
4820             uiCls: me.uiCls,
4821             baseCls: me.baseCls,
4822             componentCls: me.componentCls,
4823             frame: me.frame
4824         });
4825     },
4826
4827     /**
4828      * @private
4829      */
4830     getTpl: function(name) {
4831         var me = this,
4832             prototype = me.self.prototype,
4833             ownerPrototype,
4834             tpl;
4835
4836         if (me.hasOwnProperty(name)) {
4837             tpl = me[name];
4838             if (tpl && !(tpl instanceof Ext.XTemplate)) {
4839                 me[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
4840             }
4841
4842             return me[name];
4843         }
4844
4845         if (!(prototype[name] instanceof Ext.XTemplate)) {
4846             ownerPrototype = prototype;
4847
4848             do {
4849                 if (ownerPrototype.hasOwnProperty(name)) {
4850                     tpl = ownerPrototype[name];
4851                     if (tpl && !(tpl instanceof Ext.XTemplate)) {
4852                         ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
4853                         break;
4854                     }
4855                 }
4856
4857                 ownerPrototype = ownerPrototype.superclass;
4858             } while (ownerPrototype);
4859         }
4860
4861         return prototype[name];
4862     },
4863
4864     /**
4865      * Initializes the renderTpl.
4866      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
4867      * @private
4868      */
4869     initRenderTpl: function() {
4870         return this.getTpl('renderTpl');
4871     },
4872
4873     /**
4874      * Converts style definitions to String.
4875      * @return {String} A CSS style string with style, padding, margin and border.
4876      * @private
4877      */
4878     initStyles: function() {
4879         var style = {},
4880             me = this,
4881             Element = Ext.Element;
4882
4883         if (Ext.isString(me.style)) {
4884             style = Element.parseStyles(me.style);
4885         } else {
4886             style = Ext.apply({}, me.style);
4887         }
4888
4889         // Convert the padding, margin and border properties from a space seperated string
4890         // into a proper style string
4891         if (me.padding !== undefined) {
4892             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
4893         }
4894
4895         if (me.margin !== undefined) {
4896             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
4897         }
4898
4899         delete me.style;
4900         return style;
4901     },
4902
4903     /**
4904      * Initializes this components contents. It checks for the properties html, contentEl and tpl/data.
4905      * @private
4906      */
4907     initContent: function() {
4908         var me = this,
4909             target = me.getTargetEl(),
4910             contentEl,
4911             pre;
4912
4913         if (me.html) {
4914             target.update(Ext.DomHelper.markup(me.html));
4915             delete me.html;
4916         }
4917
4918         if (me.contentEl) {
4919             contentEl = Ext.get(me.contentEl);
4920             pre = Ext.baseCSSPrefix;
4921             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
4922             target.appendChild(contentEl.dom);
4923         }
4924
4925         if (me.tpl) {
4926             // Make sure this.tpl is an instantiated XTemplate
4927             if (!me.tpl.isTemplate) {
4928                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
4929             }
4930
4931             if (me.data) {
4932                 me.tpl[me.tplWriteMode](target, me.data);
4933                 delete me.data;
4934             }
4935         }
4936     },
4937
4938     // @private
4939     initEvents : function() {
4940         var me = this,
4941             afterRenderEvents = me.afterRenderEvents,
4942             el,
4943             property,
4944             fn = function(listeners){
4945                 me.mon(el, listeners);
4946             };
4947         if (afterRenderEvents) {
4948             for (property in afterRenderEvents) {
4949                 if (afterRenderEvents.hasOwnProperty(property)) {
4950                     el = me[property];
4951                     if (el && el.on) {
4952                         Ext.each(afterRenderEvents[property], fn);
4953                     }
4954                 }
4955             }
4956         }
4957     },
4958
4959     /**
4960      * Adds each argument passed to this method to the {@link #childEls} array.
4961      */
4962     addChildEls: function () {
4963         var me = this,
4964             childEls = me.childEls || (me.childEls = []);
4965
4966         childEls.push.apply(childEls, arguments);
4967     },
4968
4969     /**
4970      * Removes items in the childEls array based on the return value of a supplied test function. The function is called
4971      * with a entry in childEls and if the test function return true, that entry is removed. If false, that entry is
4972      * kept.
4973      * @param {Function} testFn The test function.
4974      */
4975     removeChildEls: function (testFn) {
4976         var me = this,
4977             old = me.childEls,
4978             keepers = (me.childEls = []),
4979             n, i, cel;
4980
4981         for (i = 0, n = old.length; i < n; ++i) {
4982             cel = old[i];
4983             if (!testFn(cel)) {
4984                 keepers.push(cel);
4985             }
4986         }
4987     },
4988
4989     /**
4990      * Sets references to elements inside the component. This applies {@link #renderSelectors}
4991      * as well as {@link #childEls}.
4992      * @private
4993      */
4994     applyRenderSelectors: function() {
4995         var me = this,
4996             childEls = me.childEls,
4997             selectors = me.renderSelectors,
4998             el = me.el,
4999             dom = el.dom,
5000             baseId, childName, childId, i, selector;
5001
5002         if (childEls) {
5003             baseId = me.id + '-';
5004             for (i = childEls.length; i--; ) {
5005                 childName = childId = childEls[i];
5006                 if (typeof(childName) != 'string') {
5007                     childId = childName.id || (baseId + childName.itemId);
5008                     childName = childName.name;
5009                 } else {
5010                     childId = baseId + childId;
5011                 }
5012
5013                 // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
5014                 // we know the el's are children of our el we use getById instead:
5015                 me[childName] = el.getById(childId);
5016             }
5017         }
5018
5019         // We still support renderSelectors. There are a few places in the framework that
5020         // need them and they are a documented part of the API. In fact, we support mixing
5021         // childEls and renderSelectors (no reason not to).
5022         if (selectors) {
5023             for (selector in selectors) {
5024                 if (selectors.hasOwnProperty(selector) && selectors[selector]) {
5025                     me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
5026                 }
5027             }
5028         }
5029     },
5030
5031     /**
5032      * Tests whether this Component matches the selector string.
5033      * @param {String} selector The selector string to test against.
5034      * @return {Boolean} True if this Component matches the selector.
5035      */
5036     is: function(selector) {
5037         return Ext.ComponentQuery.is(this, selector);
5038     },
5039
5040     /**
5041      * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
5042      *
5043      * Example:
5044      *
5045      *     var owningTabPanel = grid.up('tabpanel');
5046      *
5047      * @param {String} [selector] The simple selector to test.
5048      * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
5049      */
5050     up: function(selector) {
5051         var result = this.ownerCt;
5052         if (selector) {
5053             for (; result; result = result.ownerCt) {
5054                 if (Ext.ComponentQuery.is(result, selector)) {
5055                     return result;
5056                 }
5057             }
5058         }
5059         return result;
5060     },
5061
5062     /**
5063      * Returns the next sibling of this Component.
5064      *
5065      * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
5066      *
5067      * May also be refered to as **`next()`**
5068      *
5069      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
5070      * {@link #nextNode}
5071      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
5072      * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
5073      * Returns null if there is no matching sibling.
5074      */
5075     nextSibling: function(selector) {
5076         var o = this.ownerCt, it, last, idx, c;
5077         if (o) {
5078             it = o.items;
5079             idx = it.indexOf(this) + 1;
5080             if (idx) {
5081                 if (selector) {
5082                     for (last = it.getCount(); idx < last; idx++) {
5083                         if ((c = it.getAt(idx)).is(selector)) {
5084                             return c;
5085                         }
5086                     }
5087                 } else {
5088                     if (idx < it.getCount()) {
5089                         return it.getAt(idx);
5090                     }
5091                 }
5092             }
5093         }
5094         return null;
5095     },
5096
5097     /**
5098      * Returns the previous sibling of this Component.
5099      *
5100      * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
5101      * selector.
5102      *
5103      * May also be refered to as **`prev()`**
5104      *
5105      * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
5106      * {@link #previousNode}
5107      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
5108      * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
5109      * Returns null if there is no matching sibling.
5110      */
5111     previousSibling: function(selector) {
5112         var o = this.ownerCt, it, idx, c;
5113         if (o) {
5114             it = o.items;
5115             idx = it.indexOf(this);
5116             if (idx != -1) {
5117                 if (selector) {
5118                     for (--idx; idx >= 0; idx--) {
5119                         if ((c = it.getAt(idx)).is(selector)) {
5120                             return c;
5121                         }
5122                     }
5123                 } else {
5124                     if (idx) {
5125                         return it.getAt(--idx);
5126                     }
5127                 }
5128             }
5129         }
5130         return null;
5131     },
5132
5133     /**
5134      * Returns the previous node in the Component tree in tree traversal order.
5135      *
5136      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
5137      * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
5138      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
5139      * @return {Ext.Component} The previous node (or the previous node which matches the selector).
5140      * Returns null if there is no matching node.
5141      */
5142     previousNode: function(selector, includeSelf) {
5143         var node = this,
5144             result,
5145             it, len, i;
5146
5147         // If asked to include self, test me
5148         if (includeSelf && node.is(selector)) {
5149             return node;
5150         }
5151
5152         result = this.prev(selector);
5153         if (result) {
5154             return result;
5155         }
5156
5157         if (node.ownerCt) {
5158             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
5159                 if (it[i].query) {
5160                     result = it[i].query(selector);
5161                     result = result[result.length - 1];
5162                     if (result) {
5163                         return result;
5164                     }
5165                 }
5166             }
5167             return node.ownerCt.previousNode(selector, true);
5168         }
5169     },
5170
5171     /**
5172      * Returns the next node in the Component tree in tree traversal order.
5173      *
5174      * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
5175      * tree to attempt to find a match. Contrast with {@link #nextSibling}.
5176      * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
5177      * @return {Ext.Component} The next node (or the next node which matches the selector).
5178      * Returns null if there is no matching node.
5179      */
5180     nextNode: function(selector, includeSelf) {
5181         var node = this,
5182             result,
5183             it, len, i;
5184
5185         // If asked to include self, test me
5186         if (includeSelf && node.is(selector)) {
5187             return node;
5188         }
5189
5190         result = this.next(selector);
5191         if (result) {
5192             return result;
5193         }
5194
5195         if (node.ownerCt) {
5196             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
5197                 if (it[i].down) {
5198                     result = it[i].down(selector);
5199                     if (result) {
5200                         return result;
5201                     }
5202                 }
5203             }
5204             return node.ownerCt.nextNode(selector);
5205         }
5206     },
5207
5208     /**
5209      * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
5210      * @return {String}
5211      */
5212     getId : function() {
5213         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
5214     },
5215
5216     getItemId : function() {
5217         return this.itemId || this.id;
5218     },
5219
5220     /**
5221      * Retrieves the top level element representing this component.
5222      * @return {Ext.core.Element}
5223      */
5224     getEl : function() {
5225         return this.el;
5226     },
5227
5228     /**
5229      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
5230      * @private
5231      */
5232     getTargetEl: function() {
5233         return this.frameBody || this.el;
5234     },
5235
5236     /**
5237      * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
5238      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
5239      *
5240      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
5241      * determination of inherited xtypes.**
5242      *
5243      * For a list of all available xtypes, see the {@link Ext.Component} header.
5244      *
5245      * Example usage:
5246      *
5247      *     var t = new Ext.form.field.Text();
5248      *     var isText = t.isXType('textfield');        // true
5249      *     var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
5250      *     var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
5251      *
5252      * @param {String} xtype The xtype to check for this Component
5253      * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
5254      * check whether this Component is descended from the xtype.
5255      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
5256      */
5257     isXType: function(xtype, shallow) {
5258         //assume a string by default
5259         if (Ext.isFunction(xtype)) {
5260             xtype = xtype.xtype;
5261             //handle being passed the class, e.g. Ext.Component
5262         } else if (Ext.isObject(xtype)) {
5263             xtype = xtype.statics().xtype;
5264             //handle being passed an instance
5265         }
5266
5267         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
5268     },
5269
5270     /**
5271      * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
5272      * {@link Ext.Component} header.
5273      *
5274      * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
5275      * determination of inherited xtypes.**
5276      *
5277      * Example usage:
5278      *
5279      *     var t = new Ext.form.field.Text();
5280      *     alert(t.getXTypes());  // alerts 'component/field/textfield'
5281      *
5282      * @return {String} The xtype hierarchy string
5283      */
5284     getXTypes: function() {
5285         var self = this.self,
5286             xtypes, parentPrototype, parentXtypes;
5287
5288         if (!self.xtypes) {
5289             xtypes = [];
5290             parentPrototype = this;
5291
5292             while (parentPrototype) {
5293                 parentXtypes = parentPrototype.xtypes;
5294
5295                 if (parentXtypes !== undefined) {
5296                     xtypes.unshift.apply(xtypes, parentXtypes);
5297                 }
5298
5299                 parentPrototype = parentPrototype.superclass;
5300             }
5301
5302             self.xtypeChain = xtypes;
5303             self.xtypes = xtypes.join('/');
5304         }
5305
5306         return self.xtypes;
5307     },
5308
5309     /**
5310      * Update the content area of a component.
5311      * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
5312      * it will use this argument as data to populate the template. If this component was not configured with a template,
5313      * the components content area will be updated via Ext.Element update
5314      * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
5315      * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
5316      * scripts have finished loading
5317      */
5318     update : function(htmlOrData, loadScripts, cb) {
5319         var me = this;
5320
5321         if (me.tpl && !Ext.isString(htmlOrData)) {
5322             me.data = htmlOrData;
5323             if (me.rendered) {
5324                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
5325             }
5326         } else {
5327             me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
5328             if (me.rendered) {
5329                 me.getTargetEl().update(me.html, loadScripts, cb);
5330             }
5331         }
5332
5333         if (me.rendered) {
5334             me.doComponentLayout();
5335         }
5336     },
5337
5338     /**
5339      * Convenience function to hide or show this component by boolean.
5340      * @param {Boolean} visible True to show, false to hide
5341      * @return {Ext.Component} this
5342      */
5343     setVisible : function(visible) {
5344         return this[visible ? 'show': 'hide']();
5345     },
5346
5347     /**
5348      * Returns true if this component is visible.
5349      *
5350      * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
5351      * determine whether this Component is truly visible to the user.
5352      *
5353      * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
5354      * dynamically laid out UIs in a hidden Container before showing them.
5355      *
5356      * @return {Boolean} True if this component is visible, false otherwise.
5357      */
5358     isVisible: function(deep) {
5359         var me = this,
5360             child = me,
5361             visible = !me.hidden,
5362             ancestor = me.ownerCt;
5363
5364         // Clear hiddenOwnerCt property
5365         me.hiddenAncestor = false;
5366         if (me.destroyed) {
5367             return false;
5368         }
5369
5370         if (deep && visible && me.rendered && ancestor) {
5371             while (ancestor) {
5372                 // If any ancestor is hidden, then this is hidden.
5373                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
5374                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
5375                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
5376                 if (ancestor.hidden || (ancestor.collapsed &&
5377                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
5378                     // Store hiddenOwnerCt property if needed
5379                     me.hiddenAncestor = ancestor;
5380                     visible = false;
5381                     break;
5382                 }
5383                 child = ancestor;
5384                 ancestor = ancestor.ownerCt;
5385             }
5386         }
5387         return visible;
5388     },
5389
5390     /**
5391      * Enable the component
5392      * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
5393      */
5394     enable: function(silent) {
5395         var me = this;
5396
5397         if (me.rendered) {
5398             me.el.removeCls(me.disabledCls);
5399             me.el.dom.disabled = false;
5400             me.onEnable();
5401         }
5402
5403         me.disabled = false;
5404
5405         if (silent !== true) {
5406             me.fireEvent('enable', me);
5407         }
5408
5409         return me;
5410     },
5411
5412     /**
5413      * Disable the component.
5414      * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
5415      */
5416     disable: function(silent) {
5417         var me = this;
5418
5419         if (me.rendered) {
5420             me.el.addCls(me.disabledCls);
5421             me.el.dom.disabled = true;
5422             me.onDisable();
5423         }
5424
5425         me.disabled = true;
5426
5427         if (silent !== true) {
5428             me.fireEvent('disable', me);
5429         }
5430
5431         return me;
5432     },
5433
5434     // @private
5435     onEnable: function() {
5436         if (this.maskOnDisable) {
5437             this.el.unmask();
5438         }
5439     },
5440
5441     // @private
5442     onDisable : function() {
5443         if (this.maskOnDisable) {
5444             this.el.mask();
5445         }
5446     },
5447
5448     /**
5449      * Method to determine whether this Component is currently disabled.
5450      * @return {Boolean} the disabled state of this Component.
5451      */
5452     isDisabled : function() {
5453         return this.disabled;
5454     },
5455
5456     /**
5457      * Enable or disable the component.
5458      * @param {Boolean} disabled True to disable.
5459      */
5460     setDisabled : function(disabled) {
5461         return this[disabled ? 'disable': 'enable']();
5462     },
5463
5464     /**
5465      * Method to determine whether this Component is currently set to hidden.
5466      * @return {Boolean} the hidden state of this Component.
5467      */
5468     isHidden : function() {
5469         return this.hidden;
5470     },
5471
5472     /**
5473      * Adds a CSS class to the top level element representing this component.
5474      * @param {String} cls The CSS class name to add
5475      * @return {Ext.Component} Returns the Component to allow method chaining.
5476      */
5477     addCls : function(className) {
5478         var me = this;
5479         if (!className) {
5480             return me;
5481         }
5482         if (!Ext.isArray(className)){
5483             className = className.replace(me.trimRe, '').split(me.spacesRe);
5484         }
5485         if (me.rendered) {
5486             me.el.addCls(className);
5487         }
5488         else {
5489             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
5490         }
5491         return me;
5492     },
5493
5494     /**
5495      * Adds a CSS class to the top level element representing this component.
5496      * @param {String} cls The CSS class name to add
5497      * @return {Ext.Component} Returns the Component to allow method chaining.
5498      */
5499     addClass : function() {
5500         return this.addCls.apply(this, arguments);
5501     },
5502
5503     /**
5504      * Removes a CSS class from the top level element representing this component.
5505      * @param {Object} className
5506      * @return {Ext.Component} Returns the Component to allow method chaining.
5507      */
5508     removeCls : function(className) {
5509         var me = this;
5510
5511         if (!className) {
5512             return me;
5513         }
5514         if (!Ext.isArray(className)){
5515             className = className.replace(me.trimRe, '').split(me.spacesRe);
5516         }
5517         if (me.rendered) {
5518             me.el.removeCls(className);
5519         }
5520         else if (me.additionalCls.length) {
5521             Ext.each(className, function(cls) {
5522                 Ext.Array.remove(me.additionalCls, cls);
5523             });
5524         }
5525         return me;
5526     },
5527
5528     //<debug>
5529     removeClass : function() {
5530         if (Ext.isDefined(Ext.global.console)) {
5531             Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
5532         }
5533         return this.removeCls.apply(this, arguments);
5534     },
5535     //</debug>
5536
5537     addOverCls: function() {
5538         var me = this;
5539         if (!me.disabled) {
5540             me.el.addCls(me.overCls);
5541         }
5542     },
5543
5544     removeOverCls: function() {
5545         this.el.removeCls(this.overCls);
5546     },
5547
5548     addListener : function(element, listeners, scope, options) {
5549         var me = this,
5550             fn,
5551             option;
5552
5553         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
5554             if (options.element) {
5555                 fn = listeners;
5556
5557                 listeners = {};
5558                 listeners[element] = fn;
5559                 element = options.element;
5560                 if (scope) {
5561                     listeners.scope = scope;
5562                 }
5563
5564                 for (option in options) {
5565                     if (options.hasOwnProperty(option)) {
5566                         if (me.eventOptionsRe.test(option)) {
5567                             listeners[option] = options[option];
5568                         }
5569                     }
5570                 }
5571             }
5572
5573             // At this point we have a variable called element,
5574             // and a listeners object that can be passed to on
5575             if (me[element] && me[element].on) {
5576                 me.mon(me[element], listeners);
5577             } else {
5578                 me.afterRenderEvents = me.afterRenderEvents || {};
5579                 if (!me.afterRenderEvents[element]) {
5580                     me.afterRenderEvents[element] = [];
5581                 }
5582                 me.afterRenderEvents[element].push(listeners);
5583             }
5584         }
5585
5586         return me.mixins.observable.addListener.apply(me, arguments);
5587     },
5588
5589     // inherit docs
5590     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
5591         var me = this,
5592             element = managedListener.options ? managedListener.options.element : null;
5593
5594         if (element) {
5595             element = me[element];
5596             if (element && element.un) {
5597                 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
5598                     element.un(managedListener.ename, managedListener.fn, managedListener.scope);
5599                     if (!isClear) {
5600                         Ext.Array.remove(me.managedListeners, managedListener);
5601                     }
5602                 }
5603             }
5604         } else {
5605             return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
5606         }
5607     },
5608
5609     /**
5610      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
5611      * @return {Ext.container.Container} the Container which owns this Component.
5612      */
5613     getBubbleTarget : function() {
5614         return this.ownerCt;
5615     },
5616
5617     /**
5618      * Method to determine whether this Component is floating.
5619      * @return {Boolean} the floating state of this component.
5620      */
5621     isFloating : function() {
5622         return this.floating;
5623     },
5624
5625     /**
5626      * Method to determine whether this Component is draggable.
5627      * @return {Boolean} the draggable state of this component.
5628      */
5629     isDraggable : function() {
5630         return !!this.draggable;
5631     },
5632
5633     /**
5634      * Method to determine whether this Component is droppable.
5635      * @return {Boolean} the droppable state of this component.
5636      */
5637     isDroppable : function() {
5638         return !!this.droppable;
5639     },
5640
5641     /**
5642      * @private
5643      * Method to manage awareness of when components are added to their
5644      * respective Container, firing an added event.
5645      * References are established at add time rather than at render time.
5646      * @param {Ext.container.Container} container Container which holds the component
5647      * @param {Number} pos Position at which the component was added
5648      */
5649     onAdded : function(container, pos) {
5650         this.ownerCt = container;
5651         this.fireEvent('added', this, container, pos);
5652     },
5653
5654     /**
5655      * @private
5656      * Method to manage awareness of when components are removed from their
5657      * respective Container, firing an removed event. References are properly
5658      * cleaned up after removing a component from its owning container.
5659      */
5660     onRemoved : function() {
5661         var me = this;
5662
5663         me.fireEvent('removed', me, me.ownerCt);
5664         delete me.ownerCt;
5665     },
5666
5667     // @private
5668     beforeDestroy : Ext.emptyFn,
5669     // @private
5670     // @private
5671     onResize : Ext.emptyFn,
5672
5673     /**
5674      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
5675      * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
5676      *
5677      * @param {Number/String/Object} width The new width to set. This may be one of:
5678      *
5679      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
5680      *   - A String used to set the CSS width style.
5681      *   - A size object in the format `{width: widthValue, height: heightValue}`.
5682      *   - `undefined` to leave the width unchanged.
5683      *
5684      * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
5685      * This may be one of:
5686      *
5687      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
5688      *   - A String used to set the CSS height style. Animation may **not** be used.
5689      *   - `undefined` to leave the height unchanged.
5690      *
5691      * @return {Ext.Component} this
5692      */
5693     setSize : function(width, height) {
5694         var me = this,
5695             layoutCollection;
5696
5697         // support for standard size objects
5698         if (Ext.isObject(width)) {
5699             height = width.height;
5700             width  = width.width;
5701         }
5702
5703         // Constrain within configured maxima
5704         if (Ext.isNumber(width)) {
5705             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
5706         }
5707         if (Ext.isNumber(height)) {
5708             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
5709         }
5710
5711         if (!me.rendered || !me.isVisible()) {
5712             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
5713             if (me.hiddenAncestor) {
5714                 layoutCollection = me.hiddenAncestor.layoutOnShow;
5715                 layoutCollection.remove(me);
5716                 layoutCollection.add(me);
5717             }
5718             me.needsLayout = {
5719                 width: width,
5720                 height: height,
5721                 isSetSize: true
5722             };
5723             if (!me.rendered) {
5724                 me.width  = (width !== undefined) ? width : me.width;
5725                 me.height = (height !== undefined) ? height : me.height;
5726             }
5727             return me;
5728         }
5729         me.doComponentLayout(width, height, true);
5730
5731         return me;
5732     },
5733
5734     isFixedWidth: function() {
5735         var me = this,
5736             layoutManagedWidth = me.layoutManagedWidth;
5737
5738         if (Ext.isDefined(me.width) || layoutManagedWidth == 1) {
5739             return true;
5740         }
5741         if (layoutManagedWidth == 2) {
5742             return false;
5743         }
5744         return (me.ownerCt && me.ownerCt.isFixedWidth());
5745     },
5746
5747     isFixedHeight: function() {
5748         var me = this,
5749             layoutManagedHeight = me.layoutManagedHeight;
5750
5751         if (Ext.isDefined(me.height) || layoutManagedHeight == 1) {
5752             return true;
5753         }
5754         if (layoutManagedHeight == 2) {
5755             return false;
5756         }
5757         return (me.ownerCt && me.ownerCt.isFixedHeight());
5758     },
5759
5760     setCalculatedSize : function(width, height, callingContainer) {
5761         var me = this,
5762             layoutCollection;
5763
5764         // support for standard size objects
5765         if (Ext.isObject(width)) {
5766             callingContainer = width.ownerCt;
5767             height = width.height;
5768             width  = width.width;
5769         }
5770
5771         // Constrain within configured maxima
5772         if (Ext.isNumber(width)) {
5773             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
5774         }
5775         if (Ext.isNumber(height)) {
5776             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
5777         }
5778
5779         if (!me.rendered || !me.isVisible()) {
5780             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
5781             if (me.hiddenAncestor) {
5782                 layoutCollection = me.hiddenAncestor.layoutOnShow;
5783                 layoutCollection.remove(me);
5784                 layoutCollection.add(me);
5785             }
5786             me.needsLayout = {
5787                 width: width,
5788                 height: height,
5789                 isSetSize: false,
5790                 ownerCt: callingContainer
5791             };
5792             return me;
5793         }
5794         me.doComponentLayout(width, height, false, callingContainer);
5795
5796         return me;
5797     },
5798
5799     /**
5800      * This method needs to be called whenever you change something on this component that requires the Component's
5801      * layout to be recalculated.
5802      * @param {Object} width
5803      * @param {Object} height
5804      * @param {Object} isSetSize
5805      * @param {Object} callingContainer
5806      * @return {Ext.container.Container} this
5807      */
5808     doComponentLayout : function(width, height, isSetSize, callingContainer) {
5809         var me = this,
5810             componentLayout = me.getComponentLayout(),
5811             lastComponentSize = componentLayout.lastComponentSize || {
5812                 width: undefined,
5813                 height: undefined
5814             };
5815
5816         // collapsed state is not relevant here, so no testing done.
5817         // Only Panels have a collapse method, and that just sets the width/height such that only
5818         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
5819         if (me.rendered && componentLayout) {
5820             // If no width passed, then only insert a value if the Component is NOT ALLOWED to autowidth itself.
5821             if (!Ext.isDefined(width)) {
5822                 if (me.isFixedWidth()) {
5823                     width = Ext.isDefined(me.width) ? me.width : lastComponentSize.width;
5824                 }
5825             }
5826             // If no height passed, then only insert a value if the Component is NOT ALLOWED to autoheight itself.
5827             if (!Ext.isDefined(height)) {
5828                 if (me.isFixedHeight()) {
5829                     height = Ext.isDefined(me.height) ? me.height : lastComponentSize.height;
5830                 }
5831             }
5832
5833             if (isSetSize) {
5834                 me.width = width;
5835                 me.height = height;
5836             }
5837
5838             componentLayout.layout(width, height, isSetSize, callingContainer);
5839         }
5840
5841         return me;
5842     },
5843
5844     /**
5845      * Forces this component to redo its componentLayout.
5846      */
5847     forceComponentLayout: function () {
5848         this.doComponentLayout();
5849     },
5850
5851     // @private
5852     setComponentLayout : function(layout) {
5853         var currentLayout = this.componentLayout;
5854         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
5855             currentLayout.setOwner(null);
5856         }
5857         this.componentLayout = layout;
5858         layout.setOwner(this);
5859     },
5860
5861     getComponentLayout : function() {
5862         var me = this;
5863
5864         if (!me.componentLayout || !me.componentLayout.isLayout) {
5865             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
5866         }
5867         return me.componentLayout;
5868     },
5869
5870     /**
5871      * Occurs after componentLayout is run.
5872      * @param {Number} adjWidth The box-adjusted width that was set
5873      * @param {Number} adjHeight The box-adjusted height that was set
5874      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
5875      * @param {Ext.Component} callingContainer Container requesting the layout. Only used when isSetSize is false.
5876      */
5877     afterComponentLayout: function(width, height, isSetSize, callingContainer) {
5878         var me = this,
5879             layout = me.componentLayout,
5880             oldSize = me.preLayoutSize;
5881
5882         ++me.componentLayoutCounter;
5883         if (!oldSize || ((width !== oldSize.width) || (height !== oldSize.height))) {
5884             me.fireEvent('resize', me, width, height);
5885         }
5886     },
5887
5888     /**
5889      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
5890      * being executed.
5891      * @param {Number} adjWidth The box-adjusted width that was set
5892      * @param {Number} adjHeight The box-adjusted height that was set
5893      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
5894      * @param {Ext.Component} callingContainer Container requesting sent the layout. Only used when isSetSize is false.
5895      */
5896     beforeComponentLayout: function(width, height, isSetSize, callingContainer) {
5897         this.preLayoutSize = this.componentLayout.lastComponentSize;
5898         return true;
5899     },
5900
5901     /**
5902      * Sets the left and top of the component. To set the page XY position instead, use
5903      * {@link Ext.Component#setPagePosition setPagePosition}. This method fires the {@link #move} event.
5904      * @param {Number} left The new left
5905      * @param {Number} top The new top
5906      * @return {Ext.Component} this
5907      */
5908     setPosition : function(x, y) {
5909         var me = this;
5910
5911         if (Ext.isObject(x)) {
5912             y = x.y;
5913             x = x.x;
5914         }
5915
5916         if (!me.rendered) {
5917             return me;
5918         }
5919
5920         if (x !== undefined || y !== undefined) {
5921             me.el.setBox(x, y);
5922             me.onPosition(x, y);
5923             me.fireEvent('move', me, x, y);
5924         }
5925         return me;
5926     },
5927
5928     /**
5929      * @private
5930      * Called after the component is moved, this method is empty by default but can be implemented by any
5931      * subclass that needs to perform custom logic after a move occurs.
5932      * @param {Number} x The new x position
5933      * @param {Number} y The new y position
5934      */
5935     onPosition: Ext.emptyFn,
5936
5937     /**
5938      * Sets the width of the component. This method fires the {@link #resize} event.
5939      *
5940      * @param {Number} width The new width to setThis may be one of:
5941      *
5942      *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
5943      *   - A String used to set the CSS width style.
5944      *
5945      * @return {Ext.Component} this
5946      */
5947     setWidth : function(width) {
5948         return this.setSize(width);
5949     },
5950
5951     /**
5952      * Sets the height of the component. This method fires the {@link #resize} event.
5953      *
5954      * @param {Number} height The new height to set. This may be one of:
5955      *
5956      *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
5957      *   - A String used to set the CSS height style.
5958      *   - _undefined_ to leave the height unchanged.
5959      *
5960      * @return {Ext.Component} this
5961      */
5962     setHeight : function(height) {
5963         return this.setSize(undefined, height);
5964     },
5965
5966     /**
5967      * Gets the current size of the component's underlying element.
5968      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
5969      */
5970     getSize : function() {
5971         return this.el.getSize();
5972     },
5973
5974     /**
5975      * Gets the current width of the component's underlying element.
5976      * @return {Number}
5977      */
5978     getWidth : function() {
5979         return this.el.getWidth();
5980     },
5981
5982     /**
5983      * Gets the current height of the component's underlying element.
5984      * @return {Number}
5985      */
5986     getHeight : function() {
5987         return this.el.getHeight();
5988     },
5989
5990     /**
5991      * Gets the {@link Ext.ComponentLoader} for this Component.
5992      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
5993      */
5994     getLoader: function(){
5995         var me = this,
5996             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
5997             loader = me.loader || autoLoad;
5998
5999         if (loader) {
6000             if (!loader.isLoader) {
6001                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
6002                     target: me,
6003                     autoLoad: autoLoad
6004                 }, loader));
6005             } else {
6006                 loader.setTarget(me);
6007             }
6008             return me.loader;
6009
6010         }
6011         return null;
6012     },
6013
6014     /**
6015      * This method allows you to show or hide a LoadMask on top of this component.
6016      *
6017      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
6018      * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
6019      * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
6020      * setting this to true on a Panel will cause only the body to be masked.
6021      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
6022      */
6023     setLoading : function(load, targetEl) {
6024         var me = this,
6025             config;
6026
6027         if (me.rendered) {
6028             if (load !== false && !me.collapsed) {
6029                 if (Ext.isObject(load)) {
6030                     config = load;
6031                 }
6032                 else if (Ext.isString(load)) {
6033                     config = {msg: load};
6034                 }
6035                 else {
6036                     config = {};
6037                 }
6038                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
6039                 me.loadMask.show();
6040             } else if (me.loadMask) {
6041                 Ext.destroy(me.loadMask);
6042                 me.loadMask = null;
6043             }
6044         }
6045
6046         return me.loadMask;
6047     },
6048
6049     /**
6050      * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
6051      * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
6052      * @param {Object} dock The dock position.
6053      * @param {Boolean} [layoutParent=false] True to re-layout parent.
6054      * @return {Ext.Component} this
6055      */
6056     setDocked : function(dock, layoutParent) {
6057         var me = this;
6058
6059         me.dock = dock;
6060         if (layoutParent && me.ownerCt && me.rendered) {
6061             me.ownerCt.doComponentLayout();
6062         }
6063         return me;
6064     },
6065
6066     onDestroy : function() {
6067         var me = this;
6068
6069         if (me.monitorResize && Ext.EventManager.resizeEvent) {
6070             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
6071         }
6072         // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
6073         Ext.destroy(
6074             me.componentLayout,
6075             me.loadMask,
6076             me.floatingItems
6077         );
6078     },
6079
6080     /**
6081      * Remove any references to elements added via renderSelectors/childEls
6082      * @private
6083      */
6084     cleanElementRefs: function(){
6085         var me = this,
6086             i = 0,
6087             childEls = me.childEls,
6088             selectors = me.renderSelectors,
6089             selector,
6090             name,
6091             len;
6092
6093         if (me.rendered) {
6094             if (childEls) {
6095                 for (len = childEls.length; i < len; ++i) {
6096                     name = childEls[i];
6097                     if (typeof(name) != 'string') {
6098                         name = name.name;
6099                     }
6100                     delete me[name];
6101                 }
6102             }
6103
6104             if (selectors) {
6105                 for (selector in selectors) {
6106                     if (selectors.hasOwnProperty(selector)) {
6107                         delete me[selector];
6108                     }
6109                 }
6110             }
6111         }
6112         delete me.rendered;
6113         delete me.el;
6114         delete me.frameBody;
6115     },
6116
6117     /**
6118      * Destroys the Component.
6119      */
6120     destroy : function() {
6121         var me = this;
6122
6123         if (!me.isDestroyed) {
6124             if (me.fireEvent('beforedestroy', me) !== false) {
6125                 me.destroying = true;
6126                 me.beforeDestroy();
6127
6128                 if (me.floating) {
6129                     delete me.floatParent;
6130                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
6131                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
6132                     if (me.zIndexManager) {
6133                         me.zIndexManager.unregister(me);
6134                     }
6135                 } else if (me.ownerCt && me.ownerCt.remove) {
6136                     me.ownerCt.remove(me, false);
6137                 }
6138
6139                 me.onDestroy();
6140
6141                 // Attempt to destroy all plugins
6142                 Ext.destroy(me.plugins);
6143
6144                 if (me.rendered) {
6145                     me.el.remove();
6146                 }
6147
6148                 me.fireEvent('destroy', me);
6149                 Ext.ComponentManager.unregister(me);
6150
6151                 me.mixins.state.destroy.call(me);
6152
6153                 me.clearListeners();
6154                 // make sure we clean up the element references after removing all events
6155                 me.cleanElementRefs();
6156                 me.destroying = false;
6157                 me.isDestroyed = true;
6158             }
6159         }
6160     },
6161
6162     /**
6163      * Retrieves a plugin by its pluginId which has been bound to this component.
6164      * @param {Object} pluginId
6165      * @return {Ext.AbstractPlugin} plugin instance.
6166      */
6167     getPlugin: function(pluginId) {
6168         var i = 0,
6169             plugins = this.plugins,
6170             ln = plugins.length;
6171         for (; i < ln; i++) {
6172             if (plugins[i].pluginId === pluginId) {
6173                 return plugins[i];
6174             }
6175         }
6176     },
6177
6178     /**
6179      * Determines whether this component is the descendant of a particular container.
6180      * @param {Ext.Container} container
6181      * @return {Boolean} True if it is.
6182      */
6183     isDescendantOf: function(container) {
6184         return !!this.findParentBy(function(p){
6185             return p === container;
6186         });
6187     }
6188 }, function() {
6189     this.createAlias({
6190         on: 'addListener',
6191         prev: 'previousSibling',
6192         next: 'nextSibling'
6193     });
6194 });
6195
6196 /**
6197  * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
6198  *
6199  * This class defines the essential API of plugins as used by Components by defining the following methods:
6200  *
6201  *   - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
6202  *
6203  *     The Component passes itself as the sole parameter.
6204  *
6205  *     Subclasses should set up bidirectional links between the plugin and its client Component here.
6206  *
6207  *   - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
6208  *
6209  *     Use this method to break links between the plugin and the Component and to free any allocated resources.
6210  *
6211  *   - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
6212  *
6213  *   - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
6214  */
6215 Ext.define('Ext.AbstractPlugin', {
6216     disabled: false,
6217
6218     constructor: function(config) {
6219         //<debug>
6220         if (!config.cmp && Ext.global.console) {
6221             Ext.global.console.warn("Attempted to attach a plugin ");
6222         }
6223         //</debug>
6224         Ext.apply(this, config);
6225     },
6226
6227     getCmp: function() {
6228         return this.cmp;
6229     },
6230
6231     /**
6232      * @method
6233      * The init method is invoked after initComponent method has been run for the client Component.
6234      *
6235      * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
6236      * links between the plugin and its client Component in their own implementation of this method.
6237      * @param {Ext.Component} client The client Component which owns this plugin.
6238      */
6239     init: Ext.emptyFn,
6240
6241     /**
6242      * @method
6243      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
6244      *
6245      * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
6246      * this method.
6247      */
6248     destroy: Ext.emptyFn,
6249
6250     /**
6251      * The base implementation just sets the plugin's `disabled` flag to `false`
6252      *
6253      * Plugin subclasses which need more complex processing may implement an overriding implementation.
6254      */
6255     enable: function() {
6256         this.disabled = false;
6257     },
6258
6259     /**
6260      * The base implementation just sets the plugin's `disabled` flag to `true`
6261      *
6262      * Plugin subclasses which need more complex processing may implement an overriding implementation.
6263      */
6264     disable: function() {
6265         this.disabled = true;
6266     }
6267 });
6268 /**
6269  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
6270  * to a configured URL, or to a URL specified at request time.
6271  *
6272  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
6273  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
6274  * in the request options object, or an {@link #requestcomplete event listener}.
6275  *
6276  * # File Uploads
6277  *
6278  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
6279  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
6280  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
6281  * after the return data has been gathered.
6282  *
6283  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
6284  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
6285  * insert the text unchanged into the document body.
6286  *
6287  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
6288  * `&amp;` etc.
6289  *
6290  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
6291  * responseText property in order to conform to the requirements of event handlers and callbacks.
6292  *
6293  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
6294  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
6295  * packet content.
6296  *
6297  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
6298  */
6299 Ext.define('Ext.data.Connection', {
6300     mixins: {
6301         observable: 'Ext.util.Observable'
6302     },
6303
6304     statics: {
6305         requestId: 0
6306     },
6307
6308     url: null,
6309     async: true,
6310     method: null,
6311     username: '',
6312     password: '',
6313
6314     /**
6315      * @cfg {Boolean} disableCaching
6316      * True to add a unique cache-buster param to GET requests.
6317      */
6318     disableCaching: true,
6319
6320     /**
6321      * @cfg {Boolean} withCredentials
6322      * True to set `withCredentials = true` on the XHR object
6323      */
6324     withCredentials: false,
6325
6326     /**
6327      * @cfg {Boolean} cors
6328      * True to enable CORS support on the XHR object. Currently the only effect of this option
6329      * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
6330      */
6331     cors: false,
6332
6333     /**
6334      * @cfg {String} disableCachingParam
6335      * Change the parameter which is sent went disabling caching through a cache buster.
6336      */
6337     disableCachingParam: '_dc',
6338
6339     /**
6340      * @cfg {Number} timeout
6341      * The timeout in milliseconds to be used for requests.
6342      */
6343     timeout : 30000,
6344
6345     /**
6346      * @cfg {Object} extraParams
6347      * Any parameters to be appended to the request.
6348      */
6349
6350     useDefaultHeader : true,
6351     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
6352     useDefaultXhrHeader : true,
6353     defaultXhrHeader : 'XMLHttpRequest',
6354
6355     constructor : function(config) {
6356         config = config || {};
6357         Ext.apply(this, config);
6358
6359         this.addEvents(
6360             /**
6361              * @event beforerequest
6362              * Fires before a network request is made to retrieve a data object.
6363              * @param {Ext.data.Connection} conn This Connection object.
6364              * @param {Object} options The options config object passed to the {@link #request} method.
6365              */
6366             'beforerequest',
6367             /**
6368              * @event requestcomplete
6369              * Fires if the request was successfully completed.
6370              * @param {Ext.data.Connection} conn This Connection object.
6371              * @param {Object} response The XHR object containing the response data.
6372              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
6373              * @param {Object} options The options config object passed to the {@link #request} method.
6374              */
6375             'requestcomplete',
6376             /**
6377              * @event requestexception
6378              * Fires if an error HTTP status was returned from the server.
6379              * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
6380              * for details of HTTP status codes.
6381              * @param {Ext.data.Connection} conn This Connection object.
6382              * @param {Object} response The XHR object containing the response data.
6383              * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
6384              * @param {Object} options The options config object passed to the {@link #request} method.
6385              */
6386             'requestexception'
6387         );
6388         this.requests = {};
6389         this.mixins.observable.constructor.call(this);
6390     },
6391
6392     /**
6393      * Sends an HTTP request to a remote server.
6394      *
6395      * **Important:** Ajax server requests are asynchronous, and this call will
6396      * return before the response has been received. Process any returned data
6397      * in a callback function.
6398      *
6399      *     Ext.Ajax.request({
6400      *         url: 'ajax_demo/sample.json',
6401      *         success: function(response, opts) {
6402      *             var obj = Ext.decode(response.responseText);
6403      *             console.dir(obj);
6404      *         },
6405      *         failure: function(response, opts) {
6406      *             console.log('server-side failure with status code ' + response.status);
6407      *         }
6408      *     });
6409      *
6410      * To execute a callback function in the correct scope, use the `scope` option.
6411      *
6412      * @param {Object} options An object which may contain the following properties:
6413      *
6414      * (The options object may also contain any other property which might be needed to perform
6415      * postprocessing in a callback because it is passed to callback functions.)
6416      *
6417      * @param {String/Function} options.url The URL to which to send the request, or a function
6418      * to call which returns a URL string. The scope of the function is specified by the `scope` option.
6419      * Defaults to the configured `url`.
6420      *
6421      * @param {Object/String/Function} options.params An object containing properties which are
6422      * used as parameters to the request, a url encoded string or a function to call to get either. The scope
6423      * of the function is specified by the `scope` option.
6424      *
6425      * @param {String} options.method The HTTP method to use
6426      * for the request. Defaults to the configured method, or if no method was configured,
6427      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
6428      * the method name is case-sensitive and should be all caps.
6429      *
6430      * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
6431      * The callback is called regardless of success or failure and is passed the following parameters:
6432      * @param {Object} options.callback.options The parameter to the request call.
6433      * @param {Boolean} options.callback.success True if the request succeeded.
6434      * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
6435      * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
6436      * accessing elements of the response.
6437      *
6438      * @param {Function} options.success The function to be called upon success of the request.
6439      * The callback is passed the following parameters:
6440      * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
6441      * @param {Object} options.success.options The parameter to the request call.
6442      *
6443      * @param {Function} options.failure The function to be called upon success of the request.
6444      * The callback is passed the following parameters:
6445      * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
6446      * @param {Object} options.failure.options The parameter to the request call.
6447      *
6448      * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
6449      * the callback function. If the `url`, or `params` options were specified as functions from which to
6450      * draw values, then this also serves as the scope for those function calls. Defaults to the browser
6451      * window.
6452      *
6453      * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
6454      * Defaults to 30 seconds.
6455      *
6456      * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
6457      * to pull parameters from.
6458      *
6459      * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
6460      *
6461      * True if the form object is a file upload (will be set automatically if the form was configured
6462      * with **`enctype`** `"multipart/form-data"`).
6463      *
6464      * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
6465      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
6466      * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
6467      * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
6468      * has been gathered.
6469      *
6470      * The server response is parsed by the browser to create the document for the IFRAME. If the
6471      * server is using JSON to send the return object, then the [Content-Type][] header must be set to
6472      * "text/html" in order to tell the browser to insert the text unchanged into the document body.
6473      *
6474      * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
6475      * containing a `responseText` property in order to conform to the requirements of event handlers
6476      * and callbacks.
6477      *
6478      * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
6479      * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
6480      * and parameter values from the packet content.
6481      *
6482      * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
6483      * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
6484      * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
6485      *
6486      * @param {Object} options.headers Request headers to set for the request.
6487      *
6488      * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
6489      * of params for the post data. Any params will be appended to the URL.
6490      *
6491      * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
6492      * instead of params for the post data. Any params will be appended to the URL.
6493      *
6494      * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
6495      *
6496      * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
6497      *
6498      * @return {Object} The request object. This may be used to cancel the request.
6499      */
6500     request : function(options) {
6501         options = options || {};
6502         var me = this,
6503             scope = options.scope || window,
6504             username = options.username || me.username,
6505             password = options.password || me.password || '',
6506             async,
6507             requestOptions,
6508             request,
6509             headers,
6510             xhr;
6511
6512         if (me.fireEvent('beforerequest', me, options) !== false) {
6513
6514             requestOptions = me.setOptions(options, scope);
6515
6516             if (this.isFormUpload(options) === true) {
6517                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
6518                 return null;
6519             }
6520
6521             // if autoabort is set, cancel the current transactions
6522             if (options.autoAbort === true || me.autoAbort) {
6523                 me.abort();
6524             }
6525
6526             // create a connection object
6527
6528             if ((options.cors === true || me.cors === true) && Ext.isIe && Ext.ieVersion >= 8) {
6529                 xhr = new XDomainRequest();
6530             } else {
6531                 xhr = this.getXhrInstance();
6532             }
6533
6534             async = options.async !== false ? (options.async || me.async) : false;
6535
6536             // open the request
6537             if (username) {
6538                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
6539             } else {
6540                 xhr.open(requestOptions.method, requestOptions.url, async);
6541             }
6542
6543             if (options.withCredentials === true || me.withCredentials === true) {
6544                 xhr.withCredentials = true;
6545             }
6546
6547             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
6548
6549             // create the transaction object
6550             request = {
6551                 id: ++Ext.data.Connection.requestId,
6552                 xhr: xhr,
6553                 headers: headers,
6554                 options: options,
6555                 async: async,
6556                 timeout: setTimeout(function() {
6557                     request.timedout = true;
6558                     me.abort(request);
6559                 }, options.timeout || me.timeout)
6560             };
6561             me.requests[request.id] = request;
6562             me.latestId = request.id;
6563             // bind our statechange listener
6564             if (async) {
6565                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
6566             }
6567
6568             // start the request!
6569             xhr.send(requestOptions.data);
6570             if (!async) {
6571                 return this.onComplete(request);
6572             }
6573             return request;
6574         } else {
6575             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
6576             return null;
6577         }
6578     },
6579
6580     /**
6581      * Uploads a form using a hidden iframe.
6582      * @param {String/HTMLElement/Ext.Element} form The form to upload
6583      * @param {String} url The url to post to
6584      * @param {String} params Any extra parameters to pass
6585      * @param {Object} options The initial options
6586      */
6587     upload: function(form, url, params, options) {
6588         form = Ext.getDom(form);
6589         options = options || {};
6590
6591         var id = Ext.id(),
6592                 frame = document.createElement('iframe'),
6593                 hiddens = [],
6594                 encoding = 'multipart/form-data',
6595                 buf = {
6596                     target: form.target,
6597                     method: form.method,
6598                     encoding: form.encoding,
6599                     enctype: form.enctype,
6600                     action: form.action
6601                 }, hiddenItem;
6602
6603         /*
6604          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
6605          * the frame had been added to the document. It seems this has since been corrected in
6606          * Opera so the behaviour has been reverted, the URL will be set before being added.
6607          */
6608         Ext.fly(frame).set({
6609             id: id,
6610             name: id,
6611             cls: Ext.baseCSSPrefix + 'hide-display',
6612             src: Ext.SSL_SECURE_URL
6613         });
6614
6615         document.body.appendChild(frame);
6616
6617         // This is required so that IE doesn't pop the response up in a new window.
6618         if (document.frames) {
6619            document.frames[id].name = id;
6620         }
6621
6622         Ext.fly(form).set({
6623             target: id,
6624             method: 'POST',
6625             enctype: encoding,
6626             encoding: encoding,
6627             action: url || buf.action
6628         });
6629
6630         // add dynamic params
6631         if (params) {
6632             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
6633                 hiddenItem = document.createElement('input');
6634                 Ext.fly(hiddenItem).set({
6635                     type: 'hidden',
6636                     value: value,
6637                     name: name
6638                 });
6639                 form.appendChild(hiddenItem);
6640                 hiddens.push(hiddenItem);
6641             });
6642         }
6643
6644         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
6645         form.submit();
6646
6647         Ext.fly(form).set(buf);
6648         Ext.each(hiddens, function(h) {
6649             Ext.removeNode(h);
6650         });
6651     },
6652
6653     /**
6654      * @private
6655      * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
6656      * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
6657      * (or a textarea inside the body). We then clean up by removing the iframe
6658      */
6659     onUploadComplete: function(frame, options) {
6660         var me = this,
6661             // bogus response object
6662             response = {
6663                 responseText: '',
6664                 responseXML: null
6665             }, doc, firstChild;
6666
6667         try {
6668             doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
6669             if (doc) {
6670                 if (doc.body) {
6671                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
6672                         response.responseText = firstChild.value;
6673                     } else {
6674                         response.responseText = doc.body.innerHTML;
6675                     }
6676                 }
6677                 //in IE the document may still have a body even if returns XML.
6678                 response.responseXML = doc.XMLDocument || doc;
6679             }
6680         } catch (e) {
6681         }
6682
6683         me.fireEvent('requestcomplete', me, response, options);
6684
6685         Ext.callback(options.success, options.scope, [response, options]);
6686         Ext.callback(options.callback, options.scope, [options, true, response]);
6687
6688         setTimeout(function(){
6689             Ext.removeNode(frame);
6690         }, 100);
6691     },
6692
6693     /**
6694      * Detects whether the form is intended to be used for an upload.
6695      * @private
6696      */
6697     isFormUpload: function(options){
6698         var form = this.getForm(options);
6699         if (form) {
6700             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
6701         }
6702         return false;
6703     },
6704
6705     /**
6706      * Gets the form object from options.
6707      * @private
6708      * @param {Object} options The request options
6709      * @return {HTMLElement} The form, null if not passed
6710      */
6711     getForm: function(options){
6712         return Ext.getDom(options.form) || null;
6713     },
6714
6715     /**
6716      * Sets various options such as the url, params for the request
6717      * @param {Object} options The initial options
6718      * @param {Object} scope The scope to execute in
6719      * @return {Object} The params for the request
6720      */
6721     setOptions: function(options, scope){
6722         var me =  this,
6723             params = options.params || {},
6724             extraParams = me.extraParams,
6725             urlParams = options.urlParams,
6726             url = options.url || me.url,
6727             jsonData = options.jsonData,
6728             method,
6729             disableCache,
6730             data;
6731
6732
6733         // allow params to be a method that returns the params object
6734         if (Ext.isFunction(params)) {
6735             params = params.call(scope, options);
6736         }
6737
6738         // allow url to be a method that returns the actual url
6739         if (Ext.isFunction(url)) {
6740             url = url.call(scope, options);
6741         }
6742
6743         url = this.setupUrl(options, url);
6744
6745         //<debug>
6746         if (!url) {
6747             Ext.Error.raise({
6748                 options: options,
6749                 msg: 'No URL specified'
6750             });
6751         }
6752         //</debug>
6753
6754         // check for xml or json data, and make sure json data is encoded
6755         data = options.rawData || options.xmlData || jsonData || null;
6756         if (jsonData && !Ext.isPrimitive(jsonData)) {
6757             data = Ext.encode(data);
6758         }
6759
6760         // make sure params are a url encoded string and include any extraParams if specified
6761         if (Ext.isObject(params)) {
6762             params = Ext.Object.toQueryString(params);
6763         }
6764
6765         if (Ext.isObject(extraParams)) {
6766             extraParams = Ext.Object.toQueryString(extraParams);
6767         }
6768
6769         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
6770
6771         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
6772
6773         params = this.setupParams(options, params);
6774
6775         // decide the proper method for this request
6776         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
6777         this.setupMethod(options, method);
6778
6779
6780         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
6781         // if the method is get append date to prevent caching
6782         if (method === 'GET' && disableCache) {
6783             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
6784         }
6785
6786         // if the method is get or there is json/xml data append the params to the url
6787         if ((method == 'GET' || data) && params) {
6788             url = Ext.urlAppend(url, params);
6789             params = null;
6790         }
6791
6792         // allow params to be forced into the url
6793         if (urlParams) {
6794             url = Ext.urlAppend(url, urlParams);
6795         }
6796
6797         return {
6798             url: url,
6799             method: method,
6800             data: data || params || null
6801         };
6802     },
6803
6804     /**
6805      * Template method for overriding url
6806      * @template
6807      * @private
6808      * @param {Object} options
6809      * @param {String} url
6810      * @return {String} The modified url
6811      */
6812     setupUrl: function(options, url){
6813         var form = this.getForm(options);
6814         if (form) {
6815             url = url || form.action;
6816         }
6817         return url;
6818     },
6819
6820
6821     /**
6822      * Template method for overriding params
6823      * @template
6824      * @private
6825      * @param {Object} options
6826      * @param {String} params
6827      * @return {String} The modified params
6828      */
6829     setupParams: function(options, params) {
6830         var form = this.getForm(options),
6831             serializedForm;
6832         if (form && !this.isFormUpload(options)) {
6833             serializedForm = Ext.Element.serializeForm(form);
6834             params = params ? (params + '&' + serializedForm) : serializedForm;
6835         }
6836         return params;
6837     },
6838
6839     /**
6840      * Template method for overriding method
6841      * @template
6842      * @private
6843      * @param {Object} options
6844      * @param {String} method
6845      * @return {String} The modified method
6846      */
6847     setupMethod: function(options, method){
6848         if (this.isFormUpload(options)) {
6849             return 'POST';
6850         }
6851         return method;
6852     },
6853
6854     /**
6855      * Setup all the headers for the request
6856      * @private
6857      * @param {Object} xhr The xhr object
6858      * @param {Object} options The options for the request
6859      * @param {Object} data The data for the request
6860      * @param {Object} params The params for the request
6861      */
6862     setupHeaders: function(xhr, options, data, params){
6863         var me = this,
6864             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
6865             contentType = me.defaultPostHeader,
6866             jsonData = options.jsonData,
6867             xmlData = options.xmlData,
6868             key,
6869             header;
6870
6871         if (!headers['Content-Type'] && (data || params)) {
6872             if (data) {
6873                 if (options.rawData) {
6874                     contentType = 'text/plain';
6875                 } else {
6876                     if (xmlData && Ext.isDefined(xmlData)) {
6877                         contentType = 'text/xml';
6878                     } else if (jsonData && Ext.isDefined(jsonData)) {
6879                         contentType = 'application/json';
6880                     }
6881                 }
6882             }
6883             headers['Content-Type'] = contentType;
6884         }
6885
6886         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
6887             headers['X-Requested-With'] = me.defaultXhrHeader;
6888         }
6889         // set up all the request headers on the xhr object
6890         try{
6891             for (key in headers) {
6892                 if (headers.hasOwnProperty(key)) {
6893                     header = headers[key];
6894                     xhr.setRequestHeader(key, header);
6895                 }
6896
6897             }
6898         } catch(e) {
6899             me.fireEvent('exception', key, header);
6900         }
6901         return headers;
6902     },
6903
6904     /**
6905      * Creates the appropriate XHR transport for the browser.
6906      * @private
6907      */
6908     getXhrInstance: (function(){
6909         var options = [function(){
6910             return new XMLHttpRequest();
6911         }, function(){
6912             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
6913         }, function(){
6914             return new ActiveXObject('MSXML2.XMLHTTP');
6915         }, function(){
6916             return new ActiveXObject('Microsoft.XMLHTTP');
6917         }], i = 0,
6918             len = options.length,
6919             xhr;
6920
6921         for(; i < len; ++i) {
6922             try{
6923                 xhr = options[i];
6924                 xhr();
6925                 break;
6926             }catch(e){}
6927         }
6928         return xhr;
6929     })(),
6930
6931     /**
6932      * Determines whether this object has a request outstanding.
6933      * @param {Object} [request] Defaults to the last transaction
6934      * @return {Boolean} True if there is an outstanding request.
6935      */
6936     isLoading : function(request) {
6937         if (!request) {
6938             request = this.getLatest();
6939         }
6940         if (!(request && request.xhr)) {
6941             return false;
6942         }
6943         // if there is a connection and readyState is not 0 or 4
6944         var state = request.xhr.readyState;
6945         return !(state === 0 || state == 4);
6946     },
6947
6948     /**
6949      * Aborts an active request.
6950      * @param {Object} [request] Defaults to the last request
6951      */
6952     abort : function(request) {
6953         var me = this;
6954         
6955         if (!request) {
6956             request = me.getLatest();
6957         }
6958
6959         if (request && me.isLoading(request)) {
6960             /*
6961              * Clear out the onreadystatechange here, this allows us
6962              * greater control, the browser may/may not fire the function
6963              * depending on a series of conditions.
6964              */
6965             request.xhr.onreadystatechange = null;
6966             request.xhr.abort();
6967             me.clearTimeout(request);
6968             if (!request.timedout) {
6969                 request.aborted = true;
6970             }
6971             me.onComplete(request);
6972             me.cleanup(request);
6973         }
6974     },
6975     
6976     /**
6977      * Aborts all active requests
6978      */
6979     abortAll: function(){
6980         var requests = this.requests,
6981             id;
6982         
6983         for (id in requests) {
6984             if (requests.hasOwnProperty(id)) {
6985                 this.abort(requests[id]);
6986             }
6987         }
6988     },
6989     
6990     /**
6991      * Gets the most recent request
6992      * @private
6993      * @return {Object} The request. Null if there is no recent request
6994      */
6995     getLatest: function(){
6996         var id = this.latestId,
6997             request;
6998             
6999         if (id) {
7000             request = this.requests[id];
7001         }
7002         return request || null;
7003     },
7004
7005     /**
7006      * Fires when the state of the xhr changes
7007      * @private
7008      * @param {Object} request The request
7009      */
7010     onStateChange : function(request) {
7011         if (request.xhr.readyState == 4) {
7012             this.clearTimeout(request);
7013             this.onComplete(request);
7014             this.cleanup(request);
7015         }
7016     },
7017
7018     /**
7019      * Clears the timeout on the request
7020      * @private
7021      * @param {Object} The request
7022      */
7023     clearTimeout: function(request){
7024         clearTimeout(request.timeout);
7025         delete request.timeout;
7026     },
7027
7028     /**
7029      * Cleans up any left over information from the request
7030      * @private
7031      * @param {Object} The request
7032      */
7033     cleanup: function(request){
7034         request.xhr = null;
7035         delete request.xhr;
7036     },
7037
7038     /**
7039      * To be called when the request has come back from the server
7040      * @private
7041      * @param {Object} request
7042      * @return {Object} The response
7043      */
7044     onComplete : function(request) {
7045         var me = this,
7046             options = request.options,
7047             result,
7048             success,
7049             response;
7050
7051         try {
7052             result = me.parseStatus(request.xhr.status);
7053         } catch (e) {
7054             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
7055             result = {
7056                 success : false,
7057                 isException : false
7058             };
7059         }
7060         success = result.success;
7061
7062         if (success) {
7063             response = me.createResponse(request);
7064             me.fireEvent('requestcomplete', me, response, options);
7065             Ext.callback(options.success, options.scope, [response, options]);
7066         } else {
7067             if (result.isException || request.aborted || request.timedout) {
7068                 response = me.createException(request);
7069             } else {
7070                 response = me.createResponse(request);
7071             }
7072             me.fireEvent('requestexception', me, response, options);
7073             Ext.callback(options.failure, options.scope, [response, options]);
7074         }
7075         Ext.callback(options.callback, options.scope, [options, success, response]);
7076         delete me.requests[request.id];
7077         return response;
7078     },
7079
7080     /**
7081      * Checks if the response status was successful
7082      * @param {Number} status The status code
7083      * @return {Object} An object containing success/status state
7084      */
7085     parseStatus: function(status) {
7086         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
7087         status = status == 1223 ? 204 : status;
7088
7089         var success = (status >= 200 && status < 300) || status == 304,
7090             isException = false;
7091
7092         if (!success) {
7093             switch (status) {
7094                 case 12002:
7095                 case 12029:
7096                 case 12030:
7097                 case 12031:
7098                 case 12152:
7099                 case 13030:
7100                     isException = true;
7101                     break;
7102             }
7103         }
7104         return {
7105             success: success,
7106             isException: isException
7107         };
7108     },
7109
7110     /**
7111      * Creates the response object
7112      * @private
7113      * @param {Object} request
7114      */
7115     createResponse : function(request) {
7116         var xhr = request.xhr,
7117             headers = {},
7118             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
7119             count = lines.length,
7120             line, index, key, value, response;
7121
7122         while (count--) {
7123             line = lines[count];
7124             index = line.indexOf(':');
7125             if(index >= 0) {
7126                 key = line.substr(0, index).toLowerCase();
7127                 if (line.charAt(index + 1) == ' ') {
7128                     ++index;
7129                 }
7130                 headers[key] = line.substr(index + 1);
7131             }
7132         }
7133
7134         request.xhr = null;
7135         delete request.xhr;
7136
7137         response = {
7138             request: request,
7139             requestId : request.id,
7140             status : xhr.status,
7141             statusText : xhr.statusText,
7142             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
7143             getAllResponseHeaders : function(){ return headers; },
7144             responseText : xhr.responseText,
7145             responseXML : xhr.responseXML
7146         };
7147
7148         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
7149         // functions created with getResponseHeader/getAllResponseHeaders
7150         xhr = null;
7151         return response;
7152     },
7153
7154     /**
7155      * Creates the exception object
7156      * @private
7157      * @param {Object} request
7158      */
7159     createException : function(request) {
7160         return {
7161             request : request,
7162             requestId : request.id,
7163             status : request.aborted ? -1 : 0,
7164             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
7165             aborted: request.aborted,
7166             timedout: request.timedout
7167         };
7168     }
7169 });
7170
7171 /**
7172  * @class Ext.Ajax
7173  * @singleton
7174  * @markdown
7175  * @extends Ext.data.Connection
7176
7177 A singleton instance of an {@link Ext.data.Connection}. This class
7178 is used to communicate with your server side code. It can be used as follows:
7179
7180     Ext.Ajax.request({
7181         url: 'page.php',
7182         params: {
7183             id: 1
7184         },
7185         success: function(response){
7186             var text = response.responseText;
7187             // process server response here
7188         }
7189     });
7190
7191 Default options for all requests can be set by changing a property on the Ext.Ajax class:
7192
7193     Ext.Ajax.timeout = 60000; // 60 seconds
7194
7195 Any options specified in the request method for the Ajax request will override any
7196 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
7197 request will be 60 seconds.
7198
7199     Ext.Ajax.timeout = 120000; // 120 seconds
7200     Ext.Ajax.request({
7201         url: 'page.aspx',
7202         timeout: 60000
7203     });
7204
7205 In general, this class will be used for all Ajax requests in your application.
7206 The main reason for creating a separate {@link Ext.data.Connection} is for a
7207 series of requests that share common settings that are different to all other
7208 requests in the application.
7209
7210  */
7211 Ext.define('Ext.Ajax', {
7212     extend: 'Ext.data.Connection',
7213     singleton: true,
7214
7215     /**
7216      * @cfg {String} url @hide
7217      */
7218     /**
7219      * @cfg {Object} extraParams @hide
7220      */
7221     /**
7222      * @cfg {Object} defaultHeaders @hide
7223      */
7224     /**
7225      * @cfg {String} method (Optional) @hide
7226      */
7227     /**
7228      * @cfg {Number} timeout (Optional) @hide
7229      */
7230     /**
7231      * @cfg {Boolean} autoAbort (Optional) @hide
7232      */
7233
7234     /**
7235      * @cfg {Boolean} disableCaching (Optional) @hide
7236      */
7237
7238     /**
7239      * @property {Boolean} disableCaching
7240      * True to add a unique cache-buster param to GET requests. Defaults to true.
7241      */
7242     /**
7243      * @property {String} url
7244      * The default URL to be used for requests to the server.
7245      * If the server receives all requests through one URL, setting this once is easier than
7246      * entering it on every request.
7247      */
7248     /**
7249      * @property {Object} extraParams
7250      * An object containing properties which are used as extra parameters to each request made
7251      * by this object. Session information and other data that you need
7252      * to pass with each request are commonly put here.
7253      */
7254     /**
7255      * @property {Object} defaultHeaders
7256      * An object containing request headers which are added to each request made by this object.
7257      */
7258     /**
7259      * @property {String} method
7260      * The default HTTP method to be used for requests. Note that this is case-sensitive and
7261      * should be all caps (if not set but params are present will use
7262      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
7263      */
7264     /**
7265      * @property {Number} timeout
7266      * The timeout in milliseconds to be used for requests. Defaults to 30000.
7267      */
7268
7269     /**
7270      * @property {Boolean} autoAbort
7271      * Whether a new request should abort any pending requests.
7272      */
7273     autoAbort : false
7274 });
7275 /**
7276  * A class used to load remote content to an Element. Sample usage:
7277  *
7278  *     Ext.get('el').load({
7279  *         url: 'myPage.php',
7280  *         scripts: true,
7281  *         params: {
7282  *             id: 1
7283  *         }
7284  *     });
7285  *
7286  * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
7287  * will be used.
7288  */
7289 Ext.define('Ext.ElementLoader', {
7290
7291     /* Begin Definitions */
7292
7293     mixins: {
7294         observable: 'Ext.util.Observable'
7295     },
7296
7297     uses: [
7298         'Ext.data.Connection',
7299         'Ext.Ajax'
7300     ],
7301
7302     statics: {
7303         Renderer: {
7304             Html: function(loader, response, active){
7305                 loader.getTarget().update(response.responseText, active.scripts === true);
7306                 return true;
7307             }
7308         }
7309     },
7310
7311     /* End Definitions */
7312
7313     /**
7314      * @cfg {String} url
7315      * The url to retrieve the content from.
7316      */
7317     url: null,
7318
7319     /**
7320      * @cfg {Object} params
7321      * Any params to be attached to the Ajax request. These parameters will
7322      * be overridden by any params in the load options.
7323      */
7324     params: null,
7325
7326     /**
7327      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
7328      * will not be overridden by any params in the load options.
7329      */
7330     baseParams: null,
7331
7332     /**
7333      * @cfg {Boolean/Object} autoLoad
7334      * True to have the loader make a request as soon as it is created.
7335      * This argument can also be a set of options that will be passed to {@link #load} is called.
7336      */
7337     autoLoad: false,
7338
7339     /**
7340      * @cfg {HTMLElement/Ext.Element/String} target
7341      * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
7342      */
7343     target: null,
7344
7345     /**
7346      * @cfg {Boolean/String} loadMask
7347      * True or a string to show when the element is loading.
7348      */
7349     loadMask: false,
7350
7351     /**
7352      * @cfg {Object} ajaxOptions
7353      * Any additional options to be passed to the request, for example timeout or headers.
7354      */
7355     ajaxOptions: null,
7356
7357     /**
7358      * @cfg {Boolean} scripts
7359      * True to parse any inline script tags in the response.
7360      */
7361     scripts: false,
7362
7363     /**
7364      * @cfg {Function} success
7365      * A function to be called when a load request is successful.
7366      * Will be called with the following config parameters:
7367      *
7368      * - this - The ElementLoader instance.
7369      * - response - The response object.
7370      * - options - Ajax options.
7371      */
7372
7373     /**
7374      * @cfg {Function} failure A function to be called when a load request fails.
7375      * Will be called with the following config parameters:
7376      *
7377      * - this - The ElementLoader instance.
7378      * - response - The response object.
7379      * - options - Ajax options.
7380      */
7381
7382     /**
7383      * @cfg {Function} callback A function to be called when a load request finishes.
7384      * Will be called with the following config parameters:
7385      *
7386      * - this - The ElementLoader instance.
7387      * - success - True if successful request.
7388      * - response - The response object.
7389      * - options - Ajax options.
7390      */
7391
7392     /**
7393      * @cfg {Object} scope
7394      * The scope to execute the {@link #success} and {@link #failure} functions in.
7395      */
7396
7397     /**
7398      * @cfg {Function} renderer
7399      * A custom function to render the content to the element. The passed parameters are:
7400      *
7401      * - The loader
7402      * - The response
7403      * - The active request
7404      */
7405
7406     isLoader: true,
7407
7408     constructor: function(config) {
7409         var me = this,
7410             autoLoad;
7411
7412         config = config || {};
7413         Ext.apply(me, config);
7414         me.setTarget(me.target);
7415         me.addEvents(
7416             /**
7417              * @event beforeload
7418              * Fires before a load request is made to the server.
7419              * Returning false from an event listener can prevent the load
7420              * from occurring.
7421              * @param {Ext.ElementLoader} this
7422              * @param {Object} options The options passed to the request
7423              */
7424             'beforeload',
7425
7426             /**
7427              * @event exception
7428              * Fires after an unsuccessful load.
7429              * @param {Ext.ElementLoader} this
7430              * @param {Object} response The response from the server
7431              * @param {Object} options The options passed to the request
7432              */
7433             'exception',
7434
7435             /**
7436              * @event load
7437              * Fires after a successful load.
7438              * @param {Ext.ElementLoader} this
7439              * @param {Object} response The response from the server
7440              * @param {Object} options The options passed to the request
7441              */
7442             'load'
7443         );
7444
7445         // don't pass config because we have already applied it.
7446         me.mixins.observable.constructor.call(me);
7447
7448         if (me.autoLoad) {
7449             autoLoad = me.autoLoad;
7450             if (autoLoad === true) {
7451                 autoLoad = {};
7452             }
7453             me.load(autoLoad);
7454         }
7455     },
7456
7457     /**
7458      * Sets an {@link Ext.Element} as the target of this loader.
7459      * Note that if the target is changed, any active requests will be aborted.
7460      * @param {String/HTMLElement/Ext.Element} target The element or its ID.
7461      */
7462     setTarget: function(target){
7463         var me = this;
7464         target = Ext.get(target);
7465         if (me.target && me.target != target) {
7466             me.abort();
7467         }
7468         me.target = target;
7469     },
7470
7471     /**
7472      * Returns the target of this loader.
7473      * @return {Ext.Component} The target or null if none exists.
7474      */
7475     getTarget: function(){
7476         return this.target || null;
7477     },
7478
7479     /**
7480      * Aborts the active load request
7481      */
7482     abort: function(){
7483         var active = this.active;
7484         if (active !== undefined) {
7485             Ext.Ajax.abort(active.request);
7486             if (active.mask) {
7487                 this.removeMask();
7488             }
7489             delete this.active;
7490         }
7491     },
7492
7493     /**
7494      * Removes the mask on the target
7495      * @private
7496      */
7497     removeMask: function(){
7498         this.target.unmask();
7499     },
7500
7501     /**
7502      * Adds the mask on the target
7503      * @private
7504      * @param {Boolean/Object} mask The mask configuration
7505      */
7506     addMask: function(mask){
7507         this.target.mask(mask === true ? null : mask);
7508     },
7509
7510     /**
7511      * Loads new data from the server.
7512      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
7513      * the class, with the exception of the target option. Note that any options passed to the method will override any
7514      * class defaults.
7515      */
7516     load: function(options) {
7517         //<debug>
7518         if (!this.target) {
7519             Ext.Error.raise('A valid target is required when loading content');
7520         }
7521         //</debug>
7522
7523         options = Ext.apply({}, options);
7524
7525         var me = this,
7526             target = me.target,
7527             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
7528             params = Ext.apply({}, options.params),
7529             ajaxOptions = Ext.apply({}, options.ajaxOptions),
7530             callback = options.callback || me.callback,
7531             scope = options.scope || me.scope || me,
7532             request;
7533
7534         Ext.applyIf(ajaxOptions, me.ajaxOptions);
7535         Ext.applyIf(options, ajaxOptions);
7536
7537         Ext.applyIf(params, me.params);
7538         Ext.apply(params, me.baseParams);
7539
7540         Ext.applyIf(options, {
7541             url: me.url
7542         });
7543
7544         //<debug>
7545         if (!options.url) {
7546             Ext.Error.raise('You must specify the URL from which content should be loaded');
7547         }
7548         //</debug>
7549
7550         Ext.apply(options, {
7551             scope: me,
7552             params: params,
7553             callback: me.onComplete
7554         });
7555
7556         if (me.fireEvent('beforeload', me, options) === false) {
7557             return;
7558         }
7559
7560         if (mask) {
7561             me.addMask(mask);
7562         }
7563
7564         request = Ext.Ajax.request(options);
7565         me.active = {
7566             request: request,
7567             options: options,
7568             mask: mask,
7569             scope: scope,
7570             callback: callback,
7571             success: options.success || me.success,
7572             failure: options.failure || me.failure,
7573             renderer: options.renderer || me.renderer,
7574             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
7575         };
7576         me.setOptions(me.active, options);
7577     },
7578
7579     /**
7580      * Sets any additional options on the active request
7581      * @private
7582      * @param {Object} active The active request
7583      * @param {Object} options The initial options
7584      */
7585     setOptions: Ext.emptyFn,
7586
7587     /**
7588      * Parses the response after the request completes
7589      * @private
7590      * @param {Object} options Ajax options
7591      * @param {Boolean} success Success status of the request
7592      * @param {Object} response The response object
7593      */
7594     onComplete: function(options, success, response) {
7595         var me = this,
7596             active = me.active,
7597             scope = active.scope,
7598             renderer = me.getRenderer(active.renderer);
7599
7600
7601         if (success) {
7602             success = renderer.call(me, me, response, active);
7603         }
7604
7605         if (success) {
7606             Ext.callback(active.success, scope, [me, response, options]);
7607             me.fireEvent('load', me, response, options);
7608         } else {
7609             Ext.callback(active.failure, scope, [me, response, options]);
7610             me.fireEvent('exception', me, response, options);
7611         }
7612         Ext.callback(active.callback, scope, [me, success, response, options]);
7613
7614         if (active.mask) {
7615             me.removeMask();
7616         }
7617
7618         delete me.active;
7619     },
7620
7621     /**
7622      * Gets the renderer to use
7623      * @private
7624      * @param {String/Function} renderer The renderer to use
7625      * @return {Function} A rendering function to use.
7626      */
7627     getRenderer: function(renderer){
7628         if (Ext.isFunction(renderer)) {
7629             return renderer;
7630         }
7631         return this.statics().Renderer.Html;
7632     },
7633
7634     /**
7635      * Automatically refreshes the content over a specified period.
7636      * @param {Number} interval The interval to refresh in ms.
7637      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
7638      */
7639     startAutoRefresh: function(interval, options){
7640         var me = this;
7641         me.stopAutoRefresh();
7642         me.autoRefresh = setInterval(function(){
7643             me.load(options);
7644         }, interval);
7645     },
7646
7647     /**
7648      * Clears any auto refresh. See {@link #startAutoRefresh}.
7649      */
7650     stopAutoRefresh: function(){
7651         clearInterval(this.autoRefresh);
7652         delete this.autoRefresh;
7653     },
7654
7655     /**
7656      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
7657      * @return {Boolean} True if the loader is automatically refreshing
7658      */
7659     isAutoRefreshing: function(){
7660         return Ext.isDefined(this.autoRefresh);
7661     },
7662
7663     /**
7664      * Destroys the loader. Any active requests will be aborted.
7665      */
7666     destroy: function(){
7667         var me = this;
7668         me.stopAutoRefresh();
7669         delete me.target;
7670         me.abort();
7671         me.clearListeners();
7672     }
7673 });
7674
7675 /**
7676  * @class Ext.ComponentLoader
7677  * @extends Ext.ElementLoader
7678  *
7679  * This class is used to load content via Ajax into a {@link Ext.Component}. In general
7680  * this class will not be instanced directly, rather a loader configuration will be passed to the
7681  * constructor of the {@link Ext.Component}.
7682  *
7683  * ## HTML Renderer
7684  * By default, the content loaded will be processed as raw html. The response text
7685  * from the request is taken and added to the component. This can be used in
7686  * conjunction with the {@link #scripts} option to execute any inline scripts in
7687  * the resulting content. Using this renderer has the same effect as passing the
7688  * {@link Ext.Component#html} configuration option.
7689  *
7690  * ## Data Renderer
7691  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
7692  * The content received from the response is passed to the {@link Ext.Component#update} method.
7693  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
7694  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
7695  * configuration in conjunction with a {@link Ext.Component#tpl}.
7696  *
7697  * ## Component Renderer
7698  * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
7699  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
7700  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
7701  * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
7702  * the {@link Ext.container.Container#items} configuration on a Container.
7703  *
7704  * ## Custom Renderer
7705  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
7706  *
7707  * ## Example Usage
7708  *     new Ext.Component({
7709  *         tpl: '{firstName} - {lastName}',
7710  *         loader: {
7711  *             url: 'myPage.php',
7712  *             renderer: 'data',
7713  *             params: {
7714  *                 userId: 1
7715  *             }
7716  *         }
7717  *     });
7718  */
7719 Ext.define('Ext.ComponentLoader', {
7720
7721     /* Begin Definitions */
7722
7723     extend: 'Ext.ElementLoader',
7724
7725     statics: {
7726         Renderer: {
7727             Data: function(loader, response, active){
7728                 var success = true;
7729                 try {
7730                     loader.getTarget().update(Ext.decode(response.responseText));
7731                 } catch (e) {
7732                     success = false;
7733                 }
7734                 return success;
7735             },
7736
7737             Component: function(loader, response, active){
7738                 var success = true,
7739                     target = loader.getTarget(),
7740                     items = [];
7741
7742                 //<debug>
7743                 if (!target.isContainer) {
7744                     Ext.Error.raise({
7745                         target: target,
7746                         msg: 'Components can only be loaded into a container'
7747                     });
7748                 }
7749                 //</debug>
7750
7751                 try {
7752                     items = Ext.decode(response.responseText);
7753                 } catch (e) {
7754                     success = false;
7755                 }
7756
7757                 if (success) {
7758                     if (active.removeAll) {
7759                         target.removeAll();
7760                     }
7761                     target.add(items);
7762                 }
7763                 return success;
7764             }
7765         }
7766     },
7767
7768     /* End Definitions */
7769
7770     /**
7771      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
7772      * If a string is passed it will be looked up via the id.
7773      */
7774     target: null,
7775
7776     /**
7777      * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
7778      */
7779     loadMask: false,
7780
7781     /**
7782      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
7783      * {@link #renderer}.
7784      */
7785
7786     /**
7787      * @cfg {String/Function} renderer
7788
7789 The type of content that is to be loaded into, which can be one of 3 types:
7790
7791 + **html** : Loads raw html content, see {@link Ext.Component#html}
7792 + **data** : Loads raw html content, see {@link Ext.Component#data}
7793 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
7794
7795 Alternatively, you can pass a function which is called with the following parameters.
7796
7797 + loader - Loader instance
7798 + response - The server response
7799 + active - The active request
7800
7801 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
7802
7803     new Ext.Component({
7804         loader: {
7805             url: 'myPage.php',
7806             renderer: function(loader, response, active) {
7807                 var text = response.responseText;
7808                 loader.getTarget().update('The response is ' + text);
7809                 return true;
7810             }
7811         }
7812     });
7813      */
7814     renderer: 'html',
7815
7816     /**
7817      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
7818      * any active requests will be aborted.
7819      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
7820      * it will be looked up via its id.
7821      */
7822     setTarget: function(target){
7823         var me = this;
7824
7825         if (Ext.isString(target)) {
7826             target = Ext.getCmp(target);
7827         }
7828
7829         if (me.target && me.target != target) {
7830             me.abort();
7831         }
7832         me.target = target;
7833     },
7834
7835     // inherit docs
7836     removeMask: function(){
7837         this.target.setLoading(false);
7838     },
7839
7840     /**
7841      * Add the mask on the target
7842      * @private
7843      * @param {Boolean/Object} mask The mask configuration
7844      */
7845     addMask: function(mask){
7846         this.target.setLoading(mask);
7847     },
7848
7849     /**
7850      * Get the target of this loader.
7851      * @return {Ext.Component} target The target, null if none exists.
7852      */
7853
7854     setOptions: function(active, options){
7855         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
7856     },
7857
7858     /**
7859      * Gets the renderer to use
7860      * @private
7861      * @param {String/Function} renderer The renderer to use
7862      * @return {Function} A rendering function to use.
7863      */
7864     getRenderer: function(renderer){
7865         if (Ext.isFunction(renderer)) {
7866             return renderer;
7867         }
7868
7869         var renderers = this.statics().Renderer;
7870         switch (renderer) {
7871             case 'component':
7872                 return renderers.Component;
7873             case 'data':
7874                 return renderers.Data;
7875             default:
7876                 return Ext.ElementLoader.Renderer.Html;
7877         }
7878     }
7879 });
7880
7881 /**
7882  * @author Ed Spencer
7883  *
7884  * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
7885  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
7886  * express like this:
7887  *
7888  *     Ext.define('User', {
7889  *         extend: 'Ext.data.Model',
7890  *         fields: ['id', 'name', 'email'],
7891  *
7892  *         hasMany: {model: 'Order', name: 'orders'}
7893  *     });
7894  *
7895  *     Ext.define('Order', {
7896  *         extend: 'Ext.data.Model',
7897  *         fields: ['id', 'user_id', 'status', 'price'],
7898  *
7899  *         belongsTo: 'User'
7900  *     });
7901  *
7902  * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
7903  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
7904  * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
7905  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
7906  *
7907  * **Further Reading**
7908  *
7909  *   - {@link Ext.data.HasManyAssociation hasMany associations}
7910  *   - {@link Ext.data.BelongsToAssociation belongsTo associations}
7911  *   - {@link Ext.data.Model using Models}
7912  *
7913  * # Self association models
7914  *
7915  * We can also have models that create parent/child associations between the same type. Below is an example, where
7916  * groups can be nested inside other groups:
7917  *
7918  *     // Server Data
7919  *     {
7920  *         "groups": {
7921  *             "id": 10,
7922  *             "parent_id": 100,
7923  *             "name": "Main Group",
7924  *             "parent_group": {
7925  *                 "id": 100,
7926  *                 "parent_id": null,
7927  *                 "name": "Parent Group"
7928  *             },
7929  *             "child_groups": [{
7930  *                 "id": 2,
7931  *                 "parent_id": 10,
7932  *                 "name": "Child Group 1"
7933  *             },{
7934  *                 "id": 3,
7935  *                 "parent_id": 10,
7936  *                 "name": "Child Group 2"
7937  *             },{
7938  *                 "id": 4,
7939  *                 "parent_id": 10,
7940  *                 "name": "Child Group 3"
7941  *             }]
7942  *         }
7943  *     }
7944  *
7945  *     // Client code
7946  *     Ext.define('Group', {
7947  *         extend: 'Ext.data.Model',
7948  *         fields: ['id', 'parent_id', 'name'],
7949  *         proxy: {
7950  *             type: 'ajax',
7951  *             url: 'data.json',
7952  *             reader: {
7953  *                 type: 'json',
7954  *                 root: 'groups'
7955  *             }
7956  *         },
7957  *         associations: [{
7958  *             type: 'hasMany',
7959  *             model: 'Group',
7960  *             primaryKey: 'id',
7961  *             foreignKey: 'parent_id',
7962  *             autoLoad: true,
7963  *             associationKey: 'child_groups' // read child data from child_groups
7964  *         }, {
7965  *             type: 'belongsTo',
7966  *             model: 'Group',
7967  *             primaryKey: 'id',
7968  *             foreignKey: 'parent_id',
7969  *             associationKey: 'parent_group' // read parent data from parent_group
7970  *         }]
7971  *     });
7972  *
7973  *     Ext.onReady(function(){
7974  *
7975  *         Group.load(10, {
7976  *             success: function(group){
7977  *                 console.log(group.getGroup().get('name'));
7978  *
7979  *                 group.groups().each(function(rec){
7980  *                     console.log(rec.get('name'));
7981  *                 });
7982  *             }
7983  *         });
7984  *
7985  *     });
7986  *
7987  */
7988 Ext.define('Ext.data.Association', {
7989     /**
7990      * @cfg {String} ownerModel (required)
7991      * The string name of the model that owns the association.
7992      */
7993
7994     /**
7995      * @cfg {String} associatedModel (required)
7996      * The string name of the model that is being associated with.
7997      */
7998
7999     /**
8000      * @cfg {String} primaryKey
8001      * The name of the primary key on the associated model. In general this will be the
8002      * {@link Ext.data.Model#idProperty} of the Model.
8003      */
8004     primaryKey: 'id',
8005
8006     /**
8007      * @cfg {Ext.data.reader.Reader} reader
8008      * A special reader to read associated data
8009      */
8010     
8011     /**
8012      * @cfg {String} associationKey
8013      * The name of the property in the data to read the association from. Defaults to the name of the associated model.
8014      */
8015
8016     defaultReaderType: 'json',
8017
8018     statics: {
8019         create: function(association){
8020             if (!association.isAssociation) {
8021                 if (Ext.isString(association)) {
8022                     association = {
8023                         type: association
8024                     };
8025                 }
8026
8027                 switch (association.type) {
8028                     case 'belongsTo':
8029                         return Ext.create('Ext.data.BelongsToAssociation', association);
8030                     case 'hasMany':
8031                         return Ext.create('Ext.data.HasManyAssociation', association);
8032                     //TODO Add this back when it's fixed
8033 //                    case 'polymorphic':
8034 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
8035                     default:
8036                         //<debug>
8037                         Ext.Error.raise('Unknown Association type: "' + association.type + '"');
8038                         //</debug>
8039                 }
8040             }
8041             return association;
8042         }
8043     },
8044
8045     /**
8046      * Creates the Association object.
8047      * @param {Object} [config] Config object.
8048      */
8049     constructor: function(config) {
8050         Ext.apply(this, config);
8051
8052         var types           = Ext.ModelManager.types,
8053             ownerName       = config.ownerModel,
8054             associatedName  = config.associatedModel,
8055             ownerModel      = types[ownerName],
8056             associatedModel = types[associatedName],
8057             ownerProto;
8058
8059         //<debug>
8060         if (ownerModel === undefined) {
8061             Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
8062         }
8063         if (associatedModel === undefined) {
8064             Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
8065         }
8066         //</debug>
8067
8068         this.ownerModel = ownerModel;
8069         this.associatedModel = associatedModel;
8070
8071         /**
8072          * @property {String} ownerName
8073          * The name of the model that 'owns' the association
8074          */
8075
8076         /**
8077          * @property {String} associatedName
8078          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
8079          * 'Order')
8080          */
8081
8082         Ext.applyIf(this, {
8083             ownerName : ownerName,
8084             associatedName: associatedName
8085         });
8086     },
8087
8088     /**
8089      * Get a specialized reader for reading associated data
8090      * @return {Ext.data.reader.Reader} The reader, null if not supplied
8091      */
8092     getReader: function(){
8093         var me = this,
8094             reader = me.reader,
8095             model = me.associatedModel;
8096
8097         if (reader) {
8098             if (Ext.isString(reader)) {
8099                 reader = {
8100                     type: reader
8101                 };
8102             }
8103             if (reader.isReader) {
8104                 reader.setModel(model);
8105             } else {
8106                 Ext.applyIf(reader, {
8107                     model: model,
8108                     type : me.defaultReaderType
8109                 });
8110             }
8111             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
8112         }
8113         return me.reader || null;
8114     }
8115 });
8116
8117 /**
8118  * @author Ed Spencer
8119  * @class Ext.ModelManager
8120  * @extends Ext.AbstractManager
8121
8122 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
8123
8124 __Creating Model Instances__
8125
8126 Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
8127 the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
8128 this by using the Model type directly. The following 3 snippets are equivalent:
8129
8130     Ext.define('User', {
8131         extend: 'Ext.data.Model',
8132         fields: ['first', 'last']
8133     });
8134
8135     // method 1, create using Ext.create (recommended)
8136     Ext.create('User', {
8137         first: 'Ed',
8138         last: 'Spencer'
8139     });
8140
8141     // method 2, create through the manager (deprecated)
8142     Ext.ModelManager.create({
8143         first: 'Ed',
8144         last: 'Spencer'
8145     }, 'User');
8146
8147     // method 3, create on the type directly
8148     new User({
8149         first: 'Ed',
8150         last: 'Spencer'
8151     });
8152
8153 __Accessing Model Types__
8154
8155 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
8156 are normal classes, you can access the type directly. The following snippets are equivalent:
8157
8158     Ext.define('User', {
8159         extend: 'Ext.data.Model',
8160         fields: ['first', 'last']
8161     });
8162
8163     // method 1, access model type through the manager
8164     var UserType = Ext.ModelManager.getModel('User');
8165
8166     // method 2, reference the type directly
8167     var UserType = User;
8168
8169  * @markdown
8170  * @singleton
8171  */
8172 Ext.define('Ext.ModelManager', {
8173     extend: 'Ext.AbstractManager',
8174     alternateClassName: 'Ext.ModelMgr',
8175     requires: ['Ext.data.Association'],
8176
8177     singleton: true,
8178
8179     typeName: 'mtype',
8180
8181     /**
8182      * Private stack of associations that must be created once their associated model has been defined
8183      * @property {Ext.data.Association[]} associationStack
8184      */
8185     associationStack: [],
8186
8187     /**
8188      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
8189      * immediately, as are any addition plugins defined in the model config.
8190      * @private
8191      */
8192     registerType: function(name, config) {
8193         var proto = config.prototype,
8194             model;
8195         if (proto && proto.isModel) {
8196             // registering an already defined model
8197             model = config;
8198         } else {
8199             // passing in a configuration
8200             if (!config.extend) {
8201                 config.extend = 'Ext.data.Model';
8202             }
8203             model = Ext.define(name, config);
8204         }
8205         this.types[name] = model;
8206         return model;
8207     },
8208
8209     /**
8210      * @private
8211      * Private callback called whenever a model has just been defined. This sets up any associations
8212      * that were waiting for the given model to be defined
8213      * @param {Function} model The model that was just created
8214      */
8215     onModelDefined: function(model) {
8216         var stack  = this.associationStack,
8217             length = stack.length,
8218             create = [],
8219             association, i, created;
8220
8221         for (i = 0; i < length; i++) {
8222             association = stack[i];
8223
8224             if (association.associatedModel == model.modelName) {
8225                 create.push(association);
8226             }
8227         }
8228
8229         for (i = 0, length = create.length; i < length; i++) {
8230             created = create[i];
8231             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
8232             Ext.Array.remove(stack, created);
8233         }
8234     },
8235
8236     /**
8237      * Registers an association where one of the models defined doesn't exist yet.
8238      * The ModelManager will check when new models are registered if it can link them
8239      * together
8240      * @private
8241      * @param {Ext.data.Association} association The association
8242      */
8243     registerDeferredAssociation: function(association){
8244         this.associationStack.push(association);
8245     },
8246
8247     /**
8248      * Returns the {@link Ext.data.Model} for a given model name
8249      * @param {String/Object} id The id of the model or the model instance.
8250      * @return {Ext.data.Model} a model class.
8251      */
8252     getModel: function(id) {
8253         var model = id;
8254         if (typeof model == 'string') {
8255             model = this.types[model];
8256         }
8257         return model;
8258     },
8259
8260     /**
8261      * Creates a new instance of a Model using the given data.
8262      *
8263      * This method is deprecated.  Use {@link Ext#create Ext.create} instead.  For example:
8264      *
8265      *     Ext.create('User', {
8266      *         first: 'Ed',
8267      *         last: 'Spencer'
8268      *     });
8269      *
8270      * @param {Object} data Data to initialize the Model's fields with
8271      * @param {String} name The name of the model to create
8272      * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
8273      */
8274     create: function(config, name, id) {
8275         var con = typeof name == 'function' ? name : this.types[name || config.name];
8276
8277         return new con(config, id);
8278     }
8279 }, function() {
8280
8281     /**
8282      * Old way for creating Model classes.  Instead use:
8283      *
8284      *     Ext.define("MyModel", {
8285      *         extend: "Ext.data.Model",
8286      *         fields: []
8287      *     });
8288      *
8289      * @param {String} name Name of the Model class.
8290      * @param {Object} config A configuration object for the Model you wish to create.
8291      * @return {Ext.data.Model} The newly registered Model
8292      * @member Ext
8293      * @deprecated 4.0.0 Use {@link Ext#define} instead.
8294      */
8295     Ext.regModel = function() {
8296         //<debug>
8297         if (Ext.isDefined(Ext.global.console)) {
8298             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: []});.');
8299         }
8300         //</debug>
8301         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
8302     };
8303 });
8304
8305 /**
8306  * @singleton
8307  *
8308  * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
8309  *
8310  * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
8311  *
8312  *     {
8313  *         ptype: 'gridviewdragdrop',
8314  *         dragText: 'Drag and drop to reorganize'
8315  *     }
8316  *
8317  * Or just use the ptype on its own:
8318  *
8319  *     'gridviewdragdrop'
8320  *
8321  * Alternatively you can instantiate the plugin with Ext.create:
8322  *
8323  *     Ext.create('Ext.view.plugin.AutoComplete', {
8324  *         ptype: 'gridviewdragdrop',
8325  *         dragText: 'Drag and drop to reorganize'
8326  *     })
8327  */
8328 Ext.define('Ext.PluginManager', {
8329     extend: 'Ext.AbstractManager',
8330     alternateClassName: 'Ext.PluginMgr',
8331     singleton: true,
8332     typeName: 'ptype',
8333
8334     /**
8335      * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
8336      * instantiate.
8337      * @param {Object} config A configuration object for the Plugin you wish to create.
8338      * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
8339      * contain a `ptype`. (Optional if the config contains a `ptype`).
8340      * @return {Ext.Component} The newly instantiated Plugin.
8341      */
8342     //create: function(plugin, defaultType) {
8343     //    if (plugin instanceof this) {
8344     //        return plugin;
8345     //    } else {
8346     //        var type, config = {};
8347     //
8348     //        if (Ext.isString(plugin)) {
8349     //            type = plugin;
8350     //        }
8351     //        else {
8352     //            type = plugin[this.typeName] || defaultType;
8353     //            config = plugin;
8354     //        }
8355     //
8356     //        return Ext.createByAlias('plugin.' + type, config);
8357     //    }
8358     //},
8359
8360     create : function(config, defaultType){
8361         if (config.init) {
8362             return config;
8363         } else {
8364             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
8365         }
8366
8367         // Prior system supported Singleton plugins.
8368         //var PluginCls = this.types[config.ptype || defaultType];
8369         //if (PluginCls.init) {
8370         //    return PluginCls;
8371         //} else {
8372         //    return new PluginCls(config);
8373         //}
8374     },
8375
8376     /**
8377      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
8378      * @param {String} type The type to search for
8379      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
8380      * truthy
8381      * @return {Ext.AbstractPlugin[]} All matching plugins
8382      */
8383     findByType: function(type, defaultsOnly) {
8384         var matches = [],
8385             types   = this.types;
8386
8387         for (var name in types) {
8388             if (!types.hasOwnProperty(name)) {
8389                 continue;
8390             }
8391             var item = types[name];
8392
8393             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
8394                 matches.push(item);
8395             }
8396         }
8397
8398         return matches;
8399     }
8400 }, function() {
8401     /**
8402      * Shorthand for {@link Ext.PluginManager#registerType}
8403      * @param {String} ptype The ptype mnemonic string by which the Plugin class
8404      * may be looked up.
8405      * @param {Function} cls The new Plugin class.
8406      * @member Ext
8407      * @method preg
8408      */
8409     Ext.preg = function() {
8410         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
8411     };
8412 });
8413
8414 /**
8415  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
8416  *
8417  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
8418  * arguments:
8419  *
8420  * # Single argument: String/Array
8421  *
8422  * The single argument may be either a String or an Array:
8423  *
8424  * - String:
8425  *
8426  *       var t = new Ext.Template("<div>Hello {0}.</div>");
8427  *       t.{@link #append}('some-element', ['foo']);
8428  *
8429  * - Array:
8430  *
8431  *   An Array will be combined with `join('')`.
8432  *
8433  *       var t = new Ext.Template([
8434  *           '<div name="{id}">',
8435  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
8436  *           '</div>',
8437  *       ]);
8438  *       t.{@link #compile}();
8439  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
8440  *
8441  * # Multiple arguments: String, Object, Array, ...
8442  *
8443  * Multiple arguments will be combined with `join('')`.
8444  *
8445  *     var t = new Ext.Template(
8446  *         '<div name="{id}">',
8447  *             '<span class="{cls}">{name} {value}</span>',
8448  *         '</div>',
8449  *         // a configuration object:
8450  *         {
8451  *             compiled: true,      // {@link #compile} immediately
8452  *         }
8453  *     );
8454  *
8455  * # Notes
8456  *
8457  * - For a list of available format functions, see {@link Ext.util.Format}.
8458  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
8459  */
8460 Ext.define('Ext.Template', {
8461
8462     /* Begin Definitions */
8463
8464     requires: ['Ext.DomHelper', 'Ext.util.Format'],
8465
8466     inheritableStatics: {
8467         /**
8468          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
8469          * @param {String/HTMLElement} el A DOM element or its id
8470          * @param {Object} config (optional) Config object
8471          * @return {Ext.Template} The created template
8472          * @static
8473          * @inheritable
8474          */
8475         from: function(el, config) {
8476             el = Ext.getDom(el);
8477             return new this(el.value || el.innerHTML, config || '');
8478         }
8479     },
8480
8481     /* End Definitions */
8482
8483     /**
8484      * Creates new template.
8485      * 
8486      * @param {String...} html List of strings to be concatenated into template.
8487      * Alternatively an array of strings can be given, but then no config object may be passed.
8488      * @param {Object} config (optional) Config object
8489      */
8490     constructor: function(html) {
8491         var me = this,
8492             args = arguments,
8493             buffer = [],
8494             i = 0,
8495             length = args.length,
8496             value;
8497
8498         me.initialConfig = {};
8499
8500         if (length > 1) {
8501             for (; i < length; i++) {
8502                 value = args[i];
8503                 if (typeof value == 'object') {
8504                     Ext.apply(me.initialConfig, value);
8505                     Ext.apply(me, value);
8506                 } else {
8507                     buffer.push(value);
8508                 }
8509             }
8510             html = buffer.join('');
8511         } else {
8512             if (Ext.isArray(html)) {
8513                 buffer.push(html.join(''));
8514             } else {
8515                 buffer.push(html);
8516             }
8517         }
8518
8519         // @private
8520         me.html = buffer.join('');
8521
8522         if (me.compiled) {
8523             me.compile();
8524         }
8525     },
8526
8527     isTemplate: true,
8528
8529     /**
8530      * @cfg {Boolean} compiled
8531      * True to immediately compile the template. Defaults to false.
8532      */
8533
8534     /**
8535      * @cfg {Boolean} disableFormats
8536      * True to disable format functions in the template. If the template doesn't contain
8537      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
8538      */
8539     disableFormats: false,
8540
8541     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
8542
8543     /**
8544      * Returns an HTML fragment of this template with the specified values applied.
8545      *
8546      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
8547      *
8548      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
8549      *     tpl.applyTemplate(['John', 25]);
8550      *
8551      * or an object:
8552      *
8553      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
8554      *     tpl.applyTemplate({name: 'John', age: 25});
8555      *
8556      * @return {String} The HTML fragment
8557      */
8558     applyTemplate: function(values) {
8559         var me = this,
8560             useFormat = me.disableFormats !== true,
8561             fm = Ext.util.Format,
8562             tpl = me;
8563
8564         if (me.compiled) {
8565             return me.compiled(values);
8566         }
8567         function fn(m, name, format, args) {
8568             if (format && useFormat) {
8569                 if (args) {
8570                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
8571                 } else {
8572                     args = [values[name]];
8573                 }
8574                 if (format.substr(0, 5) == "this.") {
8575                     return tpl[format.substr(5)].apply(tpl, args);
8576                 }
8577                 else {
8578                     return fm[format].apply(fm, args);
8579                 }
8580             }
8581             else {
8582                 return values[name] !== undefined ? values[name] : "";
8583             }
8584         }
8585         return me.html.replace(me.re, fn);
8586     },
8587
8588     /**
8589      * Sets the HTML used as the template and optionally compiles it.
8590      * @param {String} html
8591      * @param {Boolean} compile (optional) True to compile the template.
8592      * @return {Ext.Template} this
8593      */
8594     set: function(html, compile) {
8595         var me = this;
8596         me.html = html;
8597         me.compiled = null;
8598         return compile ? me.compile() : me;
8599     },
8600
8601     compileARe: /\\/g,
8602     compileBRe: /(\r\n|\n)/g,
8603     compileCRe: /'/g,
8604
8605     /**
8606      * Compiles the template into an internal function, eliminating the RegEx overhead.
8607      * @return {Ext.Template} this
8608      */
8609     compile: function() {
8610         var me = this,
8611             fm = Ext.util.Format,
8612             useFormat = me.disableFormats !== true,
8613             body, bodyReturn;
8614
8615         function fn(m, name, format, args) {
8616             if (format && useFormat) {
8617                 args = args ? ',' + args: "";
8618                 if (format.substr(0, 5) != "this.") {
8619                     format = "fm." + format + '(';
8620                 }
8621                 else {
8622                     format = 'this.' + format.substr(5) + '(';
8623                 }
8624             }
8625             else {
8626                 args = '';
8627                 format = "(values['" + name + "'] == undefined ? '' : ";
8628             }
8629             return "'," + format + "values['" + name + "']" + args + ") ,'";
8630         }
8631
8632         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
8633         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
8634         eval(body);
8635         return me;
8636     },
8637
8638     /**
8639      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
8640      *
8641      * @param {String/HTMLElement/Ext.Element} el The context element
8642      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
8643      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
8644      * @return {HTMLElement/Ext.Element} The new node or Element
8645      */
8646     insertFirst: function(el, values, returnElement) {
8647         return this.doInsert('afterBegin', el, values, returnElement);
8648     },
8649
8650     /**
8651      * Applies the supplied values to the template and inserts the new node(s) before el.
8652      *
8653      * @param {String/HTMLElement/Ext.Element} el The context element
8654      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
8655      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
8656      * @return {HTMLElement/Ext.Element} The new node or Element
8657      */
8658     insertBefore: function(el, values, returnElement) {
8659         return this.doInsert('beforeBegin', el, values, returnElement);
8660     },
8661
8662     /**
8663      * Applies the supplied values to the template and inserts the new node(s) after el.
8664      *
8665      * @param {String/HTMLElement/Ext.Element} el The context element
8666      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
8667      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
8668      * @return {HTMLElement/Ext.Element} The new node or Element
8669      */
8670     insertAfter: function(el, values, returnElement) {
8671         return this.doInsert('afterEnd', el, values, returnElement);
8672     },
8673
8674     /**
8675      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
8676      *
8677      * For example usage see {@link Ext.Template Ext.Template class docs}.
8678      *
8679      * @param {String/HTMLElement/Ext.Element} el The context element
8680      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
8681      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
8682      * @return {HTMLElement/Ext.Element} The new node or Element
8683      */
8684     append: function(el, values, returnElement) {
8685         return this.doInsert('beforeEnd', el, values, returnElement);
8686     },
8687
8688     doInsert: function(where, el, values, returnEl) {
8689         el = Ext.getDom(el);
8690         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
8691         return returnEl ? Ext.get(newNode, true) : newNode;
8692     },
8693
8694     /**
8695      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
8696      *
8697      * @param {String/HTMLElement/Ext.Element} el The context element
8698      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
8699      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
8700      * @return {HTMLElement/Ext.Element} The new node or Element
8701      */
8702     overwrite: function(el, values, returnElement) {
8703         el = Ext.getDom(el);
8704         el.innerHTML = this.applyTemplate(values);
8705         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
8706     }
8707 }, function() {
8708
8709     /**
8710      * @method apply
8711      * @member Ext.Template
8712      * Alias for {@link #applyTemplate}.
8713      * @alias Ext.Template#applyTemplate
8714      */
8715     this.createAlias('apply', 'applyTemplate');
8716 });
8717
8718 /**
8719  * A template class that supports advanced functionality like:
8720  *
8721  * - Autofilling arrays using templates and sub-templates
8722  * - Conditional processing with basic comparison operators
8723  * - Basic math function support
8724  * - Execute arbitrary inline code with special built-in template variables
8725  * - Custom member functions
8726  * - 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
8727  *
8728  * XTemplate provides the templating mechanism built into:
8729  *
8730  * - {@link Ext.view.View}
8731  *
8732  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
8733  * demonstrate all of the supported features.
8734  *
8735  * # Sample Data
8736  *
8737  * This is the data object used for reference in each code example:
8738  *
8739  *     var data = {
8740  *         name: 'Tommy Maintz',
8741  *         title: 'Lead Developer',
8742  *         company: 'Sencha Inc.',
8743  *         email: 'tommy@sencha.com',
8744  *         address: '5 Cups Drive',
8745  *         city: 'Palo Alto',
8746  *         state: 'CA',
8747  *         zip: '44102',
8748  *         drinks: ['Coffee', 'Soda', 'Water'],
8749  *         kids: [
8750  *             {
8751  *                 name: 'Joshua',
8752  *                 age:3
8753  *             },
8754  *             {
8755  *                 name: 'Matthew',
8756  *                 age:2
8757  *             },
8758  *             {
8759  *                 name: 'Solomon',
8760  *                 age:0
8761  *             }
8762  *         ]
8763  *     };
8764  *
8765  * # Auto filling of arrays
8766  *
8767  * The **tpl** tag and the **for** operator are used to process the provided data object:
8768  *
8769  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
8770  *   tag for each item in the array.
8771  * - If for="." is specified, the data object provided is examined.
8772  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
8773  *
8774  * Examples:
8775  *
8776  *     <tpl for=".">...</tpl>       // loop through array at root node
8777  *     <tpl for="foo">...</tpl>     // loop through array at foo node
8778  *     <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
8779  *
8780  * Using the sample data above:
8781  *
8782  *     var tpl = new Ext.XTemplate(
8783  *         '<p>Kids: ',
8784  *         '<tpl for=".">',       // process the data.kids node
8785  *             '<p>{#}. {name}</p>',  // use current array index to autonumber
8786  *         '</tpl></p>'
8787  *     );
8788  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
8789  *
8790  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
8791  * object to populate the template:
8792  *
8793  *     var tpl = new Ext.XTemplate(
8794  *         '<p>Name: {name}</p>',
8795  *         '<p>Title: {title}</p>',
8796  *         '<p>Company: {company}</p>',
8797  *         '<p>Kids: ',
8798  *         '<tpl for="kids">',     // interrogate the kids property within the data
8799  *             '<p>{name}</p>',
8800  *         '</tpl></p>'
8801  *     );
8802  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
8803  *
8804  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
8805  * loop. This variable will represent the value of the array at the current index:
8806  *
8807  *     var tpl = new Ext.XTemplate(
8808  *         '<p>{name}\'s favorite beverages:</p>',
8809  *         '<tpl for="drinks">',
8810  *             '<div> - {.}</div>',
8811  *         '</tpl>'
8812  *     );
8813  *     tpl.overwrite(panel.body, data);
8814  *
8815  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
8816  * members via the **parent** object:
8817  *
8818  *     var tpl = new Ext.XTemplate(
8819  *         '<p>Name: {name}</p>',
8820  *         '<p>Kids: ',
8821  *         '<tpl for="kids">',
8822  *             '<tpl if="age &gt; 1">',
8823  *                 '<p>{name}</p>',
8824  *                 '<p>Dad: {parent.name}</p>',
8825  *             '</tpl>',
8826  *         '</tpl></p>'
8827  *     );
8828  *     tpl.overwrite(panel.body, data);
8829  *
8830  * # Conditional processing with basic comparison operators
8831  *
8832  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
8833  * specific parts of the template. Notes:
8834  *
8835  * - Double quotes must be encoded if used within the conditional
8836  * - There is no else operator -- if needed, two opposite if statements should be used.
8837  *
8838  * Examples:
8839  *
8840  *     <tpl if="age > 1 && age < 10">Child</tpl>
8841  *     <tpl if="age >= 10 && age < 18">Teenager</tpl>
8842  *     <tpl if="this.isGirl(name)">...</tpl>
8843  *     <tpl if="id==\'download\'">...</tpl>
8844  *     <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
8845  *     // no good:
8846  *     <tpl if="name == "Tommy"">Hello</tpl>
8847  *     // encode " if it is part of the condition, e.g.
8848  *     <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
8849  *
8850  * Using the sample data above:
8851  *
8852  *     var tpl = new Ext.XTemplate(
8853  *         '<p>Name: {name}</p>',
8854  *         '<p>Kids: ',
8855  *         '<tpl for="kids">',
8856  *             '<tpl if="age &gt; 1">',
8857  *                 '<p>{name}</p>',
8858  *             '</tpl>',
8859  *         '</tpl></p>'
8860  *     );
8861  *     tpl.overwrite(panel.body, data);
8862  *
8863  * # Basic math support
8864  *
8865  * The following basic math operators may be applied directly on numeric data values:
8866  *
8867  *     + - * /
8868  *
8869  * For example:
8870  *
8871  *     var tpl = new Ext.XTemplate(
8872  *         '<p>Name: {name}</p>',
8873  *         '<p>Kids: ',
8874  *         '<tpl for="kids">',
8875  *             '<tpl if="age &gt; 1">',  // <-- Note that the > is encoded
8876  *                 '<p>{#}: {name}</p>',  // <-- Auto-number each item
8877  *                 '<p>In 5 Years: {age+5}</p>',  // <-- Basic math
8878  *                 '<p>Dad: {parent.name}</p>',
8879  *             '</tpl>',
8880  *         '</tpl></p>'
8881  *     );
8882  *     tpl.overwrite(panel.body, data);
8883  *
8884  * # Execute arbitrary inline code with special built-in template variables
8885  *
8886  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
8887  * variables available in that code:
8888  *
8889  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
8890  *   you can change what values is.
8891  * - **parent**: The scope (values) of the ancestor template.
8892  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
8893  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
8894  *
8895  * This example demonstrates basic row striping using an inline code block and the xindex variable:
8896  *
8897  *     var tpl = new Ext.XTemplate(
8898  *         '<p>Name: {name}</p>',
8899  *         '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
8900  *         '<p>Kids: ',
8901  *         '<tpl for="kids">',
8902  *             '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
8903  *             '{name}',
8904  *             '</div>',
8905  *         '</tpl></p>'
8906  *      );
8907  *     tpl.overwrite(panel.body, data);
8908  *
8909  * # Template member functions
8910  *
8911  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
8912  * more complex processing:
8913  *
8914  *     var tpl = new Ext.XTemplate(
8915  *         '<p>Name: {name}</p>',
8916  *         '<p>Kids: ',
8917  *         '<tpl for="kids">',
8918  *             '<tpl if="this.isGirl(name)">',
8919  *                 '<p>Girl: {name} - {age}</p>',
8920  *             '</tpl>',
8921  *              // use opposite if statement to simulate 'else' processing:
8922  *             '<tpl if="this.isGirl(name) == false">',
8923  *                 '<p>Boy: {name} - {age}</p>',
8924  *             '</tpl>',
8925  *             '<tpl if="this.isBaby(age)">',
8926  *                 '<p>{name} is a baby!</p>',
8927  *             '</tpl>',
8928  *         '</tpl></p>',
8929  *         {
8930  *             // XTemplate configuration:
8931  *             disableFormats: true,
8932  *             // member functions:
8933  *             isGirl: function(name){
8934  *                return name == 'Sara Grace';
8935  *             },
8936  *             isBaby: function(age){
8937  *                return age < 1;
8938  *             }
8939  *         }
8940  *     );
8941  *     tpl.overwrite(panel.body, data);
8942  */
8943 Ext.define('Ext.XTemplate', {
8944
8945     /* Begin Definitions */
8946
8947     extend: 'Ext.Template',
8948
8949     /* End Definitions */
8950
8951     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
8952     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
8953     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
8954     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
8955     constructor: function() {
8956         this.callParent(arguments);
8957
8958         var me = this,
8959             html = me.html,
8960             argsRe = me.argsRe,
8961             nameRe = me.nameRe,
8962             ifRe = me.ifRe,
8963             execRe = me.execRe,
8964             id = 0,
8965             tpls = [],
8966             VALUES = 'values',
8967             PARENT = 'parent',
8968             XINDEX = 'xindex',
8969             XCOUNT = 'xcount',
8970             RETURN = 'return ',
8971             WITHVALUES = 'with(values){ ',
8972             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
8973
8974         html = ['<tpl>', html, '</tpl>'].join('');
8975
8976         while ((m = html.match(argsRe))) {
8977             exp = null;
8978             fn = null;
8979             exec = null;
8980             matchName = m[0].match(nameRe);
8981             matchIf = m[0].match(ifRe);
8982             matchExec = m[0].match(execRe);
8983
8984             exp = matchIf ? matchIf[1] : null;
8985             if (exp) {
8986                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
8987             }
8988
8989             exp = matchExec ? matchExec[1] : null;
8990             if (exp) {
8991                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
8992             }
8993
8994             name = matchName ? matchName[1] : null;
8995             if (name) {
8996                 if (name === '.') {
8997                     name = VALUES;
8998                 } else if (name === '..') {
8999                     name = PARENT;
9000                 }
9001                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
9002             }
9003
9004             tpls.push({
9005                 id: id,
9006                 target: name,
9007                 exec: exec,
9008                 test: fn,
9009                 body: m[1] || ''
9010             });
9011
9012             html = html.replace(m[0], '{xtpl' + id + '}');
9013             id = id + 1;
9014         }
9015
9016         for (i = tpls.length - 1; i >= 0; --i) {
9017             me.compileTpl(tpls[i]);
9018         }
9019         me.master = tpls[tpls.length - 1];
9020         me.tpls = tpls;
9021     },
9022
9023     // @private
9024     applySubTemplate: function(id, values, parent, xindex, xcount) {
9025         var me = this, t = me.tpls[id];
9026         return t.compiled.call(me, values, parent, xindex, xcount);
9027     },
9028
9029     /**
9030      * @cfg {RegExp} codeRe
9031      * The regular expression used to match code variables. Default: matches {[expression]}.
9032      */
9033     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
9034
9035     /**
9036      * @cfg {Boolean} compiled
9037      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
9038      */
9039
9040     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
9041
9042     // @private
9043     compileTpl: function(tpl) {
9044         var fm = Ext.util.Format,
9045             me = this,
9046             useFormat = me.disableFormats !== true,
9047             body, bodyReturn, evaluatedFn;
9048
9049         function fn(m, name, format, args, math) {
9050             var v;
9051             // name is what is inside the {}
9052             // Name begins with xtpl, use a Sub Template
9053             if (name.substr(0, 4) == 'xtpl') {
9054                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
9055             }
9056             // name = "." - Just use the values object.
9057             if (name == '.') {
9058                 // filter to not include arrays/objects/nulls
9059                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
9060             }
9061
9062             // name = "#" - Use the xindex
9063             else if (name == '#') {
9064                 v = 'xindex';
9065             }
9066             else if (name.substr(0, 7) == "parent.") {
9067                 v = name;
9068             }
9069             // name has a . in it - Use object literal notation, starting from values
9070             else if (name.indexOf('.') != -1) {
9071                 v = "values." + name;
9072             }
9073
9074             // name is a property of values
9075             else {
9076                 v = "values['" + name + "']";
9077             }
9078             if (math) {
9079                 v = '(' + v + math + ')';
9080             }
9081             if (format && useFormat) {
9082                 args = args ? ',' + args : "";
9083                 if (format.substr(0, 5) != "this.") {
9084                     format = "fm." + format + '(';
9085                 }
9086                 else {
9087                     format = 'this.' + format.substr(5) + '(';
9088                 }
9089             }
9090             else {
9091                 args = '';
9092                 format = "(" + v + " === undefined ? '' : ";
9093             }
9094             return "'," + format + v + args + "),'";
9095         }
9096
9097         function codeFn(m, code) {
9098             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
9099             return "',(" + code.replace(me.compileARe, "'") + "),'";
9100         }
9101
9102         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
9103         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
9104         eval(body);
9105
9106         tpl.compiled = function(values, parent, xindex, xcount) {
9107             var vs,
9108                 length,
9109                 buffer,
9110                 i;
9111
9112             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
9113                 return '';
9114             }
9115
9116             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
9117             if (!vs) {
9118                return '';
9119             }
9120
9121             parent = tpl.target ? values : parent;
9122             if (tpl.target && Ext.isArray(vs)) {
9123                 buffer = [];
9124                 length = vs.length;
9125                 if (tpl.exec) {
9126                     for (i = 0; i < length; i++) {
9127                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
9128                         tpl.exec.call(me, vs[i], parent, i + 1, length);
9129                     }
9130                 } else {
9131                     for (i = 0; i < length; i++) {
9132                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
9133                     }
9134                 }
9135                 return buffer.join('');
9136             }
9137
9138             if (tpl.exec) {
9139                 tpl.exec.call(me, vs, parent, xindex, xcount);
9140             }
9141             return evaluatedFn.call(me, vs, parent, xindex, xcount);
9142         };
9143
9144         return this;
9145     },
9146
9147     // inherit docs from Ext.Template
9148     applyTemplate: function(values) {
9149         return this.master.compiled.call(this, values, {}, 1, 1);
9150     },
9151
9152     /**
9153      * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
9154      * @return {Ext.XTemplate} this
9155      */
9156     compile: function() {
9157         return this;
9158     }
9159 }, function() {
9160     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
9161     this.createAlias('apply', 'applyTemplate');
9162 });
9163
9164 /**
9165  * @class Ext.app.Controller
9166  *
9167  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
9168  * views) and take some action. Here's how we might create a Controller to manage Users:
9169  *
9170  *     Ext.define('MyApp.controller.Users', {
9171  *         extend: 'Ext.app.Controller',
9172  *
9173  *         init: function() {
9174  *             console.log('Initialized Users! This happens before the Application launch function is called');
9175  *         }
9176  *     });
9177  *
9178  * The init function is a special method that is called when your application boots. It is called before the
9179  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
9180  * your Viewport is created.
9181  *
9182  * The init function is a great place to set up how your controller interacts with the view, and is usually used in
9183  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
9184  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
9185  * our Users controller to tell us when the panel is rendered:
9186  *
9187  *     Ext.define('MyApp.controller.Users', {
9188  *         extend: 'Ext.app.Controller',
9189  *
9190  *         init: function() {
9191  *             this.control({
9192  *                 'viewport > panel': {
9193  *                     render: this.onPanelRendered
9194  *                 }
9195  *             });
9196  *         },
9197  *
9198  *         onPanelRendered: function() {
9199  *             console.log('The panel was rendered');
9200  *         }
9201  *     });
9202  *
9203  * We've updated the init function to use this.control to set up listeners on views in our application. The control
9204  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
9205  * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
9206  * it allows us to pass a CSS-like selector that will find every matching component on the page.
9207  *
9208  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
9209  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
9210  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
9211  * onPanelRendered function is called.
9212  *
9213  * <u>Using refs</u>
9214  *
9215  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
9216  * make it really easy to get references to Views on your page. Let's look at an example of this now:
9217  *
9218  *     Ext.define('MyApp.controller.Users', {
9219  *         extend: 'Ext.app.Controller',
9220  *
9221  *         refs: [
9222  *             {
9223  *                 ref: 'list',
9224  *                 selector: 'grid'
9225  *             }
9226  *         ],
9227  *
9228  *         init: function() {
9229  *             this.control({
9230  *                 'button': {
9231  *                     click: this.refreshGrid
9232  *                 }
9233  *             });
9234  *         },
9235  *
9236  *         refreshGrid: function() {
9237  *             this.getList().store.load();
9238  *         }
9239  *     });
9240  *
9241  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
9242  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
9243  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
9244  * assigns it to the reference 'list'.
9245  *
9246  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
9247  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
9248  * was capitalized and prepended with get to go from 'list' to 'getList'.
9249  *
9250  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
9251  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
9252  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
9253  * match a single View in your application (in the case above our selector will match any grid on the page).
9254  *
9255  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
9256  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
9257  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
9258  * simplicity). When the button is clicked we use out getList function to refresh the grid.
9259  *
9260  * You can create any number of refs and control any number of components this way, simply adding more functions to
9261  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
9262  * examples/app/feed-viewer folder in the SDK download.
9263  *
9264  * <u>Generated getter methods</u>
9265  *
9266  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
9267  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
9268  *
9269  *     Ext.define('MyApp.controller.Users', {
9270  *         extend: 'Ext.app.Controller',
9271  *
9272  *         models: ['User'],
9273  *         stores: ['AllUsers', 'AdminUsers'],
9274  *
9275  *         init: function() {
9276  *             var User = this.getUserModel(),
9277  *                 allUsers = this.getAllUsersStore();
9278  *
9279  *             var ed = new User({name: 'Ed'});
9280  *             allUsers.add(ed);
9281  *         }
9282  *     });
9283  *
9284  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
9285  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
9286  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
9287  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
9288  * functionality.
9289  *
9290  * <u>Further Reading</u>
9291  *
9292  * For more information about writing Ext JS 4 applications, please see the
9293  * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
9294  *
9295  * @docauthor Ed Spencer
9296  */
9297 Ext.define('Ext.app.Controller', {
9298
9299     mixins: {
9300         observable: 'Ext.util.Observable'
9301     },
9302
9303     /**
9304      * @cfg {String} id The id of this controller. You can use this id when dispatching.
9305      */
9306     
9307     /**
9308      * @cfg {String[]} models
9309      * Array of models to require from AppName.model namespace. For example:
9310      * 
9311      *     Ext.define("MyApp.controller.Foo", {
9312      *         extend: "Ext.app.Controller",
9313      *         models: ['User', 'Vehicle']
9314      *     });
9315      * 
9316      * This is equivalent of:
9317      * 
9318      *     Ext.define("MyApp.controller.Foo", {
9319      *         extend: "Ext.app.Controller",
9320      *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
9321      *     });
9322      * 
9323      */
9324
9325     /**
9326      * @cfg {String[]} views
9327      * Array of views to require from AppName.view namespace. For example:
9328      * 
9329      *     Ext.define("MyApp.controller.Foo", {
9330      *         extend: "Ext.app.Controller",
9331      *         views: ['List', 'Detail']
9332      *     });
9333      * 
9334      * This is equivalent of:
9335      * 
9336      *     Ext.define("MyApp.controller.Foo", {
9337      *         extend: "Ext.app.Controller",
9338      *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
9339      *     });
9340      * 
9341      */
9342
9343     /**
9344      * @cfg {String[]} stores
9345      * Array of stores to require from AppName.store namespace. For example:
9346      * 
9347      *     Ext.define("MyApp.controller.Foo", {
9348      *         extend: "Ext.app.Controller",
9349      *         stores: ['Users', 'Vehicles']
9350      *     });
9351      * 
9352      * This is equivalent of:
9353      * 
9354      *     Ext.define("MyApp.controller.Foo", {
9355      *         extend: "Ext.app.Controller",
9356      *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
9357      *     });
9358      * 
9359      */
9360
9361     onClassExtended: function(cls, data) {
9362         var className = Ext.getClassName(cls),
9363             match = className.match(/^(.*)\.controller\./);
9364
9365         if (match !== null) {
9366             var namespace = Ext.Loader.getPrefix(className) || match[1],
9367                 onBeforeClassCreated = data.onBeforeClassCreated,
9368                 requires = [],
9369                 modules = ['model', 'view', 'store'],
9370                 prefix;
9371
9372             data.onBeforeClassCreated = function(cls, data) {
9373                 var i, ln, module,
9374                     items, j, subLn, item;
9375
9376                 for (i = 0,ln = modules.length; i < ln; i++) {
9377                     module = modules[i];
9378
9379                     items = Ext.Array.from(data[module + 's']);
9380
9381                     for (j = 0,subLn = items.length; j < subLn; j++) {
9382                         item = items[j];
9383
9384                         prefix = Ext.Loader.getPrefix(item);
9385
9386                         if (prefix === '' || prefix === item) {
9387                             requires.push(namespace + '.' + module + '.' + item);
9388                         }
9389                         else {
9390                             requires.push(item);
9391                         }
9392                     }
9393                 }
9394
9395                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
9396             };
9397         }
9398     },
9399
9400     /**
9401      * Creates new Controller.
9402      * @param {Object} config (optional) Config object.
9403      */
9404     constructor: function(config) {
9405         this.mixins.observable.constructor.call(this, config);
9406
9407         Ext.apply(this, config || {});
9408
9409         this.createGetters('model', this.models);
9410         this.createGetters('store', this.stores);
9411         this.createGetters('view', this.views);
9412
9413         if (this.refs) {
9414             this.ref(this.refs);
9415         }
9416     },
9417
9418     /**
9419      * A template method that is called when your application boots. It is called before the
9420      * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
9421      * your Viewport is created.
9422      * 
9423      * @param {Ext.app.Application} application
9424      * @template
9425      */
9426     init: function(application) {},
9427
9428     /**
9429      * A template method like {@link #init}, but called after the viewport is created.
9430      * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
9431      * 
9432      * @param {Ext.app.Application} application
9433      * @template
9434      */
9435     onLaunch: function(application) {},
9436
9437     createGetters: function(type, refs) {
9438         type = Ext.String.capitalize(type);
9439         Ext.Array.each(refs, function(ref) {
9440             var fn = 'get',
9441                 parts = ref.split('.');
9442
9443             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
9444             Ext.Array.each(parts, function(part) {
9445                 fn += Ext.String.capitalize(part);
9446             });
9447             fn += type;
9448
9449             if (!this[fn]) {
9450                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
9451             }
9452             // Execute it right away
9453             this[fn](ref);
9454         },
9455         this);
9456     },
9457
9458     ref: function(refs) {
9459         var me = this;
9460         refs = Ext.Array.from(refs);
9461         Ext.Array.each(refs, function(info) {
9462             var ref = info.ref,
9463                 fn = 'get' + Ext.String.capitalize(ref);
9464             if (!me[fn]) {
9465                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
9466             }
9467         });
9468     },
9469
9470     getRef: function(ref, info, config) {
9471         this.refCache = this.refCache || {};
9472         info = info || {};
9473         config = config || {};
9474
9475         Ext.apply(info, config);
9476
9477         if (info.forceCreate) {
9478             return Ext.ComponentManager.create(info, 'component');
9479         }
9480
9481         var me = this,
9482             selector = info.selector,
9483             cached = me.refCache[ref];
9484
9485         if (!cached) {
9486             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
9487             if (!cached && info.autoCreate) {
9488                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
9489             }
9490             if (cached) {
9491                 cached.on('beforedestroy', function() {
9492                     me.refCache[ref] = null;
9493                 });
9494             }
9495         }
9496
9497         return cached;
9498     },
9499
9500     /**
9501      * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
9502      * object containing component paths mapped to a hash of listener functions.
9503      *
9504      * In the following example the `updateUser` function is mapped to to the `click`
9505      * event on a button component, which is a child of the `useredit` component.
9506      *
9507      *     Ext.define('AM.controller.Users', {
9508      *         init: function() {
9509      *             this.control({
9510      *                 'useredit button[action=save]': {
9511      *                     click: this.updateUser
9512      *                 }
9513      *             });
9514      *         },
9515      *
9516      *         updateUser: function(button) {
9517      *             console.log('clicked the Save button');
9518      *         }
9519      *     });
9520      *
9521      * See {@link Ext.ComponentQuery} for more information on component selectors.
9522      *
9523      * @param {String/Object} selectors If a String, the second argument is used as the
9524      * listeners, otherwise an object of selectors -> listeners is assumed
9525      * @param {Object} listeners
9526      */
9527     control: function(selectors, listeners) {
9528         this.application.control(selectors, listeners, this);
9529     },
9530
9531     /**
9532      * Returns instance of a {@link Ext.app.Controller controller} with the given name.
9533      * When controller doesn't exist yet, it's created.
9534      * @param {String} name
9535      * @return {Ext.app.Controller} a controller instance.
9536      */
9537     getController: function(name) {
9538         return this.application.getController(name);
9539     },
9540
9541     /**
9542      * Returns instance of a {@link Ext.data.Store Store} with the given name.
9543      * When store doesn't exist yet, it's created.
9544      * @param {String} name
9545      * @return {Ext.data.Store} a store instance.
9546      */
9547     getStore: function(name) {
9548         return this.application.getStore(name);
9549     },
9550
9551     /**
9552      * Returns a {@link Ext.data.Model Model} class with the given name.
9553      * A shorthand for using {@link Ext.ModelManager#getModel}.
9554      * @param {String} name
9555      * @return {Ext.data.Model} a model class.
9556      */
9557     getModel: function(model) {
9558         return this.application.getModel(model);
9559     },
9560
9561     /**
9562      * Returns a View class with the given name.  To create an instance of the view,
9563      * you can use it like it's used by Application to create the Viewport:
9564      * 
9565      *     this.getView('Viewport').create();
9566      * 
9567      * @param {String} name
9568      * @return {Ext.Base} a view class.
9569      */
9570     getView: function(view) {
9571         return this.application.getView(view);
9572     }
9573 });
9574
9575 /**
9576  * @author Don Griffin
9577  *
9578  * This class is a base for all id generators. It also provides lookup of id generators by
9579  * their id.
9580  * 
9581  * Generally, id generators are used to generate a primary key for new model instances. There
9582  * are different approaches to solving this problem, so this mechanism has both simple use
9583  * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
9584  * using the {@link Ext.data.Model#idgen} property.
9585  *
9586  * # Identity, Type and Shared IdGenerators
9587  *
9588  * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
9589  * This is done by giving IdGenerator instances an id property by which they can be looked
9590  * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
9591  * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
9592  * assign them the same id:
9593  *
9594  *     Ext.define('MyApp.data.MyModelA', {
9595  *         extend: 'Ext.data.Model',
9596  *         idgen: {
9597  *             type: 'sequential',
9598  *             id: 'foo'
9599  *         }
9600  *     });
9601  *
9602  *     Ext.define('MyApp.data.MyModelB', {
9603  *         extend: 'Ext.data.Model',
9604  *         idgen: {
9605  *             type: 'sequential',
9606  *             id: 'foo'
9607  *         }
9608  *     });
9609  *
9610  * To make this as simple as possible for generator types that are shared by many (or all)
9611  * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
9612  * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
9613  * to its type ('uuid'). In other words, the following Models share the same generator:
9614  *
9615  *     Ext.define('MyApp.data.MyModelX', {
9616  *         extend: 'Ext.data.Model',
9617  *         idgen: 'uuid'
9618  *     });
9619  *
9620  *     Ext.define('MyApp.data.MyModelY', {
9621  *         extend: 'Ext.data.Model',
9622  *         idgen: 'uuid'
9623  *     });
9624  *
9625  * This can be overridden (by specifying the id explicitly), but there is no particularly
9626  * good reason to do so for this generator type.
9627  *
9628  * # Creating Custom Generators
9629  * 
9630  * An id generator should derive from this class and implement the {@link #generate} method.
9631  * The constructor will apply config properties on new instances, so a constructor is often
9632  * not necessary.
9633  *
9634  * To register an id generator type, a derived class should provide an `alias` like so:
9635  *
9636  *     Ext.define('MyApp.data.CustomIdGenerator', {
9637  *         extend: 'Ext.data.IdGenerator',
9638  *         alias: 'idgen.custom',
9639  *
9640  *         configProp: 42, // some config property w/default value
9641  *
9642  *         generate: function () {
9643  *             return ... // a new id
9644  *         }
9645  *     });
9646  *
9647  * Using the custom id generator is then straightforward:
9648  *
9649  *     Ext.define('MyApp.data.MyModel', {
9650  *         extend: 'Ext.data.Model',
9651  *         idgen: 'custom'
9652  *     });
9653  *     // or...
9654  *
9655  *     Ext.define('MyApp.data.MyModel', {
9656  *         extend: 'Ext.data.Model',
9657  *         idgen: {
9658  *             type: 'custom',
9659  *             configProp: value
9660  *         }
9661  *     });
9662  *
9663  * It is not recommended to mix shared generators with generator configuration. This leads
9664  * to unpredictable results unless all configurations match (which is also redundant). In
9665  * such cases, a custom generator with a default id is the best approach.
9666  *
9667  *     Ext.define('MyApp.data.CustomIdGenerator', {
9668  *         extend: 'Ext.data.SequentialIdGenerator',
9669  *         alias: 'idgen.custom',
9670  *
9671  *         id: 'custom', // shared by default
9672  *
9673  *         prefix: 'ID_',
9674  *         seed: 1000
9675  *     });
9676  *
9677  *     Ext.define('MyApp.data.MyModelX', {
9678  *         extend: 'Ext.data.Model',
9679  *         idgen: 'custom'
9680  *     });
9681  *
9682  *     Ext.define('MyApp.data.MyModelY', {
9683  *         extend: 'Ext.data.Model',
9684  *         idgen: 'custom'
9685  *     });
9686  *
9687  *     // the above models share a generator that produces ID_1000, ID_1001, etc..
9688  *
9689  */
9690 Ext.define('Ext.data.IdGenerator', {
9691
9692     isGenerator: true,
9693
9694     /**
9695      * Initializes a new instance.
9696      * @param {Object} config (optional) Configuration object to be applied to the new instance.
9697      */
9698     constructor: function(config) {
9699         var me = this;
9700
9701         Ext.apply(me, config);
9702
9703         if (me.id) {
9704             Ext.data.IdGenerator.all[me.id] = me;
9705         }
9706     },
9707
9708     /**
9709      * @cfg {String} id
9710      * The id by which to register a new instance. This instance can be found using the
9711      * {@link Ext.data.IdGenerator#get} static method.
9712      */
9713
9714     getRecId: function (rec) {
9715         return rec.modelName + '-' + rec.internalId;
9716     },
9717
9718     /**
9719      * Generates and returns the next id. This method must be implemented by the derived
9720      * class.
9721      *
9722      * @return {String} The next id.
9723      * @method generate
9724      * @abstract
9725      */
9726
9727     statics: {
9728         /**
9729          * @property {Object} all
9730          * This object is keyed by id to lookup instances.
9731          * @private
9732          * @static
9733          */
9734         all: {},
9735
9736         /**
9737          * Returns the IdGenerator given its config description.
9738          * @param {String/Object} config If this parameter is an IdGenerator instance, it is
9739          * simply returned. If this is a string, it is first used as an id for lookup and
9740          * then, if there is no match, as a type to create a new instance. This parameter
9741          * can also be a config object that contains a `type` property (among others) that
9742          * are used to create and configure the instance.
9743          * @static
9744          */
9745         get: function (config) {
9746             var generator,
9747                 id,
9748                 type;
9749
9750             if (typeof config == 'string') {
9751                 id = type = config;
9752                 config = null;
9753             } else if (config.isGenerator) {
9754                 return config;
9755             } else {
9756                 id = config.id || config.type;
9757                 type = config.type;
9758             }
9759
9760             generator = this.all[id];
9761             if (!generator) {
9762                 generator = Ext.create('idgen.' + type, config);
9763             }
9764
9765             return generator;
9766         }
9767     }
9768 });
9769
9770 /**
9771  * @class Ext.data.SortTypes
9772  * This class defines a series of static methods that are used on a
9773  * {@link Ext.data.Field} for performing sorting. The methods cast the 
9774  * underlying values into a data type that is appropriate for sorting on
9775  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
9776  * the sortType will be set to a sane default if the sortType is not 
9777  * explicitly defined on the field. The sortType will make any necessary
9778  * modifications to the value and return it.
9779  * <ul>
9780  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
9781  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
9782  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
9783  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
9784  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
9785  * <li><b>asInt</b> - Converts the value to an integer number</li>
9786  * </ul>
9787  * <p>
9788  * It is also possible to create a custom sortType that can be used throughout
9789  * an application.
9790  * <pre><code>
9791 Ext.apply(Ext.data.SortTypes, {
9792     asPerson: function(person){
9793         // expects an object with a first and last name property
9794         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
9795     }    
9796 });
9797
9798 Ext.define('Employee', {
9799     extend: 'Ext.data.Model',
9800     fields: [{
9801         name: 'person',
9802         sortType: 'asPerson'
9803     }, {
9804         name: 'salary',
9805         type: 'float' // sortType set to asFloat
9806     }]
9807 });
9808  * </code></pre>
9809  * </p>
9810  * @singleton
9811  * @docauthor Evan Trimboli <evan@sencha.com>
9812  */
9813 Ext.define('Ext.data.SortTypes', {
9814     
9815     singleton: true,
9816     
9817     /**
9818      * Default sort that does nothing
9819      * @param {Object} s The value being converted
9820      * @return {Object} The comparison value
9821      */
9822     none : function(s) {
9823         return s;
9824     },
9825
9826     /**
9827      * The regular expression used to strip tags
9828      * @type {RegExp}
9829      * @property
9830      */
9831     stripTagsRE : /<\/?[^>]+>/gi,
9832
9833     /**
9834      * Strips all HTML tags to sort on text only
9835      * @param {Object} s The value being converted
9836      * @return {String} The comparison value
9837      */
9838     asText : function(s) {
9839         return String(s).replace(this.stripTagsRE, "");
9840     },
9841
9842     /**
9843      * Strips all HTML tags to sort on text only - Case insensitive
9844      * @param {Object} s The value being converted
9845      * @return {String} The comparison value
9846      */
9847     asUCText : function(s) {
9848         return String(s).toUpperCase().replace(this.stripTagsRE, "");
9849     },
9850
9851     /**
9852      * Case insensitive string
9853      * @param {Object} s The value being converted
9854      * @return {String} The comparison value
9855      */
9856     asUCString : function(s) {
9857         return String(s).toUpperCase();
9858     },
9859
9860     /**
9861      * Date sorting
9862      * @param {Object} s The value being converted
9863      * @return {Number} The comparison value
9864      */
9865     asDate : function(s) {
9866         if(!s){
9867             return 0;
9868         }
9869         if(Ext.isDate(s)){
9870             return s.getTime();
9871         }
9872         return Date.parse(String(s));
9873     },
9874
9875     /**
9876      * Float sorting
9877      * @param {Object} s The value being converted
9878      * @return {Number} The comparison value
9879      */
9880     asFloat : function(s) {
9881         var val = parseFloat(String(s).replace(/,/g, ""));
9882         return isNaN(val) ? 0 : val;
9883     },
9884
9885     /**
9886      * Integer sorting
9887      * @param {Object} s The value being converted
9888      * @return {Number} The comparison value
9889      */
9890     asInt : function(s) {
9891         var val = parseInt(String(s).replace(/,/g, ""), 10);
9892         return isNaN(val) ? 0 : val;
9893     }
9894 });
9895 /**
9896  * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
9897  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
9898  * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
9899  * on their records. Example usage:
9900  *
9901  *     //set up a fictional MixedCollection containing a few people to filter on
9902  *     var allNames = new Ext.util.MixedCollection();
9903  *     allNames.addAll([
9904  *         {id: 1, name: 'Ed',    age: 25},
9905  *         {id: 2, name: 'Jamie', age: 37},
9906  *         {id: 3, name: 'Abe',   age: 32},
9907  *         {id: 4, name: 'Aaron', age: 26},
9908  *         {id: 5, name: 'David', age: 32}
9909  *     ]);
9910  *
9911  *     var ageFilter = new Ext.util.Filter({
9912  *         property: 'age',
9913  *         value   : 32
9914  *     });
9915  *
9916  *     var longNameFilter = new Ext.util.Filter({
9917  *         filterFn: function(item) {
9918  *             return item.name.length > 4;
9919  *         }
9920  *     });
9921  *
9922  *     //a new MixedCollection with the 3 names longer than 4 characters
9923  *     var longNames = allNames.filter(longNameFilter);
9924  *
9925  *     //a new MixedCollection with the 2 people of age 24:
9926  *     var youngFolk = allNames.filter(ageFilter);
9927  *
9928  */
9929 Ext.define('Ext.util.Filter', {
9930
9931     /* Begin Definitions */
9932
9933     /* End Definitions */
9934     /**
9935      * @cfg {String} property
9936      * The property to filter on. Required unless a {@link #filterFn} is passed
9937      */
9938     
9939     /**
9940      * @cfg {Function} filterFn
9941      * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
9942      * true to accept each item or false to reject it
9943      */
9944     
9945     /**
9946      * @cfg {Boolean} anyMatch
9947      * True to allow any match - no regex start/end line anchors will be added.
9948      */
9949     anyMatch: false,
9950     
9951     /**
9952      * @cfg {Boolean} exactMatch
9953      * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
9954      */
9955     exactMatch: false,
9956     
9957     /**
9958      * @cfg {Boolean} caseSensitive
9959      * True to make the regex case sensitive (adds 'i' switch to regex).
9960      */
9961     caseSensitive: false,
9962     
9963     /**
9964      * @cfg {String} root
9965      * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
9966      * make the filter pull the {@link #property} out of the data object of each item
9967      */
9968
9969     /**
9970      * Creates new Filter.
9971      * @param {Object} [config] Config object
9972      */
9973     constructor: function(config) {
9974         var me = this;
9975         Ext.apply(me, config);
9976         
9977         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
9978         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
9979         me.filter = me.filter || me.filterFn;
9980         
9981         if (me.filter === undefined) {
9982             if (me.property === undefined || me.value === undefined) {
9983                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
9984                 // Model has been updated to allow string ids
9985                 
9986                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
9987             } else {
9988                 me.filter = me.createFilterFn();
9989             }
9990             
9991             me.filterFn = me.filter;
9992         }
9993     },
9994     
9995     /**
9996      * @private
9997      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
9998      */
9999     createFilterFn: function() {
10000         var me       = this,
10001             matcher  = me.createValueMatcher(),
10002             property = me.property;
10003         
10004         return function(item) {
10005             var value = me.getRoot.call(me, item)[property];
10006             return matcher === null ? value === null : matcher.test(value);
10007         };
10008     },
10009     
10010     /**
10011      * @private
10012      * Returns the root property of the given item, based on the configured {@link #root} property
10013      * @param {Object} item The item
10014      * @return {Object} The root property of the object
10015      */
10016     getRoot: function(item) {
10017         var root = this.root;
10018         return root === undefined ? item : item[root];
10019     },
10020     
10021     /**
10022      * @private
10023      * Returns a regular expression based on the given value and matching options
10024      */
10025     createValueMatcher : function() {
10026         var me            = this,
10027             value         = me.value,
10028             anyMatch      = me.anyMatch,
10029             exactMatch    = me.exactMatch,
10030             caseSensitive = me.caseSensitive,
10031             escapeRe      = Ext.String.escapeRegex;
10032             
10033         if (value === null) {
10034             return value;
10035         }
10036         
10037         if (!value.exec) { // not a regex
10038             value = String(value);
10039
10040             if (anyMatch === true) {
10041                 value = escapeRe(value);
10042             } else {
10043                 value = '^' + escapeRe(value);
10044                 if (exactMatch === true) {
10045                     value += '$';
10046                 }
10047             }
10048             value = new RegExp(value, caseSensitive ? '' : 'i');
10049          }
10050          
10051          return value;
10052     }
10053 });
10054 /**
10055  * Represents a single sorter that can be applied to a Store. The sorter is used
10056  * to compare two values against each other for the purpose of ordering them. Ordering
10057  * is achieved by specifying either:
10058  *
10059  * - {@link #property A sorting property}
10060  * - {@link #sorterFn A sorting function}
10061  *
10062  * As a contrived example, we can specify a custom sorter that sorts by rank:
10063  *
10064  *     Ext.define('Person', {
10065  *         extend: 'Ext.data.Model',
10066  *         fields: ['name', 'rank']
10067  *     });
10068  *
10069  *     Ext.create('Ext.data.Store', {
10070  *         model: 'Person',
10071  *         proxy: 'memory',
10072  *         sorters: [{
10073  *             sorterFn: function(o1, o2){
10074  *                 var getRank = function(o){
10075  *                     var name = o.get('rank');
10076  *                     if (name === 'first') {
10077  *                         return 1;
10078  *                     } else if (name === 'second') {
10079  *                         return 2;
10080  *                     } else {
10081  *                         return 3;
10082  *                     }
10083  *                 },
10084  *                 rank1 = getRank(o1),
10085  *                 rank2 = getRank(o2);
10086  *
10087  *                 if (rank1 === rank2) {
10088  *                     return 0;
10089  *                 }
10090  *
10091  *                 return rank1 < rank2 ? -1 : 1;
10092  *             }
10093  *         }],
10094  *         data: [{
10095  *             name: 'Person1',
10096  *             rank: 'second'
10097  *         }, {
10098  *             name: 'Person2',
10099  *             rank: 'third'
10100  *         }, {
10101  *             name: 'Person3',
10102  *             rank: 'first'
10103  *         }]
10104  *     });
10105  */
10106 Ext.define('Ext.util.Sorter', {
10107
10108     /**
10109      * @cfg {String} property
10110      * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
10111      * directly and compared for sorting using the built in comparison operators.
10112      */
10113     
10114     /**
10115      * @cfg {Function} sorterFn
10116      * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
10117      * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
10118      * function should return:
10119      *
10120      *   - -1 if o1 is "less than" o2
10121      *   - 0 if o1 is "equal" to o2
10122      *   - 1 if o1 is "greater than" o2
10123      */
10124     
10125     /**
10126      * @cfg {String} root
10127      * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
10128      * make the filter pull the {@link #property} out of the data object of each item
10129      */
10130     
10131     /**
10132      * @cfg {Function} transform
10133      * A function that will be run on each value before it is compared in the sorter. The function will receive a single
10134      * argument, the value.
10135      */
10136     
10137     /**
10138      * @cfg {String} direction
10139      * The direction to sort by.
10140      */
10141     direction: "ASC",
10142     
10143     constructor: function(config) {
10144         var me = this;
10145         
10146         Ext.apply(me, config);
10147         
10148         //<debug>
10149         if (me.property === undefined && me.sorterFn === undefined) {
10150             Ext.Error.raise("A Sorter requires either a property or a sorter function");
10151         }
10152         //</debug>
10153         
10154         me.updateSortFunction();
10155     },
10156     
10157     /**
10158      * @private
10159      * Creates and returns a function which sorts an array by the given property and direction
10160      * @return {Function} A function which sorts by the property/direction combination provided
10161      */
10162     createSortFunction: function(sorterFn) {
10163         var me        = this,
10164             property  = me.property,
10165             direction = me.direction || "ASC",
10166             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
10167         
10168         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
10169         //-1 if object 2 is greater or 0 if they are equal
10170         return function(o1, o2) {
10171             return modifier * sorterFn.call(me, o1, o2);
10172         };
10173     },
10174     
10175     /**
10176      * @private
10177      * Basic default sorter function that just compares the defined property of each object
10178      */
10179     defaultSorterFn: function(o1, o2) {
10180         var me = this,
10181             transform = me.transform,
10182             v1 = me.getRoot(o1)[me.property],
10183             v2 = me.getRoot(o2)[me.property];
10184             
10185         if (transform) {
10186             v1 = transform(v1);
10187             v2 = transform(v2);
10188         }
10189
10190         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
10191     },
10192     
10193     /**
10194      * @private
10195      * Returns the root property of the given item, based on the configured {@link #root} property
10196      * @param {Object} item The item
10197      * @return {Object} The root property of the object
10198      */
10199     getRoot: function(item) {
10200         return this.root === undefined ? item : item[this.root];
10201     },
10202     
10203     /**
10204      * Set the sorting direction for this sorter.
10205      * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
10206      */
10207     setDirection: function(direction) {
10208         var me = this;
10209         me.direction = direction;
10210         me.updateSortFunction();
10211     },
10212     
10213     /**
10214      * Toggles the sorting direction for this sorter.
10215      */
10216     toggle: function() {
10217         var me = this;
10218         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
10219         me.updateSortFunction();
10220     },
10221     
10222     /**
10223      * Update the sort function for this sorter.
10224      * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
10225      * sorting function.
10226      */
10227     updateSortFunction: function(fn) {
10228         var me = this;
10229         fn = fn || me.sorterFn || me.defaultSorterFn;
10230         me.sort = me.createSortFunction(fn);
10231     }
10232 });
10233 /**
10234  * @author Ed Spencer
10235  *
10236  * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
10237  * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
10238  * Operation objects directly.
10239  *
10240  * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
10241  */
10242 Ext.define('Ext.data.Operation', {
10243     /**
10244      * @cfg {Boolean} synchronous
10245      * True if this Operation is to be executed synchronously. This property is inspected by a
10246      * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
10247      */
10248     synchronous: true,
10249
10250     /**
10251      * @cfg {String} action
10252      * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
10253      */
10254     action: undefined,
10255
10256     /**
10257      * @cfg {Ext.util.Filter[]} filters
10258      * Optional array of filter objects. Only applies to 'read' actions.
10259      */
10260     filters: undefined,
10261
10262     /**
10263      * @cfg {Ext.util.Sorter[]} sorters
10264      * Optional array of sorter objects. Only applies to 'read' actions.
10265      */
10266     sorters: undefined,
10267
10268     /**
10269      * @cfg {Ext.util.Grouper} group
10270      * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
10271      */
10272     group: undefined,
10273
10274     /**
10275      * @cfg {Number} start
10276      * The start index (offset), used in paging when running a 'read' action.
10277      */
10278     start: undefined,
10279
10280     /**
10281      * @cfg {Number} limit
10282      * The number of records to load. Used on 'read' actions when paging is being used.
10283      */
10284     limit: undefined,
10285
10286     /**
10287      * @cfg {Ext.data.Batch} batch
10288      * The batch that this Operation is a part of.
10289      */
10290     batch: undefined,
10291
10292     /**
10293      * @cfg {Function} callback
10294      * Function to execute when operation completed.  Will be called with the following parameters:
10295      *
10296      * - records : Array of Ext.data.Model objects.
10297      * - operation : The Ext.data.Operation itself.
10298      * - success : True when operation completed successfully.
10299      */
10300     callback: undefined,
10301
10302     /**
10303      * @cfg {Object} scope
10304      * Scope for the {@link #callback} function.
10305      */
10306     scope: undefined,
10307
10308     /**
10309      * @property {Boolean} started
10310      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
10311      * @private
10312      */
10313     started: false,
10314
10315     /**
10316      * @property {Boolean} running
10317      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
10318      * @private
10319      */
10320     running: false,
10321
10322     /**
10323      * @property {Boolean} complete
10324      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
10325      * @private
10326      */
10327     complete: false,
10328
10329     /**
10330      * @property {Boolean} success
10331      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
10332      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
10333      * {@link #wasSuccessful} to query success status.
10334      * @private
10335      */
10336     success: undefined,
10337
10338     /**
10339      * @property {Boolean} exception
10340      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
10341      * @private
10342      */
10343     exception: false,
10344
10345     /**
10346      * @property {String/Object} error
10347      * The error object passed when {@link #setException} was called. This could be any object or primitive.
10348      * @private
10349      */
10350     error: undefined,
10351
10352     /**
10353      * @property {RegExp} actionCommitRecordsRe
10354      * The RegExp used to categorize actions that require record commits.
10355      */
10356     actionCommitRecordsRe: /^(?:create|update)$/i,
10357
10358     /**
10359      * @property {RegExp} actionSkipSyncRe
10360      * The RegExp used to categorize actions that skip local record synchronization. This defaults
10361      * to match 'destroy'.
10362      */
10363     actionSkipSyncRe: /^destroy$/i,
10364
10365     /**
10366      * Creates new Operation object.
10367      * @param {Object} config (optional) Config object.
10368      */
10369     constructor: function(config) {
10370         Ext.apply(this, config || {});
10371     },
10372
10373     /**
10374      * This method is called to commit data to this instance's records given the records in
10375      * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
10376      * those records (for 'create' and 'update' actions).
10377      *
10378      * If this {@link #action} is 'destroy', any server records are ignored and the
10379      * {@link Ext.data.Model#commit} method is not called.
10380      *
10381      * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
10382      * the server.
10383      * @markdown
10384      */
10385     commitRecords: function (serverRecords) {
10386         var me = this,
10387             mc, index, clientRecords, serverRec, clientRec;
10388
10389         if (!me.actionSkipSyncRe.test(me.action)) {
10390             clientRecords = me.records;
10391
10392             if (clientRecords && clientRecords.length) {
10393                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
10394                 mc.addAll(clientRecords);
10395
10396                 for (index = serverRecords ? serverRecords.length : 0; index--; ) {
10397                     serverRec = serverRecords[index];
10398                     clientRec = mc.get(serverRec.getId());
10399
10400                     if (clientRec) {
10401                         clientRec.beginEdit();
10402                         clientRec.set(serverRec.data);
10403                         clientRec.endEdit(true);
10404                     }
10405                 }
10406
10407                 if (me.actionCommitRecordsRe.test(me.action)) {
10408                     for (index = clientRecords.length; index--; ) {
10409                         clientRecords[index].commit();
10410                     }
10411                 }
10412             }
10413         }
10414     },
10415
10416     /**
10417      * Marks the Operation as started.
10418      */
10419     setStarted: function() {
10420         this.started = true;
10421         this.running = true;
10422     },
10423
10424     /**
10425      * Marks the Operation as completed.
10426      */
10427     setCompleted: function() {
10428         this.complete = true;
10429         this.running  = false;
10430     },
10431
10432     /**
10433      * Marks the Operation as successful.
10434      */
10435     setSuccessful: function() {
10436         this.success = true;
10437     },
10438
10439     /**
10440      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
10441      * @param {String/Object} error (optional) error string/object
10442      */
10443     setException: function(error) {
10444         this.exception = true;
10445         this.success = false;
10446         this.running = false;
10447         this.error = error;
10448     },
10449
10450     /**
10451      * Returns true if this Operation encountered an exception (see also {@link #getError})
10452      * @return {Boolean} True if there was an exception
10453      */
10454     hasException: function() {
10455         return this.exception === true;
10456     },
10457
10458     /**
10459      * Returns the error string or object that was set using {@link #setException}
10460      * @return {String/Object} The error object
10461      */
10462     getError: function() {
10463         return this.error;
10464     },
10465
10466     /**
10467      * Returns an array of Ext.data.Model instances as set by the Proxy.
10468      * @return {Ext.data.Model[]} Any loaded Records
10469      */
10470     getRecords: function() {
10471         var resultSet = this.getResultSet();
10472
10473         return (resultSet === undefined ? this.records : resultSet.records);
10474     },
10475
10476     /**
10477      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
10478      * instances as well as meta data such as number of instances fetched, number available etc
10479      * @return {Ext.data.ResultSet} The ResultSet object
10480      */
10481     getResultSet: function() {
10482         return this.resultSet;
10483     },
10484
10485     /**
10486      * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
10487      * {@link #isRunning} to test if the Operation is currently running.
10488      * @return {Boolean} True if the Operation has started
10489      */
10490     isStarted: function() {
10491         return this.started === true;
10492     },
10493
10494     /**
10495      * Returns true if the Operation has been started but has not yet completed.
10496      * @return {Boolean} True if the Operation is currently running
10497      */
10498     isRunning: function() {
10499         return this.running === true;
10500     },
10501
10502     /**
10503      * Returns true if the Operation has been completed
10504      * @return {Boolean} True if the Operation is complete
10505      */
10506     isComplete: function() {
10507         return this.complete === true;
10508     },
10509
10510     /**
10511      * Returns true if the Operation has completed and was successful
10512      * @return {Boolean} True if successful
10513      */
10514     wasSuccessful: function() {
10515         return this.isComplete() && this.success === true;
10516     },
10517
10518     /**
10519      * @private
10520      * Associates this Operation with a Batch
10521      * @param {Ext.data.Batch} batch The batch
10522      */
10523     setBatch: function(batch) {
10524         this.batch = batch;
10525     },
10526
10527     /**
10528      * Checks whether this operation should cause writing to occur.
10529      * @return {Boolean} Whether the operation should cause a write to occur.
10530      */
10531     allowWrite: function() {
10532         return this.action != 'read';
10533     }
10534 });
10535 /**
10536  * @author Ed Spencer
10537  *
10538  * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
10539  * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
10540  */
10541 Ext.define('Ext.data.validations', {
10542     singleton: true,
10543     
10544     /**
10545      * @property {String} presenceMessage
10546      * The default error message used when a presence validation fails.
10547      */
10548     presenceMessage: 'must be present',
10549     
10550     /**
10551      * @property {String} lengthMessage
10552      * The default error message used when a length validation fails.
10553      */
10554     lengthMessage: 'is the wrong length',
10555     
10556     /**
10557      * @property {Boolean} formatMessage
10558      * The default error message used when a format validation fails.
10559      */
10560     formatMessage: 'is the wrong format',
10561     
10562     /**
10563      * @property {String} inclusionMessage
10564      * The default error message used when an inclusion validation fails.
10565      */
10566     inclusionMessage: 'is not included in the list of acceptable values',
10567     
10568     /**
10569      * @property {String} exclusionMessage
10570      * The default error message used when an exclusion validation fails.
10571      */
10572     exclusionMessage: 'is not an acceptable value',
10573     
10574     /**
10575      * @property {String} emailMessage
10576      * The default error message used when an email validation fails
10577      */
10578     emailMessage: 'is not a valid email address',
10579     
10580     /**
10581      * @property {RegExp} emailRe
10582      * The regular expression used to validate email addresses
10583      */
10584     emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
10585     
10586     /**
10587      * Validates that the given value is present.
10588      * For example:
10589      *
10590      *     validations: [{type: 'presence', field: 'age'}]
10591      *
10592      * @param {Object} config Config object
10593      * @param {Object} value The value to validate
10594      * @return {Boolean} True if validation passed
10595      */
10596     presence: function(config, value) {
10597         if (value === undefined) {
10598             value = config;
10599         }
10600         
10601         //we need an additional check for zero here because zero is an acceptable form of present data
10602         return !!value || value === 0;
10603     },
10604     
10605     /**
10606      * Returns true if the given value is between the configured min and max values.
10607      * For example:
10608      *
10609      *     validations: [{type: 'length', field: 'name', min: 2}]
10610      *
10611      * @param {Object} config Config object
10612      * @param {String} value The value to validate
10613      * @return {Boolean} True if the value passes validation
10614      */
10615     length: function(config, value) {
10616         if (value === undefined || value === null) {
10617             return false;
10618         }
10619         
10620         var length = value.length,
10621             min    = config.min,
10622             max    = config.max;
10623         
10624         if ((min && length < min) || (max && length > max)) {
10625             return false;
10626         } else {
10627             return true;
10628         }
10629     },
10630     
10631     /**
10632      * Validates that an email string is in the correct format
10633      * @param {Object} config Config object
10634      * @param {String} email The email address
10635      * @return {Boolean} True if the value passes validation
10636      */
10637     email: function(config, email) {
10638         return Ext.data.validations.emailRe.test(email);
10639     },
10640     
10641     /**
10642      * Returns true if the given value passes validation against the configured `matcher` regex.
10643      * For example:
10644      *
10645      *     validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
10646      *
10647      * @param {Object} config Config object
10648      * @param {String} value The value to validate
10649      * @return {Boolean} True if the value passes the format validation
10650      */
10651     format: function(config, value) {
10652         return !!(config.matcher && config.matcher.test(value));
10653     },
10654     
10655     /**
10656      * Validates that the given value is present in the configured `list`.
10657      * For example:
10658      *
10659      *     validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
10660      *
10661      * @param {Object} config Config object
10662      * @param {String} value The value to validate
10663      * @return {Boolean} True if the value is present in the list
10664      */
10665     inclusion: function(config, value) {
10666         return config.list && Ext.Array.indexOf(config.list,value) != -1;
10667     },
10668     
10669     /**
10670      * Validates that the given value is present in the configured `list`.
10671      * For example:
10672      *
10673      *     validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
10674      *
10675      * @param {Object} config Config object
10676      * @param {String} value The value to validate
10677      * @return {Boolean} True if the value is not present in the list
10678      */
10679     exclusion: function(config, value) {
10680         return config.list && Ext.Array.indexOf(config.list,value) == -1;
10681     }
10682 });
10683 /**
10684  * @author Ed Spencer
10685  *
10686  * Simple wrapper class that represents a set of records returned by a Proxy.
10687  */
10688 Ext.define('Ext.data.ResultSet', {
10689     /**
10690      * @cfg {Boolean} loaded
10691      * True if the records have already been loaded. This is only meaningful when dealing with
10692      * SQL-backed proxies.
10693      */
10694     loaded: true,
10695
10696     /**
10697      * @cfg {Number} count
10698      * The number of records in this ResultSet. Note that total may differ from this number.
10699      */
10700     count: 0,
10701
10702     /**
10703      * @cfg {Number} total
10704      * The total number of records reported by the data source. This ResultSet may form a subset of
10705      * those records (see {@link #count}).
10706      */
10707     total: 0,
10708
10709     /**
10710      * @cfg {Boolean} success
10711      * True if the ResultSet loaded successfully, false if any errors were encountered.
10712      */
10713     success: false,
10714
10715     /**
10716      * @cfg {Ext.data.Model[]} records (required)
10717      * The array of record instances.
10718      */
10719
10720     /**
10721      * Creates the resultSet
10722      * @param {Object} [config] Config object.
10723      */
10724     constructor: function(config) {
10725         Ext.apply(this, config);
10726
10727         /**
10728          * @property {Number} totalRecords
10729          * Copy of this.total.
10730          * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
10731          */
10732         this.totalRecords = this.total;
10733
10734         if (config.count === undefined) {
10735             this.count = this.records.length;
10736         }
10737     }
10738 });
10739 /**
10740  * @author Ed Spencer
10741  *
10742  * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
10743  * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
10744  * the Operations.
10745  *
10746  * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
10747  * the config options passed to the JsonWriter's constructor.
10748  *
10749  * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
10750  * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
10751  * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
10752  */
10753 Ext.define('Ext.data.writer.Writer', {
10754     alias: 'writer.base',
10755     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
10756     
10757     /**
10758      * @cfg {Boolean} writeAllFields
10759      * True to write all fields from the record to the server. If set to false it will only send the fields that were
10760      * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
10761      */
10762     writeAllFields: true,
10763     
10764     /**
10765      * @cfg {String} nameProperty
10766      * This property is used to read the key for each value that will be sent to the server. For example:
10767      *
10768      *     Ext.define('Person', {
10769      *         extend: 'Ext.data.Model',
10770      *         fields: [{
10771      *             name: 'first',
10772      *             mapping: 'firstName'
10773      *         }, {
10774      *             name: 'last',
10775      *             mapping: 'lastName'
10776      *         }, {
10777      *             name: 'age'
10778      *         }]
10779      *     });
10780      *     new Ext.data.writer.Writer({
10781      *         writeAllFields: true,
10782      *         nameProperty: 'mapping'
10783      *     });
10784      *
10785      *     // This will be sent to the server
10786      *     {
10787      *         firstName: 'first name value',
10788      *         lastName: 'last name value',
10789      *         age: 1
10790      *     }
10791      *
10792      * If the value is not present, the field name will always be used.
10793      */
10794     nameProperty: 'name',
10795
10796     /**
10797      * Creates new Writer.
10798      * @param {Object} [config] Config object.
10799      */
10800     constructor: function(config) {
10801         Ext.apply(this, config);
10802     },
10803
10804     /**
10805      * Prepares a Proxy's Ext.data.Request object
10806      * @param {Ext.data.Request} request The request object
10807      * @return {Ext.data.Request} The modified request object
10808      */
10809     write: function(request) {
10810         var operation = request.operation,
10811             records   = operation.records || [],
10812             len       = records.length,
10813             i         = 0,
10814             data      = [];
10815
10816         for (; i < len; i++) {
10817             data.push(this.getRecordData(records[i]));
10818         }
10819         return this.writeRecords(request, data);
10820     },
10821
10822     /**
10823      * Formats the data for each record before sending it to the server. This method should be overridden to format the
10824      * data in a way that differs from the default.
10825      * @param {Object} record The record that we are writing to the server.
10826      * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
10827      * the data property on the record.
10828      */
10829     getRecordData: function(record) {
10830         var isPhantom = record.phantom === true,
10831             writeAll = this.writeAllFields || isPhantom,
10832             nameProperty = this.nameProperty,
10833             fields = record.fields,
10834             data = {},
10835             changes,
10836             name,
10837             field,
10838             key;
10839         
10840         if (writeAll) {
10841             fields.each(function(field){
10842                 if (field.persist) {
10843                     name = field[nameProperty] || field.name;
10844                     data[name] = record.get(field.name);
10845                 }
10846             });
10847         } else {
10848             // Only write the changes
10849             changes = record.getChanges();
10850             for (key in changes) {
10851                 if (changes.hasOwnProperty(key)) {
10852                     field = fields.get(key);
10853                     name = field[nameProperty] || field.name;
10854                     data[name] = changes[key];
10855                 }
10856             }
10857             if (!isPhantom) {
10858                 // always include the id for non phantoms
10859                 data[record.idProperty] = record.getId();
10860             }
10861         }
10862         return data;
10863     }
10864 });
10865
10866 /**
10867  * A mixin to add floating capability to a Component.
10868  */
10869 Ext.define('Ext.util.Floating', {
10870
10871     uses: ['Ext.Layer', 'Ext.window.Window'],
10872
10873     /**
10874      * @cfg {Boolean} focusOnToFront
10875      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
10876      * it is {@link #toFront brought to the front}.
10877      */
10878     focusOnToFront: true,
10879
10880     /**
10881      * @cfg {String/Boolean} shadow
10882      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
10883      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
10884      * shadow.
10885      */
10886     shadow: 'sides',
10887
10888     constructor: function(config) {
10889         var me = this;
10890         
10891         me.floating = true;
10892         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
10893             hideMode: me.hideMode,
10894             hidden: me.hidden,
10895             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
10896             shadowOffset: me.shadowOffset,
10897             constrain: false,
10898             shim: me.shim === false ? false : undefined
10899         }), me.el);
10900     },
10901
10902     onFloatRender: function() {
10903         var me = this;
10904         me.zIndexParent = me.getZIndexParent();
10905         me.setFloatParent(me.ownerCt);
10906         delete me.ownerCt;
10907
10908         if (me.zIndexParent) {
10909             me.zIndexParent.registerFloatingItem(me);
10910         } else {
10911             Ext.WindowManager.register(me);
10912         }
10913     },
10914
10915     setFloatParent: function(floatParent) {
10916         var me = this;
10917
10918         // Remove listeners from previous floatParent
10919         if (me.floatParent) {
10920             me.mun(me.floatParent, {
10921                 hide: me.onFloatParentHide,
10922                 show: me.onFloatParentShow,
10923                 scope: me
10924             });
10925         }
10926
10927         me.floatParent = floatParent;
10928
10929         // Floating Components as children of Containers must hide when their parent hides.
10930         if (floatParent) {
10931             me.mon(me.floatParent, {
10932                 hide: me.onFloatParentHide,
10933                 show: me.onFloatParentShow,
10934                 scope: me
10935             });
10936         }
10937
10938         // If a floating Component is configured to be constrained, but has no configured
10939         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
10940         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
10941             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
10942         }
10943     },
10944
10945     onFloatParentHide: function() {
10946         var me = this;
10947         
10948         if (me.hideOnParentHide !== false) {
10949             me.showOnParentShow = me.isVisible();
10950             me.hide();
10951         }
10952     },
10953
10954     onFloatParentShow: function() {
10955         if (this.showOnParentShow) {
10956             delete this.showOnParentShow;
10957             this.show();
10958         }
10959     },
10960
10961     /**
10962      * @private
10963      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
10964      *
10965      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
10966      *
10967      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
10968      * and the global Ext.WindowManager will be used.
10969      */
10970     getZIndexParent: function() {
10971         var p = this.ownerCt,
10972             c;
10973
10974         if (p) {
10975             while (p) {
10976                 c = p;
10977                 p = p.ownerCt;
10978             }
10979             if (c.floating) {
10980                 return c;
10981             }
10982         }
10983     },
10984
10985     // private
10986     // z-index is managed by the zIndexManager and may be overwritten at any time.
10987     // Returns the next z-index to be used.
10988     // If this is a Container, then it will have rebased any managed floating Components,
10989     // and so the next available z-index will be approximately 10000 above that.
10990     setZIndex: function(index) {
10991         var me = this;
10992         me.el.setZIndex(index);
10993
10994         // Next item goes 10 above;
10995         index += 10;
10996
10997         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
10998         // The returned value is a round number approximately 10000 above the last z-index used.
10999         if (me.floatingItems) {
11000             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
11001         }
11002         return index;
11003     },
11004
11005     /**
11006      * Moves this floating Component into a constrain region.
11007      *
11008      * By default, this Component is constrained to be within the container it was added to, or the element it was
11009      * rendered to.
11010      *
11011      * An alternative constraint may be passed.
11012      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
11013      * to be constrained. Defaults to the element into which this floating Component was rendered.
11014      */
11015     doConstrain: function(constrainTo) {
11016         var me = this,
11017             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
11018             xy;
11019
11020         if (vector) {
11021             xy = me.getPosition();
11022             xy[0] += vector[0];
11023             xy[1] += vector[1];
11024             me.setPosition(xy);
11025         }
11026     },
11027
11028
11029     /**
11030      * Gets the x/y offsets to constrain this float
11031      * @private
11032      * @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.
11033      * @return {Number[]} The x/y constraints
11034      */
11035     getConstrainVector: function(constrainTo){
11036         var me = this,
11037             el;
11038
11039         if (me.constrain || me.constrainHeader) {
11040             el = me.constrainHeader ? me.header.el : me.el;
11041             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
11042             return el.getConstrainVector(constrainTo);
11043         }
11044     },
11045
11046     /**
11047      * Aligns this floating Component to the specified element
11048      *
11049      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
11050      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
11051      * omponent instance. If a string id is passed, it will be used as an element id.
11052      * @param {String} [position="tl-bl?"] The position to align to (see {@link
11053      * Ext.Element#alignTo} for more details).
11054      * @param {Number[]} [offsets] Offset the positioning by [x, y]
11055      * @return {Ext.Component} this
11056      */
11057     alignTo: function(element, position, offsets) {
11058         if (element.isComponent) {
11059             element = element.getEl();
11060         }
11061         var xy = this.el.getAlignToXY(element, position, offsets);
11062         this.setPagePosition(xy);
11063         return this;
11064     },
11065
11066     /**
11067      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
11068      * Ext.ZIndexManager ZIndexManager}
11069      *
11070      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
11071      *
11072      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
11073      * @return {Ext.Component} this
11074      */
11075     toFront: function(preventFocus) {
11076         var me = this;
11077
11078         // Find the floating Component which provides the base for this Component's zIndexing.
11079         // That must move to front to then be able to rebase its zIndex stack and move this to the front
11080         if (me.zIndexParent) {
11081             me.zIndexParent.toFront(true);
11082         }
11083         if (me.zIndexManager.bringToFront(me)) {
11084             if (!Ext.isDefined(preventFocus)) {
11085                 preventFocus = !me.focusOnToFront;
11086             }
11087             if (!preventFocus) {
11088                 // Kick off a delayed focus request.
11089                 // If another floating Component is toFronted before the delay expires
11090                 // this will not receive focus.
11091                 me.focus(false, true);
11092             }
11093         }
11094         return me;
11095     },
11096
11097     /**
11098      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
11099      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
11100      *
11101      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
11102      *
11103      * This method also fires the {@link Ext.Component#activate activate} or
11104      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
11105      *
11106      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
11107      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
11108      */
11109     setActive: function(active, newActive) {
11110         var me = this;
11111         
11112         if (active) {
11113             if (me.el.shadow && !me.maximized) {
11114                 me.el.enableShadow(true);
11115             }
11116             me.fireEvent('activate', me);
11117         } else {
11118             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
11119             // can keep their shadows all the time
11120             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
11121                 me.el.disableShadow();
11122             }
11123             me.fireEvent('deactivate', me);
11124         }
11125     },
11126
11127     /**
11128      * Sends this Component to the back of (lower z-index than) any other visible windows
11129      * @return {Ext.Component} this
11130      */
11131     toBack: function() {
11132         this.zIndexManager.sendToBack(this);
11133         return this;
11134     },
11135
11136     /**
11137      * Center this Component in its container.
11138      * @return {Ext.Component} this
11139      */
11140     center: function() {
11141         var me = this,
11142             xy = me.el.getAlignToXY(me.container, 'c-c');
11143         me.setPagePosition(xy);
11144         return me;
11145     },
11146
11147     // private
11148     syncShadow : function(){
11149         if (this.floating) {
11150             this.el.sync(true);
11151         }
11152     },
11153
11154     // private
11155     fitContainer: function() {
11156         var parent = this.floatParent,
11157             container = parent ? parent.getTargetEl() : this.container,
11158             size = container.getViewSize(false);
11159
11160         this.setSize(size);
11161     }
11162 });
11163 /**
11164  * Base Layout class - extended by ComponentLayout and ContainerLayout
11165  */
11166 Ext.define('Ext.layout.Layout', {
11167
11168     /* Begin Definitions */
11169
11170     /* End Definitions */
11171
11172     isLayout: true,
11173     initialized: false,
11174
11175     statics: {
11176         create: function(layout, defaultType) {
11177             var type;
11178             if (layout instanceof Ext.layout.Layout) {
11179                 return Ext.createByAlias('layout.' + layout);
11180             } else {
11181                 if (!layout || typeof layout === 'string') {
11182                     type = layout || defaultType;
11183                     layout = {};                    
11184                 }
11185                 else {
11186                     type = layout.type || defaultType;
11187                 }
11188                 return Ext.createByAlias('layout.' + type, layout || {});
11189             }
11190         }
11191     },
11192
11193     constructor : function(config) {
11194         this.id = Ext.id(null, this.type + '-');
11195         Ext.apply(this, config);
11196     },
11197
11198     /**
11199      * @private
11200      */
11201     layout : function() {
11202         var me = this;
11203         me.layoutBusy = true;
11204         me.initLayout();
11205
11206         if (me.beforeLayout.apply(me, arguments) !== false) {
11207             me.layoutCancelled = false;
11208             me.onLayout.apply(me, arguments);
11209             me.childrenChanged = false;
11210             me.owner.needsLayout = false;
11211             me.layoutBusy = false;
11212             me.afterLayout.apply(me, arguments);
11213         }
11214         else {
11215             me.layoutCancelled = true;
11216         }
11217         me.layoutBusy = false;
11218         me.doOwnerCtLayouts();
11219     },
11220
11221     beforeLayout : function() {
11222         this.renderChildren();
11223         return true;
11224     },
11225
11226     renderChildren: function () {
11227         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
11228     },
11229
11230     /**
11231      * @private
11232      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
11233      * also determines if the items are in the proper place dom.
11234      */
11235     renderItems : function(items, target) {
11236         var me = this,
11237             ln = items.length,
11238             i = 0,
11239             item;
11240
11241         for (; i < ln; i++) {
11242             item = items[i];
11243             if (item && !item.rendered) {
11244                 me.renderItem(item, target, i);
11245             } else if (!me.isValidParent(item, target, i)) {
11246                 me.moveItem(item, target, i);
11247             } else {
11248                 // still need to configure the item, it may have moved in the container.
11249                 me.configureItem(item);
11250             }
11251         }
11252     },
11253
11254     // @private - Validates item is in the proper place in the dom.
11255     isValidParent : function(item, target, position) {
11256         var dom = item.el ? item.el.dom : Ext.getDom(item);
11257         if (dom && target && target.dom) {
11258             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
11259                 return false;
11260             }
11261             return (dom.parentNode == (target.dom || target));
11262         }
11263         return false;
11264     },
11265
11266     /**
11267      * @private
11268      * Renders the given Component into the target Element.
11269      * @param {Ext.Component} item The Component to render
11270      * @param {Ext.Element} target The target Element
11271      * @param {Number} position The position within the target to render the item to
11272      */
11273     renderItem : function(item, target, position) {
11274         var me = this;
11275         if (!item.rendered) {
11276             if (me.itemCls) {
11277                 item.addCls(me.itemCls);
11278             }
11279             if (me.owner.itemCls) {
11280                 item.addCls(me.owner.itemCls);
11281             }
11282             item.render(target, position);
11283             me.configureItem(item);
11284             me.childrenChanged = true;
11285         }
11286     },
11287
11288     /**
11289      * @private
11290      * Moved Component to the provided target instead.
11291      */
11292     moveItem : function(item, target, position) {
11293         // Make sure target is a dom element
11294         target = target.dom || target;
11295         if (typeof position == 'number') {
11296             position = target.childNodes[position];
11297         }
11298         target.insertBefore(item.el.dom, position || null);
11299         item.container = Ext.get(target);
11300         this.configureItem(item);
11301         this.childrenChanged = true;
11302     },
11303
11304     /**
11305      * @private
11306      * Adds the layout's targetCls if necessary and sets
11307      * initialized flag when complete.
11308      */
11309     initLayout : function() {
11310         var me = this,
11311             targetCls = me.targetCls;
11312             
11313         if (!me.initialized && !Ext.isEmpty(targetCls)) {
11314             me.getTarget().addCls(targetCls);
11315         }
11316         me.initialized = true;
11317     },
11318
11319     // @private Sets the layout owner
11320     setOwner : function(owner) {
11321         this.owner = owner;
11322     },
11323
11324     // @private - Returns empty array
11325     getLayoutItems : function() {
11326         return [];
11327     },
11328
11329     /**
11330      * @private
11331      * Applies itemCls
11332      * Empty template method
11333      */
11334     configureItem: Ext.emptyFn,
11335     
11336     // Placeholder empty functions for subclasses to extend
11337     onLayout : Ext.emptyFn,
11338     afterLayout : Ext.emptyFn,
11339     onRemove : Ext.emptyFn,
11340     onDestroy : Ext.emptyFn,
11341     doOwnerCtLayouts : Ext.emptyFn,
11342
11343     /**
11344      * @private
11345      * Removes itemCls
11346      */
11347     afterRemove : function(item) {
11348         var el = item.el,
11349             owner = this.owner,
11350             itemCls = this.itemCls,
11351             ownerCls = owner.itemCls;
11352             
11353         // Clear managed dimensions flag when removed from the layout.
11354         if (item.rendered && !item.isDestroyed) {
11355             if (itemCls) {
11356                 el.removeCls(itemCls);
11357             }
11358             if (ownerCls) {
11359                 el.removeCls(ownerCls);
11360             }
11361         }
11362
11363         // These flags are set at the time a child item is added to a layout.
11364         // The layout must decide if it is managing the item's width, or its height, or both.
11365         // See AbstractComponent for docs on these properties.
11366         delete item.layoutManagedWidth;
11367         delete item.layoutManagedHeight;
11368     },
11369
11370     /**
11371      * Destroys this layout. This is a template method that is empty by default, but should be implemented
11372      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
11373      * @template
11374      */
11375     destroy : function() {
11376         var targetCls = this.targetCls,
11377             target;
11378         
11379         if (!Ext.isEmpty(targetCls)) {
11380             target = this.getTarget();
11381             if (target) {
11382                 target.removeCls(targetCls);
11383             }
11384         }
11385         this.onDestroy();
11386     }
11387 });
11388 /**
11389  * @class Ext.ZIndexManager
11390  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
11391  * and Component activation behavior, including masking below the active (topmost) Component.</p>
11392  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
11393  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
11394  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
11395  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
11396  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
11397  * guaranteed to be correct, relative to the Window.</p>
11398  */
11399 Ext.define('Ext.ZIndexManager', {
11400
11401     alternateClassName: 'Ext.WindowGroup',
11402
11403     statics: {
11404         zBase : 9000
11405     },
11406
11407     constructor: function(container) {
11408         var me = this;
11409
11410         me.list = {};
11411         me.zIndexStack = [];
11412         me.front = null;
11413
11414         if (container) {
11415
11416             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
11417             if (container.isContainer) {
11418                 container.on('resize', me._onContainerResize, me);
11419                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
11420                 // The containing element we will be dealing with (eg masking) is the content target
11421                 me.targetEl = container.getTargetEl();
11422                 me.container = container;
11423             }
11424             // This is the ZIndexManager for a DOM element
11425             else {
11426                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
11427                 me.zseed = me.getNextZSeed();
11428                 me.targetEl = Ext.get(container);
11429             }
11430         }
11431         // No container passed means we are the global WindowManager. Our target is the doc body.
11432         // DOM must be ready to collect that ref.
11433         else {
11434             Ext.EventManager.onWindowResize(me._onContainerResize, me);
11435             me.zseed = me.getNextZSeed();
11436             Ext.onDocumentReady(function() {
11437                 me.targetEl = Ext.getBody();
11438             });
11439         }
11440     },
11441
11442     getNextZSeed: function() {
11443         return (Ext.ZIndexManager.zBase += 10000);
11444     },
11445
11446     setBase: function(baseZIndex) {
11447         this.zseed = baseZIndex;
11448         return this.assignZIndices();
11449     },
11450
11451     // private
11452     assignZIndices: function() {
11453         var a = this.zIndexStack,
11454             len = a.length,
11455             i = 0,
11456             zIndex = this.zseed,
11457             comp;
11458
11459         for (; i < len; i++) {
11460             comp = a[i];
11461             if (comp && !comp.hidden) {
11462
11463                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
11464                 // that Component.
11465                 // If it's just a plain floating Component such as a BoundList, then the
11466                 // return value is the passed value plus 10, ready for the next item.
11467                 // If a floating *Container* has its zIndex set, it re-orders its managed
11468                 // floating children, starting from that new base, and returns a value 10000 above
11469                 // the highest zIndex which it allocates.
11470                 zIndex = comp.setZIndex(zIndex);
11471             }
11472         }
11473         this._activateLast();
11474         return zIndex;
11475     },
11476
11477     // private
11478     _setActiveChild: function(comp) {
11479         if (comp !== this.front) {
11480
11481             if (this.front) {
11482                 this.front.setActive(false, comp);
11483             }
11484             this.front = comp;
11485             if (comp) {
11486                 comp.setActive(true);
11487                 if (comp.modal) {
11488                     this._showModalMask(comp);
11489                 }
11490             }
11491         }
11492     },
11493
11494     // private
11495     _activateLast: function(justHidden) {
11496         var comp,
11497             lastActivated = false,
11498             i;
11499
11500         // Go down through the z-index stack.
11501         // Activate the next visible one down.
11502         // Keep going down to find the next visible modal one to shift the modal mask down under
11503         for (i = this.zIndexStack.length-1; i >= 0; --i) {
11504             comp = this.zIndexStack[i];
11505             if (!comp.hidden) {
11506                 if (!lastActivated) {
11507                     this._setActiveChild(comp);
11508                     lastActivated = true;
11509                 }
11510
11511                 // Move any modal mask down to just under the next modal floater down the stack
11512                 if (comp.modal) {
11513                     this._showModalMask(comp);
11514                     return;
11515                 }
11516             }
11517         }
11518
11519         // none to activate, so there must be no modal mask.
11520         // And clear the currently active property
11521         this._hideModalMask();
11522         if (!lastActivated) {
11523             this._setActiveChild(null);
11524         }
11525     },
11526
11527     _showModalMask: function(comp) {
11528         var zIndex = comp.el.getStyle('zIndex') - 4,
11529             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
11530             parentBox;
11531         
11532         if (!maskTarget) {
11533             //<debug>
11534             Ext.global.console && Ext.global.console.warn && Ext.global.console.warn('mask target could not be found. Mask cannot be shown');
11535             //</debug>
11536             return;
11537         }
11538         
11539         parentBox = maskTarget.getBox();
11540
11541         if (!this.mask) {
11542             this.mask = Ext.getBody().createChild({
11543                 cls: Ext.baseCSSPrefix + 'mask'
11544             });
11545             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
11546             this.mask.on('click', this._onMaskClick, this);
11547         }
11548         if (maskTarget.dom === document.body) {
11549             parentBox.height = Ext.Element.getViewHeight();
11550         }
11551         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
11552         this.mask.setBox(parentBox);
11553         this.mask.setStyle('zIndex', zIndex);
11554         this.mask.show();
11555     },
11556
11557     _hideModalMask: function() {
11558         if (this.mask && this.mask.dom.parentNode) {
11559             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
11560             this.mask.hide();
11561         }
11562     },
11563
11564     _onMaskClick: function() {
11565         if (this.front) {
11566             this.front.focus();
11567         }
11568     },
11569
11570     _onContainerResize: function() {
11571         if (this.mask && this.mask.isVisible()) {
11572             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
11573         }
11574     },
11575
11576     /**
11577      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
11578      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
11579      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
11580      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
11581      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
11582      * ZIndexManager in the desktop sample app:</p><code><pre>
11583 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
11584 </pre></code>
11585      * @param {Ext.Component} comp The Component to register.
11586      */
11587     register : function(comp) {
11588         if (comp.zIndexManager) {
11589             comp.zIndexManager.unregister(comp);
11590         }
11591         comp.zIndexManager = this;
11592
11593         this.list[comp.id] = comp;
11594         this.zIndexStack.push(comp);
11595         comp.on('hide', this._activateLast, this);
11596     },
11597
11598     /**
11599      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
11600      * need to be called. Components are automatically unregistered upon destruction.
11601      * See {@link #register}.</p>
11602      * @param {Ext.Component} comp The Component to unregister.
11603      */
11604     unregister : function(comp) {
11605         delete comp.zIndexManager;
11606         if (this.list && this.list[comp.id]) {
11607             delete this.list[comp.id];
11608             comp.un('hide', this._activateLast);
11609             Ext.Array.remove(this.zIndexStack, comp);
11610
11611             // Destruction requires that the topmost visible floater be activated. Same as hiding.
11612             this._activateLast(comp);
11613         }
11614     },
11615
11616     /**
11617      * Gets a registered Component by id.
11618      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
11619      * @return {Ext.Component}
11620      */
11621     get : function(id) {
11622         return typeof id == "object" ? id : this.list[id];
11623     },
11624
11625    /**
11626      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
11627      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
11628      * @return {Boolean} True if the dialog was brought to the front, else false
11629      * if it was already in front
11630      */
11631     bringToFront : function(comp) {
11632         comp = this.get(comp);
11633         if (comp !== this.front) {
11634             Ext.Array.remove(this.zIndexStack, comp);
11635             this.zIndexStack.push(comp);
11636             this.assignZIndices();
11637             return true;
11638         }
11639         if (comp.modal) {
11640             this._showModalMask(comp);
11641         }
11642         return false;
11643     },
11644
11645     /**
11646      * Sends the specified Component to the back of other active Components in this ZIndexManager.
11647      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
11648      * @return {Ext.Component} The Component
11649      */
11650     sendToBack : function(comp) {
11651         comp = this.get(comp);
11652         Ext.Array.remove(this.zIndexStack, comp);
11653         this.zIndexStack.unshift(comp);
11654         this.assignZIndices();
11655         return comp;
11656     },
11657
11658     /**
11659      * Hides all Components managed by this ZIndexManager.
11660      */
11661     hideAll : function() {
11662         for (var id in this.list) {
11663             if (this.list[id].isComponent && this.list[id].isVisible()) {
11664                 this.list[id].hide();
11665             }
11666         }
11667     },
11668
11669     /**
11670      * @private
11671      * Temporarily hides all currently visible managed Components. This is for when
11672      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
11673      * they should all be hidden just for the duration of the drag.
11674      */
11675     hide: function() {
11676         var i = 0,
11677             ln = this.zIndexStack.length,
11678             comp;
11679
11680         this.tempHidden = [];
11681         for (; i < ln; i++) {
11682             comp = this.zIndexStack[i];
11683             if (comp.isVisible()) {
11684                 this.tempHidden.push(comp);
11685                 comp.hide();
11686             }
11687         }
11688     },
11689
11690     /**
11691      * @private
11692      * Restores temporarily hidden managed Components to visibility.
11693      */
11694     show: function() {
11695         var i = 0,
11696             ln = this.tempHidden.length,
11697             comp,
11698             x,
11699             y;
11700
11701         for (; i < ln; i++) {
11702             comp = this.tempHidden[i];
11703             x = comp.x;
11704             y = comp.y;
11705             comp.show();
11706             comp.setPosition(x, y);
11707         }
11708         delete this.tempHidden;
11709     },
11710
11711     /**
11712      * Gets the currently-active Component in this ZIndexManager.
11713      * @return {Ext.Component} The active Component
11714      */
11715     getActive : function() {
11716         return this.front;
11717     },
11718
11719     /**
11720      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
11721      * The function should accept a single {@link Ext.Component} reference as its only argument and should
11722      * return true if the Component matches the search criteria, otherwise it should return false.
11723      * @param {Function} fn The search function
11724      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
11725      * that gets passed to the function if not specified)
11726      * @return {Array} An array of zero or more matching windows
11727      */
11728     getBy : function(fn, scope) {
11729         var r = [],
11730             i = 0,
11731             len = this.zIndexStack.length,
11732             comp;
11733
11734         for (; i < len; i++) {
11735             comp = this.zIndexStack[i];
11736             if (fn.call(scope||comp, comp) !== false) {
11737                 r.push(comp);
11738             }
11739         }
11740         return r;
11741     },
11742
11743     /**
11744      * Executes the specified function once for every Component in this ZIndexManager, passing each
11745      * Component as the only parameter. Returning false from the function will stop the iteration.
11746      * @param {Function} fn The function to execute for each item
11747      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
11748      */
11749     each : function(fn, scope) {
11750         var comp;
11751         for (var id in this.list) {
11752             comp = this.list[id];
11753             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
11754                 return;
11755             }
11756         }
11757     },
11758
11759     /**
11760      * Executes the specified function once for every Component in this ZIndexManager, passing each
11761      * Component as the only parameter. Returning false from the function will stop the iteration.
11762      * The components are passed to the function starting at the bottom and proceeding to the top.
11763      * @param {Function} fn The function to execute for each item
11764      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
11765      * is executed. Defaults to the current Component in the iteration.
11766      */
11767     eachBottomUp: function (fn, scope) {
11768         var comp,
11769             stack = this.zIndexStack,
11770             i, n;
11771
11772         for (i = 0, n = stack.length ; i < n; i++) {
11773             comp = stack[i];
11774             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
11775                 return;
11776             }
11777         }
11778     },
11779
11780     /**
11781      * Executes the specified function once for every Component in this ZIndexManager, passing each
11782      * Component as the only parameter. Returning false from the function will stop the iteration.
11783      * The components are passed to the function starting at the top and proceeding to the bottom.
11784      * @param {Function} fn The function to execute for each item
11785      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
11786      * is executed. Defaults to the current Component in the iteration.
11787      */
11788     eachTopDown: function (fn, scope) {
11789         var comp,
11790             stack = this.zIndexStack,
11791             i;
11792
11793         for (i = stack.length ; i-- > 0; ) {
11794             comp = stack[i];
11795             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
11796                 return;
11797             }
11798         }
11799     },
11800
11801     destroy: function() {
11802         this.each(function(c) {
11803             c.destroy();
11804         });
11805         delete this.zIndexStack;
11806         delete this.list;
11807         delete this.container;
11808         delete this.targetEl;
11809     }
11810 }, function() {
11811     /**
11812      * @class Ext.WindowManager
11813      * @extends Ext.ZIndexManager
11814      * <p>The default global floating Component group that is available automatically.</p>
11815      * <p>This manages instances of floating Components which were rendered programatically without
11816      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
11817      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
11818      * there are managed by that ZIndexManager.</p>
11819      * @singleton
11820      */
11821     Ext.WindowManager = Ext.WindowMgr = new this();
11822 });
11823
11824 /**
11825  * @private
11826  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
11827  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
11828  * for its container.
11829  */
11830 Ext.define('Ext.layout.container.boxOverflow.None', {
11831     
11832     alternateClassName: 'Ext.layout.boxOverflow.None',
11833     
11834     constructor: function(layout, config) {
11835         this.layout = layout;
11836         Ext.apply(this, config || {});
11837     },
11838
11839     handleOverflow: Ext.emptyFn,
11840
11841     clearOverflow: Ext.emptyFn,
11842     
11843     onRemove: Ext.emptyFn,
11844
11845     /**
11846      * @private
11847      * Normalizes an item reference, string id or numerical index into a reference to the item
11848      * @param {Ext.Component/String/Number} item The item reference, id or index
11849      * @return {Ext.Component} The item
11850      */
11851     getItem: function(item) {
11852         return this.layout.owner.getComponent(item);
11853     },
11854     
11855     onRemove: Ext.emptyFn
11856 });
11857 /**
11858  * @class Ext.util.KeyMap
11859  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
11860  * The constructor accepts the same config object as defined by {@link #addBinding}.
11861  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
11862  * combination it will call the function with this signature (if the match is a multi-key
11863  * combination the callback will still be called only once): (String key, Ext.EventObject e)
11864  * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
11865  * Usage:
11866  <pre><code>
11867 // map one key by key code
11868 var map = new Ext.util.KeyMap("my-element", {
11869     key: 13, // or Ext.EventObject.ENTER
11870     fn: myHandler,
11871     scope: myObject
11872 });
11873
11874 // map multiple keys to one action by string
11875 var map = new Ext.util.KeyMap("my-element", {
11876     key: "a\r\n\t",
11877     fn: myHandler,
11878     scope: myObject
11879 });
11880
11881 // map multiple keys to multiple actions by strings and array of codes
11882 var map = new Ext.util.KeyMap("my-element", [
11883     {
11884         key: [10,13],
11885         fn: function(){ alert("Return was pressed"); }
11886     }, {
11887         key: "abc",
11888         fn: function(){ alert('a, b or c was pressed'); }
11889     }, {
11890         key: "\t",
11891         ctrl:true,
11892         shift:true,
11893         fn: function(){ alert('Control + shift + tab was pressed.'); }
11894     }
11895 ]);
11896 </code></pre>
11897  */
11898 Ext.define('Ext.util.KeyMap', {
11899     alternateClassName: 'Ext.KeyMap',
11900
11901     /**
11902      * Creates new KeyMap.
11903      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
11904      * @param {Object} binding The binding (see {@link #addBinding})
11905      * @param {String} [eventName="keydown"] The event to bind to
11906      */
11907     constructor: function(el, binding, eventName){
11908         var me = this;
11909
11910         Ext.apply(me, {
11911             el: Ext.get(el),
11912             eventName: eventName || me.eventName,
11913             bindings: []
11914         });
11915         if (binding) {
11916             me.addBinding(binding);
11917         }
11918         me.enable();
11919     },
11920
11921     eventName: 'keydown',
11922
11923     /**
11924      * Add a new binding to this KeyMap. The following config object properties are supported:
11925      * <pre>
11926 Property            Type             Description
11927 ----------          ---------------  ----------------------------------------------------------------------
11928 key                 String/Array     A single keycode or an array of keycodes to handle
11929 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)
11930 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)
11931 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)
11932 handler             Function         The function to call when KeyMap finds the expected key combination
11933 fn                  Function         Alias of handler (for backwards-compatibility)
11934 scope               Object           The scope of the callback function
11935 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.
11936 </pre>
11937      *
11938      * Usage:
11939      * <pre><code>
11940 // Create a KeyMap
11941 var map = new Ext.util.KeyMap(document, {
11942     key: Ext.EventObject.ENTER,
11943     fn: handleKey,
11944     scope: this
11945 });
11946
11947 //Add a new binding to the existing KeyMap later
11948 map.addBinding({
11949     key: 'abc',
11950     shift: true,
11951     fn: handleKey,
11952     scope: this
11953 });
11954 </code></pre>
11955      * @param {Object/Object[]} binding A single KeyMap config or an array of configs
11956      */
11957     addBinding : function(binding){
11958         if (Ext.isArray(binding)) {
11959             Ext.each(binding, this.addBinding, this);
11960             return;
11961         }
11962
11963         var keyCode = binding.key,
11964             processed = false,
11965             key,
11966             keys,
11967             keyString,
11968             i,
11969             len;
11970
11971         if (Ext.isString(keyCode)) {
11972             keys = [];
11973             keyString = keyCode.toUpperCase();
11974
11975             for (i = 0, len = keyString.length; i < len; ++i){
11976                 keys.push(keyString.charCodeAt(i));
11977             }
11978             keyCode = keys;
11979             processed = true;
11980         }
11981
11982         if (!Ext.isArray(keyCode)) {
11983             keyCode = [keyCode];
11984         }
11985
11986         if (!processed) {
11987             for (i = 0, len = keyCode.length; i < len; ++i) {
11988                 key = keyCode[i];
11989                 if (Ext.isString(key)) {
11990                     keyCode[i] = key.toUpperCase().charCodeAt(0);
11991                 }
11992             }
11993         }
11994
11995         this.bindings.push(Ext.apply({
11996             keyCode: keyCode
11997         }, binding));
11998     },
11999
12000     /**
12001      * Process any keydown events on the element
12002      * @private
12003      * @param {Ext.EventObject} event
12004      */
12005     handleKeyDown: function(event) {
12006         if (this.enabled) { //just in case
12007             var bindings = this.bindings,
12008                 i = 0,
12009                 len = bindings.length;
12010
12011             event = this.processEvent(event);
12012             for(; i < len; ++i){
12013                 this.processBinding(bindings[i], event);
12014             }
12015         }
12016     },
12017
12018     /**
12019      * Ugly hack to allow this class to be tested. Currently WebKit gives
12020      * no way to raise a key event properly with both
12021      * a) A keycode
12022      * b) The alt/ctrl/shift modifiers
12023      * So we have to simulate them here. Yuk!
12024      * This is a stub method intended to be overridden by tests.
12025      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
12026      * @private
12027      */
12028     processEvent: function(event){
12029         return event;
12030     },
12031
12032     /**
12033      * Process a particular binding and fire the handler if necessary.
12034      * @private
12035      * @param {Object} binding The binding information
12036      * @param {Ext.EventObject} event
12037      */
12038     processBinding: function(binding, event){
12039         if (this.checkModifiers(binding, event)) {
12040             var key = event.getKey(),
12041                 handler = binding.fn || binding.handler,
12042                 scope = binding.scope || this,
12043                 keyCode = binding.keyCode,
12044                 defaultEventAction = binding.defaultEventAction,
12045                 i,
12046                 len,
12047                 keydownEvent = new Ext.EventObjectImpl(event);
12048
12049
12050             for (i = 0, len = keyCode.length; i < len; ++i) {
12051                 if (key === keyCode[i]) {
12052                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
12053                         keydownEvent[defaultEventAction]();
12054                     }
12055                     break;
12056                 }
12057             }
12058         }
12059     },
12060
12061     /**
12062      * Check if the modifiers on the event match those on the binding
12063      * @private
12064      * @param {Object} binding
12065      * @param {Ext.EventObject} event
12066      * @return {Boolean} True if the event matches the binding
12067      */
12068     checkModifiers: function(binding, e){
12069         var keys = ['shift', 'ctrl', 'alt'],
12070             i = 0,
12071             len = keys.length,
12072             val, key;
12073
12074         for (; i < len; ++i){
12075             key = keys[i];
12076             val = binding[key];
12077             if (!(val === undefined || (val === e[key + 'Key']))) {
12078                 return false;
12079             }
12080         }
12081         return true;
12082     },
12083
12084     /**
12085      * Shorthand for adding a single key listener
12086      * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
12087      * following options:
12088      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
12089      * @param {Function} fn The function to call
12090      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
12091      */
12092     on: function(key, fn, scope) {
12093         var keyCode, shift, ctrl, alt;
12094         if (Ext.isObject(key) && !Ext.isArray(key)) {
12095             keyCode = key.key;
12096             shift = key.shift;
12097             ctrl = key.ctrl;
12098             alt = key.alt;
12099         } else {
12100             keyCode = key;
12101         }
12102         this.addBinding({
12103             key: keyCode,
12104             shift: shift,
12105             ctrl: ctrl,
12106             alt: alt,
12107             fn: fn,
12108             scope: scope
12109         });
12110     },
12111
12112     /**
12113      * Returns true if this KeyMap is enabled
12114      * @return {Boolean}
12115      */
12116     isEnabled : function(){
12117         return this.enabled;
12118     },
12119
12120     /**
12121      * Enables this KeyMap
12122      */
12123     enable: function(){
12124         var me = this;
12125         
12126         if (!me.enabled) {
12127             me.el.on(me.eventName, me.handleKeyDown, me);
12128             me.enabled = true;
12129         }
12130     },
12131
12132     /**
12133      * Disable this KeyMap
12134      */
12135     disable: function(){
12136         var me = this;
12137         
12138         if (me.enabled) {
12139             me.el.removeListener(me.eventName, me.handleKeyDown, me);
12140             me.enabled = false;
12141         }
12142     },
12143
12144     /**
12145      * Convenience function for setting disabled/enabled by boolean.
12146      * @param {Boolean} disabled
12147      */
12148     setDisabled : function(disabled){
12149         if (disabled) {
12150             this.disable();
12151         } else {
12152             this.enable();
12153         }
12154     },
12155
12156     /**
12157      * Destroys the KeyMap instance and removes all handlers.
12158      * @param {Boolean} removeEl True to also remove the attached element
12159      */
12160     destroy: function(removeEl){
12161         var me = this;
12162
12163         me.bindings = [];
12164         me.disable();
12165         if (removeEl === true) {
12166             me.el.remove();
12167         }
12168         delete me.el;
12169     }
12170 });
12171 /**
12172  * @class Ext.util.ClickRepeater
12173  * @extends Ext.util.Observable
12174  *
12175  * A wrapper class which can be applied to any element. Fires a "click" event while the
12176  * mouse is pressed. The interval between firings may be specified in the config but
12177  * defaults to 20 milliseconds.
12178  *
12179  * Optionally, a CSS class may be applied to the element during the time it is pressed.
12180  *
12181  */
12182 Ext.define('Ext.util.ClickRepeater', {
12183     extend: 'Ext.util.Observable',
12184
12185     /**
12186      * Creates new ClickRepeater.
12187      * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
12188      * @param {Object} config (optional) Config object.
12189      */
12190     constructor : function(el, config){
12191         this.el = Ext.get(el);
12192         this.el.unselectable();
12193
12194         Ext.apply(this, config);
12195
12196         this.addEvents(
12197         /**
12198          * @event mousedown
12199          * Fires when the mouse button is depressed.
12200          * @param {Ext.util.ClickRepeater} this
12201          * @param {Ext.EventObject} e
12202          */
12203         "mousedown",
12204         /**
12205          * @event click
12206          * Fires on a specified interval during the time the element is pressed.
12207          * @param {Ext.util.ClickRepeater} this
12208          * @param {Ext.EventObject} e
12209          */
12210         "click",
12211         /**
12212          * @event mouseup
12213          * Fires when the mouse key is released.
12214          * @param {Ext.util.ClickRepeater} this
12215          * @param {Ext.EventObject} e
12216          */
12217         "mouseup"
12218         );
12219
12220         if(!this.disabled){
12221             this.disabled = true;
12222             this.enable();
12223         }
12224
12225         // allow inline handler
12226         if(this.handler){
12227             this.on("click", this.handler,  this.scope || this);
12228         }
12229
12230         this.callParent();
12231     },
12232
12233     /**
12234      * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
12235      */
12236
12237     /**
12238      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
12239      */
12240
12241     /**
12242      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
12243      * "interval" and "delay" are ignored.
12244      */
12245
12246     /**
12247      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
12248      */
12249     interval : 20,
12250
12251     /**
12252      * @cfg {Number} delay The initial delay before the repeating event begins firing.
12253      * Similar to an autorepeat key delay.
12254      */
12255     delay: 250,
12256
12257     /**
12258      * @cfg {Boolean} preventDefault True to prevent the default click event
12259      */
12260     preventDefault : true,
12261     /**
12262      * @cfg {Boolean} stopDefault True to stop the default click event
12263      */
12264     stopDefault : false,
12265
12266     timer : 0,
12267
12268     /**
12269      * Enables the repeater and allows events to fire.
12270      */
12271     enable: function(){
12272         if(this.disabled){
12273             this.el.on('mousedown', this.handleMouseDown, this);
12274             if (Ext.isIE){
12275                 this.el.on('dblclick', this.handleDblClick, this);
12276             }
12277             if(this.preventDefault || this.stopDefault){
12278                 this.el.on('click', this.eventOptions, this);
12279             }
12280         }
12281         this.disabled = false;
12282     },
12283
12284     /**
12285      * Disables the repeater and stops events from firing.
12286      */
12287     disable: function(/* private */ force){
12288         if(force || !this.disabled){
12289             clearTimeout(this.timer);
12290             if(this.pressedCls){
12291                 this.el.removeCls(this.pressedCls);
12292             }
12293             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
12294             this.el.removeAllListeners();
12295         }
12296         this.disabled = true;
12297     },
12298
12299     /**
12300      * Convenience function for setting disabled/enabled by boolean.
12301      * @param {Boolean} disabled
12302      */
12303     setDisabled: function(disabled){
12304         this[disabled ? 'disable' : 'enable']();
12305     },
12306
12307     eventOptions: function(e){
12308         if(this.preventDefault){
12309             e.preventDefault();
12310         }
12311         if(this.stopDefault){
12312             e.stopEvent();
12313         }
12314     },
12315
12316     // private
12317     destroy : function() {
12318         this.disable(true);
12319         Ext.destroy(this.el);
12320         this.clearListeners();
12321     },
12322
12323     handleDblClick : function(e){
12324         clearTimeout(this.timer);
12325         this.el.blur();
12326
12327         this.fireEvent("mousedown", this, e);
12328         this.fireEvent("click", this, e);
12329     },
12330
12331     // private
12332     handleMouseDown : function(e){
12333         clearTimeout(this.timer);
12334         this.el.blur();
12335         if(this.pressedCls){
12336             this.el.addCls(this.pressedCls);
12337         }
12338         this.mousedownTime = new Date();
12339
12340         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
12341         this.el.on("mouseout", this.handleMouseOut, this);
12342
12343         this.fireEvent("mousedown", this, e);
12344         this.fireEvent("click", this, e);
12345
12346         // Do not honor delay or interval if acceleration wanted.
12347         if (this.accelerate) {
12348             this.delay = 400;
12349         }
12350
12351         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
12352         // the global shared EventObject gets a new Event put into it before the timer fires.
12353         e = new Ext.EventObjectImpl(e);
12354
12355         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
12356     },
12357
12358     // private
12359     click : function(e){
12360         this.fireEvent("click", this, e);
12361         this.timer =  Ext.defer(this.click, this.accelerate ?
12362             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
12363                 400,
12364                 -390,
12365                 12000) :
12366             this.interval, this, [e]);
12367     },
12368
12369     easeOutExpo : function (t, b, c, d) {
12370         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
12371     },
12372
12373     // private
12374     handleMouseOut : function(){
12375         clearTimeout(this.timer);
12376         if(this.pressedCls){
12377             this.el.removeCls(this.pressedCls);
12378         }
12379         this.el.on("mouseover", this.handleMouseReturn, this);
12380     },
12381
12382     // private
12383     handleMouseReturn : function(){
12384         this.el.un("mouseover", this.handleMouseReturn, this);
12385         if(this.pressedCls){
12386             this.el.addCls(this.pressedCls);
12387         }
12388         this.click();
12389     },
12390
12391     // private
12392     handleMouseUp : function(e){
12393         clearTimeout(this.timer);
12394         this.el.un("mouseover", this.handleMouseReturn, this);
12395         this.el.un("mouseout", this.handleMouseOut, this);
12396         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
12397         if(this.pressedCls){
12398             this.el.removeCls(this.pressedCls);
12399         }
12400         this.fireEvent("mouseup", this, e);
12401     }
12402 });
12403
12404 /**
12405  * @class Ext.layout.component.Component
12406  * @extends Ext.layout.Layout
12407  *
12408  * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
12409  * configuration property.  See {@link Ext.Component#componentLayout} for additional details.
12410  *
12411  * @private
12412  */
12413 Ext.define('Ext.layout.component.Component', {
12414
12415     /* Begin Definitions */
12416
12417     extend: 'Ext.layout.Layout',
12418
12419     /* End Definitions */
12420
12421     type: 'component',
12422
12423     monitorChildren: true,
12424
12425     initLayout : function() {
12426         var me = this,
12427             owner = me.owner,
12428             ownerEl = owner.el;
12429
12430         if (!me.initialized) {
12431             if (owner.frameSize) {
12432                 me.frameSize = owner.frameSize;
12433             }
12434             else {
12435                 owner.frameSize = me.frameSize = {
12436                     top: 0,
12437                     left: 0,
12438                     bottom: 0,
12439                     right: 0
12440                 };
12441             }
12442         }
12443         me.callParent(arguments);
12444     },
12445
12446     beforeLayout : function(width, height, isSetSize, callingContainer) {
12447         this.callParent(arguments);
12448
12449         var me = this,
12450             owner = me.owner,
12451             ownerCt = owner.ownerCt,
12452             layout = owner.layout,
12453             isVisible = owner.isVisible(true),
12454             ownerElChild = owner.el.child,
12455             layoutCollection;
12456
12457         // Cache the size we began with so we can see if there has been any effect.
12458         me.previousComponentSize = me.lastComponentSize;
12459
12460         // Do not allow autoing of any dimensions which are fixed
12461         if (!isSetSize
12462             && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
12463                 (!Ext.isNumber(height) && owner.isFixedHeight()))
12464             // unless we are being told to do so by the ownerCt's layout
12465             && callingContainer && callingContainer !== ownerCt) {
12466             
12467             me.doContainerLayout();
12468             return false;
12469         }
12470
12471         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
12472         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
12473         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
12474             if (owner.hiddenAncestor) {
12475                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
12476                 layoutCollection.remove(owner);
12477                 layoutCollection.add(owner);
12478             }
12479             owner.needsLayout = {
12480                 width: width,
12481                 height: height,
12482                 isSetSize: false
12483             };
12484         }
12485
12486         if (isVisible && this.needsLayout(width, height)) {
12487             return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
12488         }
12489         else {
12490             return false;
12491         }
12492     },
12493
12494     /**
12495     * Check if the new size is different from the current size and only
12496     * trigger a layout if it is necessary.
12497     * @param {Number} width The new width to set.
12498     * @param {Number} height The new height to set.
12499     */
12500     needsLayout : function(width, height) {
12501         var me = this,
12502             widthBeingChanged,
12503             heightBeingChanged;
12504             me.lastComponentSize = me.lastComponentSize || {
12505                 width: -Infinity,
12506                 height: -Infinity
12507             };
12508
12509         // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
12510         widthBeingChanged  = !Ext.isDefined(width)  || me.lastComponentSize.width  !== width;
12511
12512         // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
12513         heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
12514
12515
12516         // isSizing flag added to prevent redundant layouts when going up the layout chain
12517         return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
12518     },
12519
12520     /**
12521     * Set the size of any element supporting undefined, null, and values.
12522     * @param {Number} width The new width to set.
12523     * @param {Number} height The new height to set.
12524     */
12525     setElementSize: function(el, width, height) {
12526         if (width !== undefined && height !== undefined) {
12527             el.setSize(width, height);
12528         }
12529         else if (height !== undefined) {
12530             el.setHeight(height);
12531         }
12532         else if (width !== undefined) {
12533             el.setWidth(width);
12534         }
12535     },
12536
12537     /**
12538      * Returns the owner component's resize element.
12539      * @return {Ext.Element}
12540      */
12541      getTarget : function() {
12542          return this.owner.el;
12543      },
12544
12545     /**
12546      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
12547      * May be overridden in Component layout managers which implement an inner element.
12548      * @return {Ext.Element}
12549      */
12550     getRenderTarget : function() {
12551         return this.owner.el;
12552     },
12553
12554     /**
12555     * Set the size of the target element.
12556     * @param {Number} width The new width to set.
12557     * @param {Number} height The new height to set.
12558     */
12559     setTargetSize : function(width, height) {
12560         var me = this;
12561         me.setElementSize(me.owner.el, width, height);
12562
12563         if (me.owner.frameBody) {
12564             var targetInfo = me.getTargetInfo(),
12565                 padding = targetInfo.padding,
12566                 border = targetInfo.border,
12567                 frameSize = me.frameSize;
12568
12569             me.setElementSize(me.owner.frameBody,
12570                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
12571                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
12572             );
12573         }
12574
12575         me.autoSized = {
12576             width: !Ext.isNumber(width),
12577             height: !Ext.isNumber(height)
12578         };
12579
12580         me.lastComponentSize = {
12581             width: width,
12582             height: height
12583         };
12584     },
12585
12586     getTargetInfo : function() {
12587         if (!this.targetInfo) {
12588             var target = this.getTarget(),
12589                 body = this.owner.getTargetEl();
12590
12591             this.targetInfo = {
12592                 padding: {
12593                     top: target.getPadding('t'),
12594                     right: target.getPadding('r'),
12595                     bottom: target.getPadding('b'),
12596                     left: target.getPadding('l')
12597                 },
12598                 border: {
12599                     top: target.getBorderWidth('t'),
12600                     right: target.getBorderWidth('r'),
12601                     bottom: target.getBorderWidth('b'),
12602                     left: target.getBorderWidth('l')
12603                 },
12604                 bodyMargin: {
12605                     top: body.getMargin('t'),
12606                     right: body.getMargin('r'),
12607                     bottom: body.getMargin('b'),
12608                     left: body.getMargin('l')
12609                 }
12610             };
12611         }
12612         return this.targetInfo;
12613     },
12614
12615     // Start laying out UP the ownerCt's layout when flagged to do so.
12616     doOwnerCtLayouts: function() {
12617         var owner = this.owner,
12618             ownerCt = owner.ownerCt,
12619             ownerCtComponentLayout, ownerCtContainerLayout,
12620             curSize = this.lastComponentSize,
12621             prevSize = this.previousComponentSize,
12622             widthChange  = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width  !== prevSize.width  : true,
12623             heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
12624
12625         // If size has not changed, do not inform upstream layouts
12626         if (!ownerCt || (!widthChange && !heightChange)) {
12627             return;
12628         }
12629
12630         ownerCtComponentLayout = ownerCt.componentLayout;
12631         ownerCtContainerLayout = ownerCt.layout;
12632
12633         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
12634             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
12635
12636                 // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
12637                 if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
12638                     // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
12639                     this.isSizing = true;
12640                     ownerCt.doComponentLayout();
12641                     this.isSizing = false;
12642                 }
12643                 // Execute upstream Container layout
12644                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
12645                     ownerCtContainerLayout.layout();
12646                 }
12647             }
12648         }
12649     },
12650
12651     doContainerLayout: function() {
12652         var me = this,
12653             owner = me.owner,
12654             ownerCt = owner.ownerCt,
12655             layout = owner.layout,
12656             ownerCtComponentLayout;
12657
12658         // Run the container layout if it exists (layout for child items)
12659         // **Unless automatic laying out is suspended, or the layout is currently running**
12660         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
12661             layout.layout();
12662         }
12663
12664         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
12665         if (ownerCt && ownerCt.componentLayout) {
12666             ownerCtComponentLayout = ownerCt.componentLayout;
12667             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
12668                 ownerCtComponentLayout.childrenChanged = true;
12669             }
12670         }
12671     },
12672
12673     afterLayout : function(width, height, isSetSize, layoutOwner) {
12674         this.doContainerLayout();
12675         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
12676     }
12677 });
12678
12679 /**
12680  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
12681  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
12682  * should not contain any HTML, otherwise it may not be measured correctly.
12683  *
12684  * The measurement works by copying the relevant CSS styles that can affect the font related display, 
12685  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
12686  * provide a **fixed width** when doing the measurement.
12687  *
12688  * If multiple measurements are being done on the same element, you create a new instance to initialize 
12689  * to avoid the overhead of copying the styles to the element repeatedly.
12690  */
12691 Ext.define('Ext.util.TextMetrics', {
12692     statics: {
12693         shared: null,
12694         /**
12695          * Measures the size of the specified text
12696          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
12697          * that can affect the size of the rendered text
12698          * @param {String} text The text to measure
12699          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
12700          * in order to accurately measure the text height
12701          * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
12702          */
12703         measure: function(el, text, fixedWidth){
12704             var me = this,
12705                 shared = me.shared;
12706             
12707             if(!shared){
12708                 shared = me.shared = new me(el, fixedWidth);
12709             }
12710             shared.bind(el);
12711             shared.setFixedWidth(fixedWidth || 'auto');
12712             return shared.getSize(text);
12713         },
12714         
12715         /**
12716           * Destroy the TextMetrics instance created by {@link #measure}.
12717           */
12718          destroy: function(){
12719              var me = this;
12720              Ext.destroy(me.shared);
12721              me.shared = null;
12722          }
12723     },
12724     
12725     /**
12726      * Creates new TextMetrics.
12727      * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
12728      * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
12729      */
12730     constructor: function(bindTo, fixedWidth){
12731         var measure = this.measure = Ext.getBody().createChild({
12732             cls: 'x-textmetrics'
12733         });
12734         this.el = Ext.get(bindTo);
12735         
12736         measure.position('absolute');
12737         measure.setLeftTop(-1000, -1000);
12738         measure.hide();
12739
12740         if (fixedWidth) {
12741            measure.setWidth(fixedWidth);
12742         }
12743     },
12744     
12745     /**
12746      * Returns the size of the specified text based on the internal element's style and width properties
12747      * @param {String} text The text to measure
12748      * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
12749      */
12750     getSize: function(text){
12751         var measure = this.measure,
12752             size;
12753         
12754         measure.update(text);
12755         size = measure.getSize();
12756         measure.update('');
12757         return size;
12758     },
12759     
12760     /**
12761      * Binds this TextMetrics instance to a new element
12762      * @param {String/HTMLElement/Ext.Element} el The element or its ID.
12763      */
12764     bind: function(el){
12765         var me = this;
12766         
12767         me.el = Ext.get(el);
12768         me.measure.setStyle(
12769             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
12770         );
12771     },
12772     
12773     /**
12774      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
12775      * to set a fixed width in order to accurately measure the text height.
12776      * @param {Number} width The width to set on the element
12777      */
12778      setFixedWidth : function(width){
12779          this.measure.setWidth(width);
12780      },
12781      
12782      /**
12783       * Returns the measured width of the specified text
12784       * @param {String} text The text to measure
12785       * @return {Number} width The width in pixels
12786       */
12787      getWidth : function(text){
12788          this.measure.dom.style.width = 'auto';
12789          return this.getSize(text).width;
12790      },
12791      
12792      /**
12793       * Returns the measured height of the specified text
12794       * @param {String} text The text to measure
12795       * @return {Number} height The height in pixels
12796       */
12797      getHeight : function(text){
12798          return this.getSize(text).height;
12799      },
12800      
12801      /**
12802       * Destroy this instance
12803       */
12804      destroy: function(){
12805          var me = this;
12806          me.measure.remove();
12807          delete me.el;
12808          delete me.measure;
12809      }
12810 }, function(){
12811     Ext.Element.addMethods({
12812         /**
12813          * Returns the width in pixels of the passed text, or the width of the text in this Element.
12814          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
12815          * @param {Number} min (optional) The minumum value to return.
12816          * @param {Number} max (optional) The maximum value to return.
12817          * @return {Number} The text width in pixels.
12818          * @member Ext.Element
12819          */
12820         getTextWidth : function(text, min, max){
12821             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
12822         }
12823     });
12824 });
12825
12826 /**
12827  * @class Ext.layout.container.boxOverflow.Scroller
12828  * @extends Ext.layout.container.boxOverflow.None
12829  * @private
12830  */
12831 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
12832
12833     /* Begin Definitions */
12834
12835     extend: 'Ext.layout.container.boxOverflow.None',
12836     requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
12837     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
12838     mixins: {
12839         observable: 'Ext.util.Observable'
12840     },
12841     
12842     /* End Definitions */
12843
12844     /**
12845      * @cfg {Boolean} animateScroll
12846      * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
12847      */
12848     animateScroll: false,
12849
12850     /**
12851      * @cfg {Number} scrollIncrement
12852      * The number of pixels to scroll by on scroller click
12853      */
12854     scrollIncrement: 20,
12855
12856     /**
12857      * @cfg {Number} wheelIncrement
12858      * The number of pixels to increment on mouse wheel scrolling.
12859      */
12860     wheelIncrement: 10,
12861
12862     /**
12863      * @cfg {Number} scrollRepeatInterval
12864      * Number of milliseconds between each scroll while a scroller button is held down
12865      */
12866     scrollRepeatInterval: 60,
12867
12868     /**
12869      * @cfg {Number} scrollDuration
12870      * Number of milliseconds that each scroll animation lasts
12871      */
12872     scrollDuration: 400,
12873
12874     /**
12875      * @cfg {String} beforeCtCls
12876      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
12877      * which must always be present at the leftmost edge of the Container
12878      */
12879
12880     /**
12881      * @cfg {String} afterCtCls
12882      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
12883      * which must always be present at the rightmost edge of the Container
12884      */
12885
12886     /**
12887      * @cfg {String} [scrollerCls='x-box-scroller']
12888      * CSS class added to both scroller elements if enableScroll is used
12889      */
12890     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
12891
12892     /**
12893      * @cfg {String} beforeScrollerCls
12894      * CSS class added to the left scroller element if enableScroll is used
12895      */
12896
12897     /**
12898      * @cfg {String} afterScrollerCls
12899      * CSS class added to the right scroller element if enableScroll is used
12900      */
12901     
12902     constructor: function(layout, config) {
12903         this.layout = layout;
12904         Ext.apply(this, config || {});
12905         
12906         this.addEvents(
12907             /**
12908              * @event scroll
12909              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
12910              * @param {Number} newPosition The new position of the scroller
12911              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
12912              */
12913             'scroll'
12914         );
12915     },
12916     
12917     initCSSClasses: function() {
12918         var me = this,
12919         layout = me.layout;
12920
12921         if (!me.CSSinitialized) {
12922             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
12923             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
12924             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
12925             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
12926             me.CSSinitializes = true;
12927         }
12928     },
12929
12930     handleOverflow: function(calculations, targetSize) {
12931         var me = this,
12932             layout = me.layout,
12933             methodName = 'get' + layout.parallelPrefixCap,
12934             newSize = {};
12935
12936         me.initCSSClasses();
12937         me.callParent(arguments);
12938         this.createInnerElements();
12939         this.showScrollers();
12940         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
12941         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
12942         return { targetSize: newSize };
12943     },
12944
12945     /**
12946      * @private
12947      * Creates the beforeCt and afterCt elements if they have not already been created
12948      */
12949     createInnerElements: function() {
12950         var me = this,
12951             target = me.layout.getRenderTarget();
12952
12953         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
12954         //special items such as scrollers or dropdown menu triggers
12955         if (!me.beforeCt) {
12956             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
12957             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
12958             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
12959             me.createWheelListener();
12960         }
12961     },
12962
12963     /**
12964      * @private
12965      * Sets up an listener to scroll on the layout's innerCt mousewheel event
12966      */
12967     createWheelListener: function() {
12968         this.layout.innerCt.on({
12969             scope     : this,
12970             mousewheel: function(e) {
12971                 e.stopEvent();
12972
12973                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
12974             }
12975         });
12976     },
12977
12978     /**
12979      * @private
12980      */
12981     clearOverflow: function() {
12982         this.hideScrollers();
12983     },
12984
12985     /**
12986      * @private
12987      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
12988      * present. 
12989      */
12990     showScrollers: function() {
12991         this.createScrollers();
12992         this.beforeScroller.show();
12993         this.afterScroller.show();
12994         this.updateScrollButtons();
12995         
12996         this.layout.owner.addClsWithUI('scroller');
12997     },
12998
12999     /**
13000      * @private
13001      * Hides the scroller elements in the beforeCt and afterCt
13002      */
13003     hideScrollers: function() {
13004         if (this.beforeScroller != undefined) {
13005             this.beforeScroller.hide();
13006             this.afterScroller.hide();
13007             
13008             this.layout.owner.removeClsWithUI('scroller');
13009         }
13010     },
13011
13012     /**
13013      * @private
13014      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
13015      */
13016     createScrollers: function() {
13017         if (!this.beforeScroller && !this.afterScroller) {
13018             var before = this.beforeCt.createChild({
13019                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
13020             });
13021
13022             var after = this.afterCt.createChild({
13023                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
13024             });
13025
13026             before.addClsOnOver(this.beforeScrollerCls + '-hover');
13027             after.addClsOnOver(this.afterScrollerCls + '-hover');
13028
13029             before.setVisibilityMode(Ext.Element.DISPLAY);
13030             after.setVisibilityMode(Ext.Element.DISPLAY);
13031
13032             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
13033                 interval: this.scrollRepeatInterval,
13034                 handler : this.scrollLeft,
13035                 scope   : this
13036             });
13037
13038             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
13039                 interval: this.scrollRepeatInterval,
13040                 handler : this.scrollRight,
13041                 scope   : this
13042             });
13043
13044             /**
13045              * @property beforeScroller
13046              * @type Ext.Element
13047              * The left scroller element. Only created when needed.
13048              */
13049             this.beforeScroller = before;
13050
13051             /**
13052              * @property afterScroller
13053              * @type Ext.Element
13054              * The left scroller element. Only created when needed.
13055              */
13056             this.afterScroller = after;
13057         }
13058     },
13059
13060     /**
13061      * @private
13062      */
13063     destroy: function() {
13064         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
13065     },
13066
13067     /**
13068      * @private
13069      * Scrolls left or right by the number of pixels specified
13070      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
13071      */
13072     scrollBy: function(delta, animate) {
13073         this.scrollTo(this.getScrollPosition() + delta, animate);
13074     },
13075
13076     /**
13077      * @private
13078      * @return {Object} Object passed to scrollTo when scrolling
13079      */
13080     getScrollAnim: function() {
13081         return {
13082             duration: this.scrollDuration, 
13083             callback: this.updateScrollButtons, 
13084             scope   : this
13085         };
13086     },
13087
13088     /**
13089      * @private
13090      * Enables or disables each scroller button based on the current scroll position
13091      */
13092     updateScrollButtons: function() {
13093         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
13094             return;
13095         }
13096
13097         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
13098             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
13099             beforeCls  = this.beforeScrollerCls + '-disabled',
13100             afterCls   = this.afterScrollerCls  + '-disabled';
13101         
13102         this.beforeScroller[beforeMeth](beforeCls);
13103         this.afterScroller[afterMeth](afterCls);
13104         this.scrolling = false;
13105     },
13106
13107     /**
13108      * @private
13109      * Returns true if the innerCt scroll is already at its left-most point
13110      * @return {Boolean} True if already at furthest left point
13111      */
13112     atExtremeBefore: function() {
13113         return this.getScrollPosition() === 0;
13114     },
13115
13116     /**
13117      * @private
13118      * Scrolls to the left by the configured amount
13119      */
13120     scrollLeft: function() {
13121         this.scrollBy(-this.scrollIncrement, false);
13122     },
13123
13124     /**
13125      * @private
13126      * Scrolls to the right by the configured amount
13127      */
13128     scrollRight: function() {
13129         this.scrollBy(this.scrollIncrement, false);
13130     },
13131
13132     /**
13133      * Returns the current scroll position of the innerCt element
13134      * @return {Number} The current scroll position
13135      */
13136     getScrollPosition: function(){
13137         var layout = this.layout;
13138         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
13139     },
13140
13141     /**
13142      * @private
13143      * Returns the maximum value we can scrollTo
13144      * @return {Number} The max scroll value
13145      */
13146     getMaxScrollPosition: function() {
13147         var layout = this.layout;
13148         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
13149     },
13150
13151     /**
13152      * @private
13153      * Returns true if the innerCt scroll is already at its right-most point
13154      * @return {Boolean} True if already at furthest right point
13155      */
13156     atExtremeAfter: function() {
13157         return this.getScrollPosition() >= this.getMaxScrollPosition();
13158     },
13159
13160     /**
13161      * @private
13162      * Scrolls to the given position. Performs bounds checking.
13163      * @param {Number} position The position to scroll to. This is constrained.
13164      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
13165      */
13166     scrollTo: function(position, animate) {
13167         var me = this,
13168             layout = me.layout,
13169             oldPosition = me.getScrollPosition(),
13170             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
13171
13172         if (newPosition != oldPosition && !me.scrolling) {
13173             if (animate == undefined) {
13174                 animate = me.animateScroll;
13175             }
13176
13177             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
13178             if (animate) {
13179                 me.scrolling = true;
13180             } else {
13181                 me.scrolling = false;
13182                 me.updateScrollButtons();
13183             }
13184             
13185             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
13186         }
13187     },
13188
13189     /**
13190      * Scrolls to the given component.
13191      * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id 
13192      * or a reference to the component itself.
13193      * @param {Boolean} animate True to animate the scrolling
13194      */
13195     scrollToItem: function(item, animate) {
13196         var me = this,
13197             layout = me.layout,
13198             visibility,
13199             box,
13200             newPos;
13201
13202         item = me.getItem(item);
13203         if (item != undefined) {
13204             visibility = this.getItemVisibility(item);
13205             if (!visibility.fullyVisible) {
13206                 box  = item.getBox(true, true);
13207                 newPos = box[layout.parallelPosition];
13208                 if (visibility.hiddenEnd) {
13209                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
13210                 }
13211                 this.scrollTo(newPos, animate);
13212             }
13213         }
13214     },
13215
13216     /**
13217      * @private
13218      * For a given item in the container, return an object with information on whether the item is visible
13219      * with the current innerCt scroll value.
13220      * @param {Ext.Component} item The item
13221      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
13222      */
13223     getItemVisibility: function(item) {
13224         var me          = this,
13225             box         = me.getItem(item).getBox(true, true),
13226             layout      = me.layout,
13227             itemStart   = box[layout.parallelPosition],
13228             itemEnd     = itemStart + box[layout.parallelPrefix],
13229             scrollStart = me.getScrollPosition(),
13230             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
13231
13232         return {
13233             hiddenStart : itemStart < scrollStart,
13234             hiddenEnd   : itemEnd > scrollEnd,
13235             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
13236         };
13237     }
13238 });
13239 /**
13240  * @class Ext.util.Offset
13241  * @ignore
13242  */
13243 Ext.define('Ext.util.Offset', {
13244
13245     /* Begin Definitions */
13246
13247     statics: {
13248         fromObject: function(obj) {
13249             return new this(obj.x, obj.y);
13250         }
13251     },
13252
13253     /* End Definitions */
13254
13255     constructor: function(x, y) {
13256         this.x = (x != null && !isNaN(x)) ? x : 0;
13257         this.y = (y != null && !isNaN(y)) ? y : 0;
13258
13259         return this;
13260     },
13261
13262     copy: function() {
13263         return new Ext.util.Offset(this.x, this.y);
13264     },
13265
13266     copyFrom: function(p) {
13267         this.x = p.x;
13268         this.y = p.y;
13269     },
13270
13271     toString: function() {
13272         return "Offset[" + this.x + "," + this.y + "]";
13273     },
13274
13275     equals: function(offset) {
13276         //<debug>
13277         if(!(offset instanceof this.statics())) {
13278             Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
13279         }
13280         //</debug>
13281
13282         return (this.x == offset.x && this.y == offset.y);
13283     },
13284
13285     round: function(to) {
13286         if (!isNaN(to)) {
13287             var factor = Math.pow(10, to);
13288             this.x = Math.round(this.x * factor) / factor;
13289             this.y = Math.round(this.y * factor) / factor;
13290         } else {
13291             this.x = Math.round(this.x);
13292             this.y = Math.round(this.y);
13293         }
13294     },
13295
13296     isZero: function() {
13297         return this.x == 0 && this.y == 0;
13298     }
13299 });
13300
13301 /**
13302  * @class Ext.util.KeyNav
13303  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
13304  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
13305  * way to implement custom navigation schemes for any UI component.</p>
13306  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
13307  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
13308  <pre><code>
13309 var nav = new Ext.util.KeyNav("my-element", {
13310     "left" : function(e){
13311         this.moveLeft(e.ctrlKey);
13312     },
13313     "right" : function(e){
13314         this.moveRight(e.ctrlKey);
13315     },
13316     "enter" : function(e){
13317         this.save();
13318     },
13319     scope : this
13320 });
13321 </code></pre>
13322  */
13323 Ext.define('Ext.util.KeyNav', {
13324     
13325     alternateClassName: 'Ext.KeyNav',
13326     
13327     requires: ['Ext.util.KeyMap'],
13328     
13329     statics: {
13330         keyOptions: {
13331             left: 37,
13332             right: 39,
13333             up: 38,
13334             down: 40,
13335             space: 32,
13336             pageUp: 33,
13337             pageDown: 34,
13338             del: 46,
13339             backspace: 8,
13340             home: 36,
13341             end: 35,
13342             enter: 13,
13343             esc: 27,
13344             tab: 9
13345         }
13346     },
13347
13348     /**
13349      * Creates new KeyNav.
13350      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
13351      * @param {Object} config The config
13352      */
13353     constructor: function(el, config){
13354         this.setConfig(el, config || {});
13355     },
13356     
13357     /**
13358      * Sets up a configuration for the KeyNav.
13359      * @private
13360      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
13361      * @param {Object} config A configuration object as specified in the constructor.
13362      */
13363     setConfig: function(el, config) {
13364         if (this.map) {
13365             this.map.destroy();
13366         }
13367         
13368         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
13369             keys = Ext.util.KeyNav.keyOptions,
13370             scope = config.scope || this,
13371             key;
13372         
13373         this.map = map;
13374         for (key in keys) {
13375             if (keys.hasOwnProperty(key)) {
13376                 if (config[key]) {
13377                     map.addBinding({
13378                         scope: scope,
13379                         key: keys[key],
13380                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
13381                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
13382                     });
13383                 }
13384             }
13385         }
13386         
13387         map.disable();
13388         if (!config.disabled) {
13389             map.enable();
13390         }
13391     },
13392     
13393     /**
13394      * Method for filtering out the map argument
13395      * @private
13396      * @param {Ext.util.KeyMap} map
13397      * @param {Ext.EventObject} event
13398      * @param {Object} options Contains the handler to call
13399      */
13400     handleEvent: function(map, event, handler){
13401         return handler.call(this, event);
13402     },
13403     
13404     /**
13405      * @cfg {Boolean} disabled
13406      * True to disable this KeyNav instance.
13407      */
13408     disabled: false,
13409     
13410     /**
13411      * @cfg {String} defaultEventAction
13412      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
13413      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
13414      * {@link Ext.EventObject#stopPropagation}.
13415      */
13416     defaultEventAction: "stopEvent",
13417     
13418     /**
13419      * @cfg {Boolean} forceKeyDown
13420      * Handle the keydown event instead of keypress.  KeyNav automatically does this for IE since
13421      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
13422      * handle keydown instead of keypress.
13423      */
13424     forceKeyDown: false,
13425     
13426     /**
13427      * Destroy this KeyNav (this is the same as calling disable).
13428      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
13429      */
13430     destroy: function(removeEl){
13431         this.map.destroy(removeEl);
13432         delete this.map;
13433     },
13434
13435     /**
13436      * Enable this KeyNav
13437      */
13438     enable: function() {
13439         this.map.enable();
13440         this.disabled = false;
13441     },
13442
13443     /**
13444      * Disable this KeyNav
13445      */
13446     disable: function() {
13447         this.map.disable();
13448         this.disabled = true;
13449     },
13450     
13451     /**
13452      * Convenience function for setting disabled/enabled by boolean.
13453      * @param {Boolean} disabled
13454      */
13455     setDisabled : function(disabled){
13456         this.map.setDisabled(disabled);
13457         this.disabled = disabled;
13458     },
13459     
13460     /**
13461      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
13462      * as well as the useKeyDown option on the EventManager.
13463      * @return {String} The type of event to listen for.
13464      */
13465     getKeyEvent: function(forceKeyDown){
13466         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
13467     }
13468 });
13469
13470 /**
13471  * @class Ext.fx.Queue
13472  * Animation Queue mixin to handle chaining and queueing by target.
13473  * @private
13474  */
13475
13476 Ext.define('Ext.fx.Queue', {
13477
13478     requires: ['Ext.util.HashMap'],
13479
13480     constructor: function() {
13481         this.targets = Ext.create('Ext.util.HashMap');
13482         this.fxQueue = {};
13483     },
13484
13485     // @private
13486     getFxDefaults: function(targetId) {
13487         var target = this.targets.get(targetId);
13488         if (target) {
13489             return target.fxDefaults;
13490         }
13491         return {};
13492     },
13493
13494     // @private
13495     setFxDefaults: function(targetId, obj) {
13496         var target = this.targets.get(targetId);
13497         if (target) {
13498             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
13499         }
13500     },
13501
13502     // @private
13503     stopAnimation: function(targetId) {
13504         var me = this,
13505             queue = me.getFxQueue(targetId),
13506             ln = queue.length;
13507         while (ln) {
13508             queue[ln - 1].end();
13509             ln--;
13510         }
13511     },
13512
13513     /**
13514      * @private
13515      * Returns current animation object if the element has any effects actively running or queued, else returns false.
13516      */
13517     getActiveAnimation: function(targetId) {
13518         var queue = this.getFxQueue(targetId);
13519         return (queue && !!queue.length) ? queue[0] : false;
13520     },
13521
13522     // @private
13523     hasFxBlock: function(targetId) {
13524         var queue = this.getFxQueue(targetId);
13525         return queue && queue[0] && queue[0].block;
13526     },
13527
13528     // @private get fx queue for passed target, create if needed.
13529     getFxQueue: function(targetId) {
13530         if (!targetId) {
13531             return false;
13532         }
13533         var me = this,
13534             queue = me.fxQueue[targetId],
13535             target = me.targets.get(targetId);
13536
13537         if (!target) {
13538             return false;
13539         }
13540
13541         if (!queue) {
13542             me.fxQueue[targetId] = [];
13543             // GarbageCollector will need to clean up Elements since they aren't currently observable
13544             if (target.type != 'element') {
13545                 target.target.on('destroy', function() {
13546                     me.fxQueue[targetId] = [];
13547                 });
13548             }
13549         }
13550         return me.fxQueue[targetId];
13551     },
13552
13553     // @private
13554     queueFx: function(anim) {
13555         var me = this,
13556             target = anim.target,
13557             queue, ln;
13558
13559         if (!target) {
13560             return;
13561         }
13562
13563         queue = me.getFxQueue(target.getId());
13564         ln = queue.length;
13565
13566         if (ln) {
13567             if (anim.concurrent) {
13568                 anim.paused = false;
13569             }
13570             else {
13571                 queue[ln - 1].on('afteranimate', function() {
13572                     anim.paused = false;
13573                 });
13574             }
13575         }
13576         else {
13577             anim.paused = false;
13578         }
13579         anim.on('afteranimate', function() {
13580             Ext.Array.remove(queue, anim);
13581             if (anim.remove) {
13582                 if (target.type == 'element') {
13583                     var el = Ext.get(target.id);
13584                     if (el) {
13585                         el.remove();
13586                     }
13587                 }
13588             }
13589         }, this);
13590         queue.push(anim);
13591     }
13592 });
13593 /**
13594  * @class Ext.fx.target.Target
13595
13596 This class specifies a generic target for an animation. It provides a wrapper around a
13597 series of different types of objects to allow for a generic animation API.
13598 A target can be a single object or a Composite object containing other objects that are 
13599 to be animated. This class and it's subclasses are generally not created directly, the 
13600 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
13601 the instance to be animated.
13602
13603 The following types of objects can be animated:
13604
13605 - {@link Ext.fx.target.Component Components}
13606 - {@link Ext.fx.target.Element Elements}
13607 - {@link Ext.fx.target.Sprite Sprites}
13608
13609  * @markdown
13610  * @abstract
13611  */
13612 Ext.define('Ext.fx.target.Target', {
13613
13614     isAnimTarget: true,
13615
13616     /**
13617      * Creates new Target.
13618      * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
13619      */
13620     constructor: function(target) {
13621         this.target = target;
13622         this.id = this.getId();
13623     },
13624     
13625     getId: function() {
13626         return this.target.id;
13627     }
13628 });
13629
13630 /**
13631  * @class Ext.fx.target.Sprite
13632  * @extends Ext.fx.target.Target
13633
13634 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
13635 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
13636 and the appropriate target will be created.
13637
13638  * @markdown
13639  */
13640
13641 Ext.define('Ext.fx.target.Sprite', {
13642
13643     /* Begin Definitions */
13644
13645     extend: 'Ext.fx.target.Target',
13646
13647     /* End Definitions */
13648
13649     type: 'draw',
13650
13651     getFromPrim: function(sprite, attr) {
13652         var o;
13653         if (attr == 'translate') {
13654             o = {
13655                 x: sprite.attr.translation.x || 0,
13656                 y: sprite.attr.translation.y || 0
13657             };
13658         }
13659         else if (attr == 'rotate') {
13660             o = {
13661                 degrees: sprite.attr.rotation.degrees || 0,
13662                 x: sprite.attr.rotation.x,
13663                 y: sprite.attr.rotation.y
13664             };
13665         }
13666         else {
13667             o = sprite.attr[attr];
13668         }
13669         return o;
13670     },
13671
13672     getAttr: function(attr, val) {
13673         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
13674     },
13675
13676     setAttr: function(targetData) {
13677         var ln = targetData.length,
13678             spriteArr = [],
13679             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
13680         for (i = 0; i < ln; i++) {
13681             attrs = targetData[i].attrs;
13682             for (attr in attrs) {
13683                 attrArr = attrs[attr];
13684                 ln2 = attrArr.length;
13685                 for (j = 0; j < ln2; j++) {
13686                     spritePtr = attrArr[j][0];
13687                     attPtr = attrArr[j][1];
13688                     if (attr === 'translate') {
13689                         value = {
13690                             x: attPtr.x,
13691                             y: attPtr.y
13692                         };
13693                     }
13694                     else if (attr === 'rotate') {
13695                         x = attPtr.x;
13696                         if (isNaN(x)) {
13697                             x = null;
13698                         }
13699                         y = attPtr.y;
13700                         if (isNaN(y)) {
13701                             y = null;
13702                         }
13703                         value = {
13704                             degrees: attPtr.degrees,
13705                             x: x,
13706                             y: y
13707                         };
13708                     }
13709                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
13710                         value = parseFloat(attPtr);
13711                     }
13712                     else {
13713                         value = attPtr;
13714                     }
13715                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
13716                     if (idx == -1) {
13717                         spriteArr.push([spritePtr, {}]);
13718                         idx = spriteArr.length - 1;
13719                     }
13720                     spriteArr[idx][1][attr] = value;
13721                 }
13722             }
13723         }
13724         ln = spriteArr.length;
13725         for (i = 0; i < ln; i++) {
13726             spritePtr = spriteArr[i];
13727             spritePtr[0].setAttributes(spritePtr[1]);
13728         }
13729         this.target.redraw();
13730     }
13731 });
13732
13733 /**
13734  * @class Ext.fx.target.CompositeSprite
13735  * @extends Ext.fx.target.Sprite
13736
13737 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
13738 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
13739 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
13740 and the appropriate target will be created.
13741
13742  * @markdown
13743  */
13744
13745 Ext.define('Ext.fx.target.CompositeSprite', {
13746
13747     /* Begin Definitions */
13748
13749     extend: 'Ext.fx.target.Sprite',
13750
13751     /* End Definitions */
13752
13753     getAttr: function(attr, val) {
13754         var out = [],
13755             target = this.target;
13756         target.each(function(sprite) {
13757             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
13758         }, this);
13759         return out;
13760     }
13761 });
13762
13763 /**
13764  * @class Ext.fx.target.Component
13765  * @extends Ext.fx.target.Target
13766  * 
13767  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
13768  * created directly, the {@link Ext.Component} will be passed to the animation and
13769  * and the appropriate target will be created.
13770  */
13771 Ext.define('Ext.fx.target.Component', {
13772
13773     /* Begin Definitions */
13774    
13775     extend: 'Ext.fx.target.Target',
13776     
13777     /* End Definitions */
13778
13779     type: 'component',
13780
13781     // Methods to call to retrieve unspecified "from" values from a target Component
13782     getPropMethod: {
13783         top: function() {
13784             return this.getPosition(true)[1];
13785         },
13786         left: function() {
13787             return this.getPosition(true)[0];
13788         },
13789         x: function() {
13790             return this.getPosition()[0];
13791         },
13792         y: function() {
13793             return this.getPosition()[1];
13794         },
13795         height: function() {
13796             return this.getHeight();
13797         },
13798         width: function() {
13799             return this.getWidth();
13800         },
13801         opacity: function() {
13802             return this.el.getStyle('opacity');
13803         }
13804     },
13805
13806     compMethod: {
13807         top: 'setPosition',
13808         left: 'setPosition',
13809         x: 'setPagePosition',
13810         y: 'setPagePosition',
13811         height: 'setSize',
13812         width: 'setSize',
13813         opacity: 'setOpacity'
13814     },
13815
13816     // Read the named attribute from the target Component. Use the defined getter for the attribute
13817     getAttr: function(attr, val) {
13818         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
13819     },
13820
13821     setAttr: function(targetData, isFirstFrame, isLastFrame) {
13822         var me = this,
13823             target = me.target,
13824             ln = targetData.length,
13825             attrs, attr, o, i, j, meth, targets, left, top, w, h;
13826         for (i = 0; i < ln; i++) {
13827             attrs = targetData[i].attrs;
13828             for (attr in attrs) {
13829                 targets = attrs[attr].length;
13830                 meth = {
13831                     setPosition: {},
13832                     setPagePosition: {},
13833                     setSize: {},
13834                     setOpacity: {}
13835                 };
13836                 for (j = 0; j < targets; j++) {
13837                     o = attrs[attr][j];
13838                     // We REALLY want a single function call, so push these down to merge them: eg
13839                     // meth.setPagePosition.target = <targetComponent>
13840                     // meth.setPagePosition['x'] = 100
13841                     // meth.setPagePosition['y'] = 100
13842                     meth[me.compMethod[attr]].target = o[0];
13843                     meth[me.compMethod[attr]][attr] = o[1];
13844                 }
13845                 if (meth.setPosition.target) {
13846                     o = meth.setPosition;
13847                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
13848                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
13849                     o.target.setPosition(left, top);
13850                 }
13851                 if (meth.setPagePosition.target) {
13852                     o = meth.setPagePosition;
13853                     o.target.setPagePosition(o.x, o.y);
13854                 }
13855                 if (meth.setSize.target && meth.setSize.target.el) {
13856                     o = meth.setSize;
13857                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
13858                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
13859                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
13860
13861                     // Only set the size of the Component on the last frame, or if the animation was
13862                     // configured with dynamic: true.
13863                     // In other cases, we just set the target element size.
13864                     // This will result in either clipping if animating a reduction in size, or the revealing of
13865                     // the inner elements of the Component if animating an increase in size.
13866                     // Component's animate function initially resizes to the larger size before resizing the
13867                     // outer element to clip the contents.
13868                     if (isLastFrame || me.dynamic) {
13869                         o.target.componentLayout.childrenChanged = true;
13870
13871                         // Flag if we are being called by an animating layout: use setCalculatedSize
13872                         if (me.layoutAnimation) {
13873                             o.target.setCalculatedSize(w, h);
13874                         } else {
13875                             o.target.setSize(w, h);
13876                         }
13877                     }
13878                     else {
13879                         o.target.el.setSize(w, h);
13880                     }
13881                 }
13882                 if (meth.setOpacity.target) {
13883                     o = meth.setOpacity;
13884                     o.target.el.setStyle('opacity', o.opacity);
13885                 }
13886             }
13887         }
13888     }
13889 });
13890
13891 /**
13892  * @class Ext.fx.CubicBezier
13893  * @ignore
13894  */
13895 Ext.define('Ext.fx.CubicBezier', {
13896
13897     /* Begin Definitions */
13898
13899     singleton: true,
13900
13901     /* End Definitions */
13902
13903     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
13904         var cx = 3 * p1x,
13905             bx = 3 * (p2x - p1x) - cx,
13906             ax = 1 - cx - bx,
13907             cy = 3 * p1y,
13908             by = 3 * (p2y - p1y) - cy,
13909             ay = 1 - cy - by;
13910         function sampleCurveX(t) {
13911             return ((ax * t + bx) * t + cx) * t;
13912         }
13913         function solve(x, epsilon) {
13914             var t = solveCurveX(x, epsilon);
13915             return ((ay * t + by) * t + cy) * t;
13916         }
13917         function solveCurveX(x, epsilon) {
13918             var t0, t1, t2, x2, d2, i;
13919             for (t2 = x, i = 0; i < 8; i++) {
13920                 x2 = sampleCurveX(t2) - x;
13921                 if (Math.abs(x2) < epsilon) {
13922                     return t2;
13923                 }
13924                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
13925                 if (Math.abs(d2) < 1e-6) {
13926                     break;
13927                 }
13928                 t2 = t2 - x2 / d2;
13929             }
13930             t0 = 0;
13931             t1 = 1;
13932             t2 = x;
13933             if (t2 < t0) {
13934                 return t0;
13935             }
13936             if (t2 > t1) {
13937                 return t1;
13938             }
13939             while (t0 < t1) {
13940                 x2 = sampleCurveX(t2);
13941                 if (Math.abs(x2 - x) < epsilon) {
13942                     return t2;
13943                 }
13944                 if (x > x2) {
13945                     t0 = t2;
13946                 } else {
13947                     t1 = t2;
13948                 }
13949                 t2 = (t1 - t0) / 2 + t0;
13950             }
13951             return t2;
13952         }
13953         return solve(t, 1 / (200 * duration));
13954     },
13955
13956     cubicBezier: function(x1, y1, x2, y2) {
13957         var fn = function(pos) {
13958             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
13959         };
13960         fn.toCSS3 = function() {
13961             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
13962         };
13963         fn.reverse = function() {
13964             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
13965         };
13966         return fn;
13967     }
13968 });
13969 /**
13970  * Represents an RGB color and provides helper functions get
13971  * color components in HSL color space.
13972  */
13973 Ext.define('Ext.draw.Color', {
13974
13975     /* Begin Definitions */
13976
13977     /* End Definitions */
13978
13979     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
13980     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
13981     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*/,
13982
13983     /**
13984      * @cfg {Number} lightnessFactor
13985      *
13986      * The default factor to compute the lighter or darker color. Defaults to 0.2.
13987      */
13988     lightnessFactor: 0.2,
13989
13990     /**
13991      * Creates new Color.
13992      * @param {Number} red Red component (0..255)
13993      * @param {Number} green Green component (0..255)
13994      * @param {Number} blue Blue component (0..255)
13995      */
13996     constructor : function(red, green, blue) {
13997         var me = this,
13998             clamp = Ext.Number.constrain;
13999         me.r = clamp(red, 0, 255);
14000         me.g = clamp(green, 0, 255);
14001         me.b = clamp(blue, 0, 255);
14002     },
14003
14004     /**
14005      * Get the red component of the color, in the range 0..255.
14006      * @return {Number}
14007      */
14008     getRed: function() {
14009         return this.r;
14010     },
14011
14012     /**
14013      * Get the green component of the color, in the range 0..255.
14014      * @return {Number}
14015      */
14016     getGreen: function() {
14017         return this.g;
14018     },
14019
14020     /**
14021      * Get the blue component of the color, in the range 0..255.
14022      * @return {Number}
14023      */
14024     getBlue: function() {
14025         return this.b;
14026     },
14027
14028     /**
14029      * Get the RGB values.
14030      * @return {Number[]}
14031      */
14032     getRGB: function() {
14033         var me = this;
14034         return [me.r, me.g, me.b];
14035     },
14036
14037     /**
14038      * Get the equivalent HSL components of the color.
14039      * @return {Number[]}
14040      */
14041     getHSL: function() {
14042         var me = this,
14043             r = me.r / 255,
14044             g = me.g / 255,
14045             b = me.b / 255,
14046             max = Math.max(r, g, b),
14047             min = Math.min(r, g, b),
14048             delta = max - min,
14049             h,
14050             s = 0,
14051             l = 0.5 * (max + min);
14052
14053         // min==max means achromatic (hue is undefined)
14054         if (min != max) {
14055             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
14056             if (r == max) {
14057                 h = 60 * (g - b) / delta;
14058             } else if (g == max) {
14059                 h = 120 + 60 * (b - r) / delta;
14060             } else {
14061                 h = 240 + 60 * (r - g) / delta;
14062             }
14063             if (h < 0) {
14064                 h += 360;
14065             }
14066             if (h >= 360) {
14067                 h -= 360;
14068             }
14069         }
14070         return [h, s, l];
14071     },
14072
14073     /**
14074      * Return a new color that is lighter than this color.
14075      * @param {Number} factor Lighter factor (0..1), default to 0.2
14076      * @return Ext.draw.Color
14077      */
14078     getLighter: function(factor) {
14079         var hsl = this.getHSL();
14080         factor = factor || this.lightnessFactor;
14081         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
14082         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
14083     },
14084
14085     /**
14086      * Return a new color that is darker than this color.
14087      * @param {Number} factor Darker factor (0..1), default to 0.2
14088      * @return Ext.draw.Color
14089      */
14090     getDarker: function(factor) {
14091         factor = factor || this.lightnessFactor;
14092         return this.getLighter(-factor);
14093     },
14094
14095     /**
14096      * Return the color in the hex format, i.e. '#rrggbb'.
14097      * @return {String}
14098      */
14099     toString: function() {
14100         var me = this,
14101             round = Math.round,
14102             r = round(me.r).toString(16),
14103             g = round(me.g).toString(16),
14104             b = round(me.b).toString(16);
14105         r = (r.length == 1) ? '0' + r : r;
14106         g = (g.length == 1) ? '0' + g : g;
14107         b = (b.length == 1) ? '0' + b : b;
14108         return ['#', r, g, b].join('');
14109     },
14110
14111     /**
14112      * Convert a color to hexadecimal format.
14113      *
14114      * **Note:** This method is both static and instance.
14115      *
14116      * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
14117      * Can also be an Array, in this case the function handles the first member.
14118      * @returns {String} The color in hexadecimal format.
14119      * @static
14120      */
14121     toHex: function(color) {
14122         if (Ext.isArray(color)) {
14123             color = color[0];
14124         }
14125         if (!Ext.isString(color)) {
14126             return '';
14127         }
14128         if (color.substr(0, 1) === '#') {
14129             return color;
14130         }
14131         var digits = this.colorToHexRe.exec(color);
14132
14133         if (Ext.isArray(digits)) {
14134             var red = parseInt(digits[2], 10),
14135                 green = parseInt(digits[3], 10),
14136                 blue = parseInt(digits[4], 10),
14137                 rgb = blue | (green << 8) | (red << 16);
14138             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
14139         }
14140         else {
14141             return '';
14142         }
14143     },
14144
14145     /**
14146      * Parse the string and create a new color.
14147      *
14148      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
14149      *
14150      * If the string is not recognized, an undefined will be returned instead.
14151      *
14152      * **Note:** This method is both static and instance.
14153      *
14154      * @param {String} str Color in string.
14155      * @returns Ext.draw.Color
14156      * @static
14157      */
14158     fromString: function(str) {
14159         var values, r, g, b,
14160             parse = parseInt;
14161
14162         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
14163             values = str.match(this.hexRe);
14164             if (values) {
14165                 r = parse(values[1], 16) >> 0;
14166                 g = parse(values[2], 16) >> 0;
14167                 b = parse(values[3], 16) >> 0;
14168                 if (str.length == 4) {
14169                     r += (r * 16);
14170                     g += (g * 16);
14171                     b += (b * 16);
14172                 }
14173             }
14174         }
14175         else {
14176             values = str.match(this.rgbRe);
14177             if (values) {
14178                 r = values[1];
14179                 g = values[2];
14180                 b = values[3];
14181             }
14182         }
14183
14184         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
14185     },
14186
14187     /**
14188      * Returns the gray value (0 to 255) of the color.
14189      *
14190      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
14191      *
14192      * @returns {Number}
14193      */
14194     getGrayscale: function() {
14195         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
14196         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
14197     },
14198
14199     /**
14200      * Create a new color based on the specified HSL values.
14201      *
14202      * **Note:** This method is both static and instance.
14203      *
14204      * @param {Number} h Hue component (0..359)
14205      * @param {Number} s Saturation component (0..1)
14206      * @param {Number} l Lightness component (0..1)
14207      * @returns Ext.draw.Color
14208      * @static
14209      */
14210     fromHSL: function(h, s, l) {
14211         var C, X, m, i, rgb = [],
14212             abs = Math.abs,
14213             floor = Math.floor;
14214
14215         if (s == 0 || h == null) {
14216             // achromatic
14217             rgb = [l, l, l];
14218         }
14219         else {
14220             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
14221             // C is the chroma
14222             // X is the second largest component
14223             // m is the lightness adjustment
14224             h /= 60;
14225             C = s * (1 - abs(2 * l - 1));
14226             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
14227             m = l - C / 2;
14228             switch (floor(h)) {
14229                 case 0:
14230                     rgb = [C, X, 0];
14231                     break;
14232                 case 1:
14233                     rgb = [X, C, 0];
14234                     break;
14235                 case 2:
14236                     rgb = [0, C, X];
14237                     break;
14238                 case 3:
14239                     rgb = [0, X, C];
14240                     break;
14241                 case 4:
14242                     rgb = [X, 0, C];
14243                     break;
14244                 case 5:
14245                     rgb = [C, 0, X];
14246                     break;
14247             }
14248             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
14249         }
14250         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
14251     }
14252 }, function() {
14253     var prototype = this.prototype;
14254
14255     //These functions are both static and instance. TODO: find a more elegant way of copying them
14256     this.addStatics({
14257         fromHSL: function() {
14258             return prototype.fromHSL.apply(prototype, arguments);
14259         },
14260         fromString: function() {
14261             return prototype.fromString.apply(prototype, arguments);
14262         },
14263         toHex: function() {
14264             return prototype.toHex.apply(prototype, arguments);
14265         }
14266     });
14267 });
14268
14269 /**
14270  * @class Ext.dd.StatusProxy
14271  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
14272  * default drag proxy used by all Ext.dd components.
14273  */
14274 Ext.define('Ext.dd.StatusProxy', {
14275     animRepair: false,
14276
14277     /**
14278      * Creates new StatusProxy.
14279      * @param {Object} config (optional) Config object.
14280      */
14281     constructor: function(config){
14282         Ext.apply(this, config);
14283         this.id = this.id || Ext.id();
14284         this.proxy = Ext.createWidget('component', {
14285             floating: true,
14286             stateful: false,
14287             id: this.id,
14288             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
14289                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
14290             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
14291             shadow: !config || config.shadow !== false,
14292             renderTo: document.body
14293         });
14294
14295         this.el = this.proxy.el;
14296         this.el.show();
14297         this.el.setVisibilityMode(Ext.Element.VISIBILITY);
14298         this.el.hide();
14299
14300         this.ghost = Ext.get(this.el.dom.childNodes[1]);
14301         this.dropStatus = this.dropNotAllowed;
14302     },
14303     /**
14304      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
14305      * The CSS class to apply to the status element when drop is allowed.
14306      */
14307     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
14308     /**
14309      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
14310      * The CSS class to apply to the status element when drop is not allowed.
14311      */
14312     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
14313
14314     /**
14315      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
14316      * over the current target element.
14317      * @param {String} cssClass The css class for the new drop status indicator image
14318      */
14319     setStatus : function(cssClass){
14320         cssClass = cssClass || this.dropNotAllowed;
14321         if(this.dropStatus != cssClass){
14322             this.el.replaceCls(this.dropStatus, cssClass);
14323             this.dropStatus = cssClass;
14324         }
14325     },
14326
14327     /**
14328      * Resets the status indicator to the default dropNotAllowed value
14329      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
14330      */
14331     reset : function(clearGhost){
14332         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
14333         this.dropStatus = this.dropNotAllowed;
14334         if(clearGhost){
14335             this.ghost.update("");
14336         }
14337     },
14338
14339     /**
14340      * Updates the contents of the ghost element
14341      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
14342      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
14343      */
14344     update : function(html){
14345         if(typeof html == "string"){
14346             this.ghost.update(html);
14347         }else{
14348             this.ghost.update("");
14349             html.style.margin = "0";
14350             this.ghost.dom.appendChild(html);
14351         }
14352         var el = this.ghost.dom.firstChild;
14353         if(el){
14354             Ext.fly(el).setStyle('float', 'none');
14355         }
14356     },
14357
14358     /**
14359      * Returns the underlying proxy {@link Ext.Layer}
14360      * @return {Ext.Layer} el
14361     */
14362     getEl : function(){
14363         return this.el;
14364     },
14365
14366     /**
14367      * Returns the ghost element
14368      * @return {Ext.Element} el
14369      */
14370     getGhost : function(){
14371         return this.ghost;
14372     },
14373
14374     /**
14375      * Hides the proxy
14376      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
14377      */
14378     hide : function(clear) {
14379         this.proxy.hide();
14380         if (clear) {
14381             this.reset(true);
14382         }
14383     },
14384
14385     /**
14386      * Stops the repair animation if it's currently running
14387      */
14388     stop : function(){
14389         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
14390             this.anim.stop();
14391         }
14392     },
14393
14394     /**
14395      * Displays this proxy
14396      */
14397     show : function() {
14398         this.proxy.show();
14399         this.proxy.toFront();
14400     },
14401
14402     /**
14403      * Force the Layer to sync its shadow and shim positions to the element
14404      */
14405     sync : function(){
14406         this.proxy.el.sync();
14407     },
14408
14409     /**
14410      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
14411      * invalid drop operation by the item being dragged.
14412      * @param {Number[]} xy The XY position of the element ([x, y])
14413      * @param {Function} callback The function to call after the repair is complete.
14414      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
14415      */
14416     repair : function(xy, callback, scope){
14417         this.callback = callback;
14418         this.scope = scope;
14419         if (xy && this.animRepair !== false) {
14420             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
14421             this.el.hideUnders(true);
14422             this.anim = this.el.animate({
14423                 duration: this.repairDuration || 500,
14424                 easing: 'ease-out',
14425                 to: {
14426                     x: xy[0],
14427                     y: xy[1]
14428                 },
14429                 stopAnimation: true,
14430                 callback: this.afterRepair,
14431                 scope: this
14432             });
14433         } else {
14434             this.afterRepair();
14435         }
14436     },
14437
14438     // private
14439     afterRepair : function(){
14440         this.hide(true);
14441         if(typeof this.callback == "function"){
14442             this.callback.call(this.scope || this);
14443         }
14444         this.callback = null;
14445         this.scope = null;
14446     },
14447
14448     destroy: function(){
14449         Ext.destroy(this.ghost, this.proxy, this.el);
14450     }
14451 });
14452 /**
14453  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
14454  * is primarily used internally for the Panel's drag drop implementation, and
14455  * should never need to be created directly.
14456  * @private
14457  */
14458 Ext.define('Ext.panel.Proxy', {
14459
14460     alternateClassName: 'Ext.dd.PanelProxy',
14461
14462     /**
14463      * Creates new panel proxy.
14464      * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
14465      * @param {Object} [config] Config object
14466      */
14467     constructor: function(panel, config){
14468         /**
14469          * @property panel
14470          * @type Ext.panel.Panel
14471          */
14472         this.panel = panel;
14473         this.id = this.panel.id +'-ddproxy';
14474         Ext.apply(this, config);
14475     },
14476
14477     /**
14478      * @cfg {Boolean} insertProxy
14479      * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
14480      * Most Panels are not absolute positioned and therefore we need to reserve this space.
14481      */
14482     insertProxy: true,
14483
14484     // private overrides
14485     setStatus: Ext.emptyFn,
14486     reset: Ext.emptyFn,
14487     update: Ext.emptyFn,
14488     stop: Ext.emptyFn,
14489     sync: Ext.emptyFn,
14490
14491     /**
14492      * Gets the proxy's element
14493      * @return {Ext.Element} The proxy's element
14494      */
14495     getEl: function(){
14496         return this.ghost.el;
14497     },
14498
14499     /**
14500      * Gets the proxy's ghost Panel
14501      * @return {Ext.panel.Panel} The proxy's ghost Panel
14502      */
14503     getGhost: function(){
14504         return this.ghost;
14505     },
14506
14507     /**
14508      * Gets the proxy element. This is the element that represents where the
14509      * Panel was before we started the drag operation.
14510      * @return {Ext.Element} The proxy's element
14511      */
14512     getProxy: function(){
14513         return this.proxy;
14514     },
14515
14516     /**
14517      * Hides the proxy
14518      */
14519     hide : function(){
14520         if (this.ghost) {
14521             if (this.proxy) {
14522                 this.proxy.remove();
14523                 delete this.proxy;
14524             }
14525
14526             // Unghost the Panel, do not move the Panel to where the ghost was
14527             this.panel.unghost(null, false);
14528             delete this.ghost;
14529         }
14530     },
14531
14532     /**
14533      * Shows the proxy
14534      */
14535     show: function(){
14536         if (!this.ghost) {
14537             var panelSize = this.panel.getSize();
14538             this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
14539             this.ghost = this.panel.ghost();
14540             if (this.insertProxy) {
14541                 // bc Panels aren't absolute positioned we need to take up the space
14542                 // of where the panel previously was
14543                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
14544                 this.proxy.setSize(panelSize);
14545             }
14546         }
14547     },
14548
14549     // private
14550     repair: function(xy, callback, scope) {
14551         this.hide();
14552         if (typeof callback == "function") {
14553             callback.call(scope || this);
14554         }
14555     },
14556
14557     /**
14558      * Moves the proxy to a different position in the DOM.  This is typically
14559      * called while dragging the Panel to keep the proxy sync'd to the Panel's
14560      * location.
14561      * @param {HTMLElement} parentNode The proxy's parent DOM node
14562      * @param {HTMLElement} [before] The sibling node before which the
14563      * proxy should be inserted (defaults to the parent's last child if not
14564      * specified)
14565      */
14566     moveProxy : function(parentNode, before){
14567         if (this.proxy) {
14568             parentNode.insertBefore(this.proxy.dom, before);
14569         }
14570     }
14571 });
14572 /**
14573  * @class Ext.layout.component.AbstractDock
14574  * @extends Ext.layout.component.Component
14575  * @private
14576  * This ComponentLayout handles docking for Panels. It takes care of panels that are
14577  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
14578  * an AutoContainerLayout in which this panel get his height based of the CSS or
14579  * or its content.
14580  */
14581
14582 Ext.define('Ext.layout.component.AbstractDock', {
14583
14584     /* Begin Definitions */
14585
14586     extend: 'Ext.layout.component.Component',
14587
14588     /* End Definitions */
14589
14590     type: 'dock',
14591
14592     /**
14593      * @private
14594      * @property autoSizing
14595      * @type Boolean
14596      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
14597      */
14598     autoSizing: true,
14599
14600     beforeLayout: function() {
14601         var returnValue = this.callParent(arguments);
14602         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
14603             this.handleItemBorders();
14604             this.initializedBorders = true;
14605         }
14606         return returnValue;
14607     },
14608     
14609     handleItemBorders: function() {
14610         var owner = this.owner,
14611             body = owner.body,
14612             docked = this.getLayoutItems(),
14613             borders = {
14614                 top: [],
14615                 right: [],
14616                 bottom: [],
14617                 left: []
14618             },
14619             oldBorders = this.borders,
14620             opposites = {
14621                 top: 'bottom',
14622                 right: 'left',
14623                 bottom: 'top',
14624                 left: 'right'
14625             },
14626             i, ln, item, dock, side;
14627
14628         for (i = 0, ln = docked.length; i < ln; i++) {
14629             item = docked[i];
14630             dock = item.dock;
14631             
14632             if (item.ignoreBorderManagement) {
14633                 continue;
14634             }
14635             
14636             if (!borders[dock].satisfied) {
14637                 borders[dock].push(item);
14638                 borders[dock].satisfied = true;
14639             }
14640             
14641             if (!borders.top.satisfied && opposites[dock] !== 'top') {
14642                 borders.top.push(item);
14643             }
14644             if (!borders.right.satisfied && opposites[dock] !== 'right') {
14645                 borders.right.push(item);
14646             }            
14647             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
14648                 borders.bottom.push(item);
14649             }            
14650             if (!borders.left.satisfied && opposites[dock] !== 'left') {
14651                 borders.left.push(item);
14652             }
14653         }
14654
14655         if (oldBorders) {
14656             for (side in oldBorders) {
14657                 if (oldBorders.hasOwnProperty(side)) {
14658                     ln = oldBorders[side].length;
14659                     if (!owner.manageBodyBorders) {
14660                         for (i = 0; i < ln; i++) {
14661                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
14662                         }
14663                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
14664                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
14665                         }                    
14666                     }
14667                     else if (oldBorders[side].satisfied) {
14668                         body.setStyle('border-' + side + '-width', '');
14669                     }
14670                 }
14671             }
14672         }
14673                 
14674         for (side in borders) {
14675             if (borders.hasOwnProperty(side)) {
14676                 ln = borders[side].length;
14677                 if (!owner.manageBodyBorders) {
14678                     for (i = 0; i < ln; i++) {
14679                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
14680                     }
14681                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
14682                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
14683                     }                    
14684                 }
14685                 else if (borders[side].satisfied) {
14686                     body.setStyle('border-' + side + '-width', '1px');
14687                 }
14688             }
14689         }
14690         
14691         this.borders = borders;
14692     },
14693     
14694     /**
14695      * @protected
14696      * @param {Ext.Component} owner The Panel that owns this DockLayout
14697      * @param {Ext.Element} target The target in which we are going to render the docked items
14698      * @param {Array} args The arguments passed to the ComponentLayout.layout method
14699      */
14700     onLayout: function(width, height) {
14701         if (this.onLayout_running) {
14702             return;
14703         }
14704         this.onLayout_running = true;
14705         var me = this,
14706             owner = me.owner,
14707             body = owner.body,
14708             layout = owner.layout,
14709             target = me.getTarget(),
14710             autoWidth = false,
14711             autoHeight = false,
14712             padding, border, frameSize;
14713
14714         // We start of by resetting all the layouts info
14715         var info = me.info = {
14716             boxes: [],
14717             size: {
14718                 width: width,
14719                 height: height
14720             },
14721             bodyBox: {}
14722         };
14723         // Clear isAutoDock flag
14724         delete layout.isAutoDock;
14725
14726         Ext.applyIf(info, me.getTargetInfo());
14727
14728         // We need to bind to the ownerCt whenever we do not have a user set height or width.
14729         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
14730             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
14731                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
14732             }
14733             else {
14734                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
14735             }
14736         }
14737
14738         // Determine if we have an autoHeight or autoWidth.
14739         if (height == null || width == null) {
14740             padding = info.padding;
14741             border = info.border;
14742             frameSize = me.frameSize;
14743
14744             // Auto-everything, clear out any style height/width and read from css
14745             if ((height == null) && (width == null)) {
14746                 autoHeight = true;
14747                 autoWidth = true;
14748                 me.setTargetSize(null);
14749                 me.setBodyBox({width: null, height: null});
14750             }
14751             // Auto-height
14752             else if (height == null) {
14753                 autoHeight = true;
14754                 // Clear any sizing that we already set in a previous layout
14755                 me.setTargetSize(width);
14756                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
14757             // Auto-width
14758             }
14759             else {
14760                 autoWidth = true;
14761                 // Clear any sizing that we already set in a previous layout
14762                 me.setTargetSize(null, height);
14763                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
14764             }
14765
14766             // Run the container
14767             if (layout && layout.isLayout) {
14768                 // Auto-Sized so have the container layout notify the component layout.
14769                 layout.bindToOwnerCtComponent = true;
14770                 // Set flag so we don't do a redundant container layout
14771                 layout.isAutoDock = layout.autoSize !== true;
14772                 layout.layout();
14773
14774                 // If this is an autosized container layout, then we must compensate for a
14775                 // body that is being autosized.  We do not want to adjust the body's size
14776                 // to accommodate the dock items, but rather we will want to adjust the
14777                 // target's size.
14778                 //
14779                 // This is necessary because, particularly in a Box layout, all child items
14780                 // are set with absolute dimensions that are not flexible to the size of its
14781                 // innerCt/target.  So once they are laid out, they are sized for good. By
14782                 // shrinking the body box to accommodate dock items, we're merely cutting off
14783                 // parts of the body.  Not good.  Instead, the target's size should expand
14784                 // to fit the dock items in.  This is valid because the target container is
14785                 // suppose to be autosized to fit everything accordingly.
14786                 info.autoSizedCtLayout = layout.autoSize === true;
14787                 info.autoHeight = autoHeight;
14788                 info.autoWidth = autoWidth;
14789             }
14790
14791             // The dockItems method will add all the top and bottom docked items height
14792             // to the info.panelSize height. That's why we have to call setSize after
14793             // we dock all the items to actually set the panel's width and height.
14794             // We have to do this because the panel body and docked items will be position
14795             // absolute which doesn't stretch the panel.
14796             me.dockItems();
14797             me.setTargetSize(info.size.width, info.size.height);
14798         }
14799         else {
14800             me.setTargetSize(width, height);
14801             me.dockItems();
14802         }
14803         me.callParent(arguments);
14804         this.onLayout_running = false;
14805     },
14806
14807     /**
14808      * @protected
14809      * This method will first update all the information about the docked items,
14810      * body dimensions and position, the panel's total size. It will then
14811      * set all these values on the docked items and panel body.
14812      * @param {Array} items Array containing all the docked items
14813      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
14814      * AutoContainerLayout
14815      */
14816     dockItems : function() {
14817         this.calculateDockBoxes();
14818
14819         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
14820         // information about the body, panel size, and boxes for docked items
14821         // inside a property called info.
14822         var info = this.info,
14823             autoWidth = info.autoWidth,
14824             autoHeight = info.autoHeight,
14825             boxes = info.boxes,
14826             ln = boxes.length,
14827             dock, i, item;
14828
14829         // We are going to loop over all the boxes that were calculated
14830         // and set the position of each item the box belongs to.
14831         for (i = 0; i < ln; i++) {
14832             dock = boxes[i];
14833             item = dock.item;
14834             item.setPosition(dock.x, dock.y);
14835             if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
14836                 // Auto-Sized so have the container layout notify the component layout.
14837                 item.layout.bindToOwnerCtComponent = true;
14838             }
14839         }
14840
14841         // Don't adjust body width/height if the target is using an auto container layout.
14842         // But, we do want to adjust the body size if the container layout is auto sized.
14843         if (!info.autoSizedCtLayout) {
14844             if (autoWidth) {
14845                 info.bodyBox.width = null;
14846             }
14847             if (autoHeight) {
14848                 info.bodyBox.height = null;
14849             }
14850         }
14851
14852         // If the bodyBox has been adjusted because of the docked items
14853         // we will update the dimensions and position of the panel's body.
14854         this.setBodyBox(info.bodyBox);
14855     },
14856
14857     /**
14858      * @protected
14859      * This method will set up some initial information about the panel size and bodybox
14860      * and then loop over all the items you pass it to take care of stretching, aligning,
14861      * dock position and all calculations involved with adjusting the body box.
14862      * @param {Array} items Array containing all the docked items we have to layout
14863      */
14864     calculateDockBoxes : function() {
14865         if (this.calculateDockBoxes_running) {
14866             // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
14867             return;
14868         }
14869         this.calculateDockBoxes_running = true;
14870         // We want to use the Panel's el width, and the Panel's body height as the initial
14871         // size we are going to use in calculateDockBoxes. We also want to account for
14872         // the border of the panel.
14873         var me = this,
14874             target = me.getTarget(),
14875             items = me.getLayoutItems(),
14876             owner = me.owner,
14877             bodyEl = owner.body,
14878             info = me.info,
14879             autoWidth = info.autoWidth,
14880             autoHeight = info.autoHeight,
14881             size = info.size,
14882             ln = items.length,
14883             padding = info.padding,
14884             border = info.border,
14885             frameSize = me.frameSize,
14886             item, i, box, rect;
14887
14888         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
14889         // around the height of the body and the width of the panel.
14890         if (autoHeight) {
14891             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
14892         }
14893         else {
14894             size.height = target.getHeight();
14895         }
14896         if (autoWidth) {
14897             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
14898         }
14899         else {
14900             size.width = target.getWidth();
14901         }
14902
14903         info.bodyBox = {
14904             x: padding.left + frameSize.left,
14905             y: padding.top + frameSize.top,
14906             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
14907             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
14908         };
14909
14910         // Loop over all the docked items
14911         for (i = 0; i < ln; i++) {
14912             item = items[i];
14913             // The initBox method will take care of stretching and alignment
14914             // In some cases it will also layout the dock items to be able to
14915             // get a width or height measurement
14916             box = me.initBox(item);
14917
14918             if (autoHeight === true) {
14919                 box = me.adjustAutoBox(box, i);
14920             }
14921             else {
14922                 box = me.adjustSizedBox(box, i);
14923             }
14924
14925             // Save our box. This allows us to loop over all docked items and do all
14926             // calculations first. Then in one loop we will actually size and position
14927             // all the docked items that have changed.
14928             info.boxes.push(box);
14929         }
14930         this.calculateDockBoxes_running = false;
14931     },
14932
14933     /**
14934      * @protected
14935      * This method will adjust the position of the docked item and adjust the body box
14936      * accordingly.
14937      * @param {Object} box The box containing information about the width and height
14938      * of this docked item
14939      * @param {Number} index The index position of this docked item
14940      * @return {Object} The adjusted box
14941      */
14942     adjustSizedBox : function(box, index) {
14943         var bodyBox = this.info.bodyBox,
14944             frameSize = this.frameSize,
14945             info = this.info,
14946             padding = info.padding,
14947             pos = box.type,
14948             border = info.border;
14949
14950         switch (pos) {
14951             case 'top':
14952                 box.y = bodyBox.y;
14953                 break;
14954
14955             case 'left':
14956                 box.x = bodyBox.x;
14957                 break;
14958
14959             case 'bottom':
14960                 box.y = (bodyBox.y + bodyBox.height) - box.height;
14961                 break;
14962
14963             case 'right':
14964                 box.x = (bodyBox.x + bodyBox.width) - box.width;
14965                 break;
14966         }
14967
14968         if (box.ignoreFrame) {
14969             if (pos == 'bottom') {
14970                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
14971             }
14972             else {
14973                 box.y -= (frameSize.top + padding.top + border.top);
14974             }
14975             if (pos == 'right') {
14976                 box.x += (frameSize.right + padding.right + border.right);
14977             }
14978             else {
14979                 box.x -= (frameSize.left + padding.left + border.left);
14980             }
14981         }
14982
14983         // If this is not an overlaying docked item, we have to adjust the body box
14984         if (!box.overlay) {
14985             switch (pos) {
14986                 case 'top':
14987                     bodyBox.y += box.height;
14988                     bodyBox.height -= box.height;
14989                     break;
14990
14991                 case 'left':
14992                     bodyBox.x += box.width;
14993                     bodyBox.width -= box.width;
14994                     break;
14995
14996                 case 'bottom':
14997                     bodyBox.height -= box.height;
14998                     break;
14999
15000                 case 'right':
15001                     bodyBox.width -= box.width;
15002                     break;
15003             }
15004         }
15005         return box;
15006     },
15007
15008     /**
15009      * @protected
15010      * This method will adjust the position of the docked item inside an AutoContainerLayout
15011      * and adjust the body box accordingly.
15012      * @param {Object} box The box containing information about the width and height
15013      * of this docked item
15014      * @param {Number} index The index position of this docked item
15015      * @return {Object} The adjusted box
15016      */
15017     adjustAutoBox : function (box, index) {
15018         var info = this.info,
15019             owner = this.owner,
15020             bodyBox = info.bodyBox,
15021             size = info.size,
15022             boxes = info.boxes,
15023             boxesLn = boxes.length,
15024             pos = box.type,
15025             frameSize = this.frameSize,
15026             padding = info.padding,
15027             border = info.border,
15028             autoSizedCtLayout = info.autoSizedCtLayout,
15029             ln = (boxesLn < index) ? boxesLn : index,
15030             i, adjustBox;
15031
15032         if (pos == 'top' || pos == 'bottom') {
15033             // This can affect the previously set left and right and bottom docked items
15034             for (i = 0; i < ln; i++) {
15035                 adjustBox = boxes[i];
15036                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
15037                     adjustBox.height += box.height;
15038                 }
15039                 else if (adjustBox.type == 'bottom') {
15040                     adjustBox.y += box.height;
15041                 }
15042             }
15043         }
15044
15045         switch (pos) {
15046             case 'top':
15047                 box.y = bodyBox.y;
15048                 if (!box.overlay) {
15049                     bodyBox.y += box.height;
15050                     if (info.autoHeight) {
15051                         size.height += box.height;
15052                     } else {
15053                         bodyBox.height -= box.height;
15054                     }
15055                 }
15056                 break;
15057
15058             case 'bottom':
15059                 if (!box.overlay) {
15060                     if (info.autoHeight) {
15061                         size.height += box.height;
15062                     } else {
15063                         bodyBox.height -= box.height;
15064                     }
15065                 }
15066                 box.y = (bodyBox.y + bodyBox.height);
15067                 break;
15068
15069             case 'left':
15070                 box.x = bodyBox.x;
15071                 if (!box.overlay) {
15072                     bodyBox.x += box.width;
15073                     if (info.autoWidth) {
15074                         size.width += box.width;
15075                     } else {
15076                         bodyBox.width -= box.width;
15077                     }
15078                 }
15079                 break;
15080
15081             case 'right':
15082                 if (!box.overlay) {
15083                     if (info.autoWidth) {
15084                         size.width += box.width;
15085                     } else {
15086                         bodyBox.width -= box.width;
15087                     }
15088                 }
15089                 box.x = (bodyBox.x + bodyBox.width);
15090                 break;
15091         }
15092
15093         if (box.ignoreFrame) {
15094             if (pos == 'bottom') {
15095                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
15096             }
15097             else {
15098                 box.y -= (frameSize.top + padding.top + border.top);
15099             }
15100             if (pos == 'right') {
15101                 box.x += (frameSize.right + padding.right + border.right);
15102             }
15103             else {
15104                 box.x -= (frameSize.left + padding.left + border.left);
15105             }
15106         }
15107         return box;
15108     },
15109
15110     /**
15111      * @protected
15112      * This method will create a box object, with a reference to the item, the type of dock
15113      * (top, left, bottom, right). It will also take care of stretching and aligning of the
15114      * docked items.
15115      * @param {Ext.Component} item The docked item we want to initialize the box for
15116      * @return {Object} The initial box containing width and height and other useful information
15117      */
15118     initBox : function(item) {
15119         var me = this,
15120             bodyBox = me.info.bodyBox,
15121             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
15122             owner = me.owner,
15123             frameSize = me.frameSize,
15124             info = me.info,
15125             padding = info.padding,
15126             border = info.border,
15127             box = {
15128                 item: item,
15129                 overlay: item.overlay,
15130                 type: item.dock,
15131                 offsets: Ext.Element.parseBox(item.offsets || {}),
15132                 ignoreFrame: item.ignoreParentFrame
15133             };
15134         // First we are going to take care of stretch and align properties for all four dock scenarios.
15135         if (item.stretch !== false) {
15136             box.stretched = true;
15137             if (horizontal) {
15138                 box.x = bodyBox.x + box.offsets.left;
15139                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
15140                 if (box.ignoreFrame) {
15141                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
15142                 }
15143                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
15144             }
15145             else {
15146                 box.y = bodyBox.y + box.offsets.top;
15147                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
15148                 if (box.ignoreFrame) {
15149                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
15150                 }
15151                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
15152
15153                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
15154                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
15155                 if (!Ext.supports.ComputedStyle) {
15156                     item.el.repaint();
15157                 }
15158             }
15159         }
15160         else {
15161             item.doComponentLayout();
15162             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
15163             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
15164             box.y += box.offsets.top;
15165             if (horizontal) {
15166                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
15167                 box.x += box.offsets.left;
15168             }
15169         }
15170
15171         // If we haven't calculated the width or height of the docked item yet
15172         // do so, since we need this for our upcoming calculations
15173         if (box.width === undefined) {
15174             box.width = item.getWidth() + item.el.getMargin('lr');
15175         }
15176         if (box.height === undefined) {
15177             box.height = item.getHeight() + item.el.getMargin('tb');
15178         }
15179
15180         return box;
15181     },
15182
15183     /**
15184      * @protected
15185      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
15186      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
15187      */
15188     getLayoutItems : function() {
15189         var it = this.owner.getDockedItems(),
15190             ln = it.length,
15191             i = 0,
15192             result = [];
15193         for (; i < ln; i++) {
15194             if (it[i].isVisible(true)) {
15195                 result.push(it[i]);
15196             }
15197         }
15198         return result;
15199     },
15200
15201     /**
15202      * @protected
15203      * Render the top and left docked items before any existing DOM nodes in our render target,
15204      * and then render the right and bottom docked items after. This is important, for such things
15205      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
15206      * Our collection of docked items will already be ordered via Panel.getDockedItems().
15207      */
15208     renderItems: function(items, target) {
15209         var cns = target.dom.childNodes,
15210             cnsLn = cns.length,
15211             ln = items.length,
15212             domLn = 0,
15213             i, j, cn, item;
15214
15215         // Calculate the number of DOM nodes in our target that are not our docked items
15216         for (i = 0; i < cnsLn; i++) {
15217             cn = Ext.get(cns[i]);
15218             for (j = 0; j < ln; j++) {
15219                 item = items[j];
15220                 if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
15221                     break;
15222                 }
15223             }
15224
15225             if (j === ln) {
15226                 domLn++;
15227             }
15228         }
15229
15230         // Now we go through our docked items and render/move them
15231         for (i = 0, j = 0; i < ln; i++, j++) {
15232             item = items[i];
15233
15234             // If we're now at the right/bottom docked item, we jump ahead in our
15235             // DOM position, just past the existing DOM nodes.
15236             //
15237             // TODO: This is affected if users provide custom weight values to their
15238             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
15239             // sort operation here, for now, in the name of performance. getDockedItems()
15240             // needs the sort operation not just for this layout-time rendering, but
15241             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
15242             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
15243                 j += domLn;
15244             }
15245
15246             // Same logic as Layout.renderItems()
15247             if (item && !item.rendered) {
15248                 this.renderItem(item, target, j);
15249             }
15250             else if (!this.isValidParent(item, target, j)) {
15251                 this.moveItem(item, target, j);
15252             }
15253         }
15254     },
15255
15256     /**
15257      * @protected
15258      * This function will be called by the dockItems method. Since the body is positioned absolute,
15259      * we need to give it dimensions and a position so that it is in the middle surrounded by
15260      * docked items
15261      * @param {Object} box An object containing new x, y, width and height values for the
15262      * Panel's body
15263      */
15264     setBodyBox : function(box) {
15265         var me = this,
15266             owner = me.owner,
15267             body = owner.body,
15268             info = me.info,
15269             bodyMargin = info.bodyMargin,
15270             padding = info.padding,
15271             border = info.border,
15272             frameSize = me.frameSize;
15273         
15274         // Panel collapse effectively hides the Panel's body, so this is a no-op.
15275         if (owner.collapsed) {
15276             return;
15277         }
15278         
15279         if (Ext.isNumber(box.width)) {
15280             box.width -= bodyMargin.left + bodyMargin.right;
15281         }
15282         
15283         if (Ext.isNumber(box.height)) {
15284             box.height -= bodyMargin.top + bodyMargin.bottom;
15285         }
15286         
15287         me.setElementSize(body, box.width, box.height);
15288         if (Ext.isNumber(box.x)) {
15289             body.setLeft(box.x - padding.left - frameSize.left);
15290         }
15291         if (Ext.isNumber(box.y)) {
15292             body.setTop(box.y - padding.top - frameSize.top);
15293         }
15294     },
15295
15296     /**
15297      * @protected
15298      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
15299      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
15300      * An example of a class added to a dock: right item is x-docked-right
15301      * @param {Ext.Component} item The item we are configuring
15302      */
15303     configureItem : function(item, pos) {
15304         this.callParent(arguments);
15305         if (item.dock == 'top' || item.dock == 'bottom') {
15306             item.layoutManagedWidth = 1;
15307             item.layoutManagedHeight = 2;
15308         } else {
15309             item.layoutManagedWidth = 2;
15310             item.layoutManagedHeight = 1;
15311         }
15312         
15313         item.addCls(Ext.baseCSSPrefix + 'docked');
15314         item.addClsWithUI('docked-' + item.dock);
15315     },
15316
15317     afterRemove : function(item) {
15318         this.callParent(arguments);
15319         if (this.itemCls) {
15320             item.el.removeCls(this.itemCls + '-' + item.dock);
15321         }
15322         var dom = item.el.dom;
15323
15324         if (!item.destroying && dom) {
15325             dom.parentNode.removeChild(dom);
15326         }
15327         this.childrenChanged = true;
15328     }
15329 });
15330 /**
15331  * @class Ext.util.Memento
15332  * This class manages a set of captured properties from an object. These captured properties
15333  * can later be restored to an object.
15334  */
15335 Ext.define('Ext.util.Memento', function () {
15336
15337     function captureOne (src, target, prop) {
15338         src[prop] = target[prop];
15339     }
15340
15341     function removeOne (src, target, prop) {
15342         delete src[prop];
15343     }
15344
15345     function restoreOne (src, target, prop) {
15346         var value = src[prop];
15347         if (value || src.hasOwnProperty(prop)) {
15348             restoreValue(target, prop, value);
15349         }
15350     }
15351
15352     function restoreValue (target, prop, value) {
15353         if (Ext.isDefined(value)) {
15354             target[prop] = value;
15355         } else {
15356             delete target[prop];
15357         }
15358     }
15359
15360     function doMany (doOne, src, target, props) {
15361         if (src) {
15362             if (Ext.isArray(props)) {
15363                 Ext.each(props, function (prop) {
15364                     doOne(src, target, prop);
15365                 });
15366             } else {
15367                 doOne(src, target, props);
15368             }
15369         }
15370     }
15371
15372     return {
15373         /**
15374          * @property data
15375          * The collection of captured properties.
15376          * @private
15377          */
15378         data: null,
15379
15380         /**
15381          * @property target
15382          * The default target object for capture/restore (passed to the constructor).
15383          */
15384         target: null,
15385
15386         /**
15387          * Creates a new memento and optionally captures properties from the target object.
15388          * @param {Object} target The target from which to capture properties. If specified in the
15389          * constructor, this target becomes the default target for all other operations.
15390          * @param {String/String[]} props The property or array of properties to capture.
15391          */
15392         constructor: function (target, props) {
15393             if (target) {
15394                 this.target = target;
15395                 if (props) {
15396                     this.capture(props);
15397                 }
15398             }
15399         },
15400
15401         /**
15402          * Captures the specified properties from the target object in this memento.
15403          * @param {String/String[]} props The property or array of properties to capture.
15404          * @param {Object} target The object from which to capture properties.
15405          */
15406         capture: function (props, target) {
15407             doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
15408         },
15409
15410         /**
15411          * Removes the specified properties from this memento. These properties will not be
15412          * restored later without re-capturing their values.
15413          * @param {String/String[]} props The property or array of properties to remove.
15414          */
15415         remove: function (props) {
15416             doMany(removeOne, this.data, null, props);
15417         },
15418
15419         /**
15420          * Restores the specified properties from this memento to the target object.
15421          * @param {String/String[]} props The property or array of properties to restore.
15422          * @param {Boolean} clear True to remove the restored properties from this memento or
15423          * false to keep them (default is true).
15424          * @param {Object} target The object to which to restore properties.
15425          */
15426         restore: function (props, clear, target) {
15427             doMany(restoreOne, this.data, target || this.target, props);
15428             if (clear !== false) {
15429                 this.remove(props);
15430             }
15431         },
15432
15433         /**
15434          * Restores all captured properties in this memento to the target object.
15435          * @param {Boolean} clear True to remove the restored properties from this memento or
15436          * false to keep them (default is true).
15437          * @param {Object} target The object to which to restore properties.
15438          */
15439         restoreAll: function (clear, target) {
15440             var me = this,
15441                 t = target || this.target;
15442
15443             Ext.Object.each(me.data, function (prop, value) {
15444                 restoreValue(t, prop, value);
15445             });
15446
15447             if (clear !== false) {
15448                 delete me.data;
15449             }
15450         }
15451     };
15452 }());
15453
15454 /**
15455  * @class Ext.app.EventBus
15456  * @private
15457  */
15458 Ext.define('Ext.app.EventBus', {
15459     requires: [
15460         'Ext.util.Event'
15461     ],
15462     mixins: {
15463         observable: 'Ext.util.Observable'
15464     },
15465
15466     constructor: function() {
15467         this.mixins.observable.constructor.call(this);
15468
15469         this.bus = {};
15470
15471         var me = this;
15472         Ext.override(Ext.Component, {
15473             fireEvent: function(ev) {
15474                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
15475                     return me.dispatch.call(me, ev, this, arguments);
15476                 }
15477                 return false;
15478             }
15479         });
15480     },
15481
15482     dispatch: function(ev, target, args) {
15483         var bus = this.bus,
15484             selectors = bus[ev],
15485             selector, controllers, id, events, event, i, ln;
15486
15487         if (selectors) {
15488             // Loop over all the selectors that are bound to this event
15489             for (selector in selectors) {
15490                 // Check if the target matches the selector
15491                 if (target.is(selector)) {
15492                     // Loop over all the controllers that are bound to this selector
15493                     controllers = selectors[selector];
15494                     for (id in controllers) {
15495                         // Loop over all the events that are bound to this selector on this controller
15496                         events = controllers[id];
15497                         for (i = 0, ln = events.length; i < ln; i++) {
15498                             event = events[i];
15499                             // Fire the event!
15500                             if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
15501                                 return false;
15502                             };
15503                         }
15504                     }
15505                 }
15506             }
15507         }
15508     },
15509
15510     control: function(selectors, listeners, controller) {
15511         var bus = this.bus,
15512             selector, fn;
15513
15514         if (Ext.isString(selectors)) {
15515             selector = selectors;
15516             selectors = {};
15517             selectors[selector] = listeners;
15518             this.control(selectors, null, controller);
15519             return;
15520         }
15521
15522         Ext.Object.each(selectors, function(selector, listeners) {
15523             Ext.Object.each(listeners, function(ev, listener) {
15524                 var options = {},
15525                     scope = controller,
15526                     event = Ext.create('Ext.util.Event', controller, ev);
15527
15528                 // Normalize the listener
15529                 if (Ext.isObject(listener)) {
15530                     options = listener;
15531                     listener = options.fn;
15532                     scope = options.scope || controller;
15533                     delete options.fn;
15534                     delete options.scope;
15535                 }
15536
15537                 event.addListener(listener, scope, options);
15538
15539                 // Create the bus tree if it is not there yet
15540                 bus[ev] = bus[ev] || {};
15541                 bus[ev][selector] = bus[ev][selector] || {};
15542                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
15543
15544                 // Push our listener in our bus
15545                 bus[ev][selector][controller.id].push(event);
15546             });
15547         });
15548     }
15549 });
15550 /**
15551  * @class Ext.data.Types
15552  * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
15553  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
15554  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
15555  * of this class.</p>
15556  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
15557  * each type definition must contain three properties:</p>
15558  * <div class="mdetail-params"><ul>
15559  * <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
15560  * to be stored in the Field. The function is passed the collowing parameters:
15561  * <div class="mdetail-params"><ul>
15562  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
15563  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
15564  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
15565  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
15566  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
15567  * </ul></div></div></li>
15568  * <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>
15569  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
15570  * </ul></div>
15571  * <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
15572  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
15573  *<pre><code>
15574 // Add a new Field data type which stores a VELatLong object in the Record.
15575 Ext.data.Types.VELATLONG = {
15576     convert: function(v, data) {
15577         return new VELatLong(data.lat, data.long);
15578     },
15579     sortType: function(v) {
15580         return v.Latitude;  // When sorting, order by latitude
15581     },
15582     type: 'VELatLong'
15583 };
15584 </code></pre>
15585  * <p>Then, when declaring a Model, use: <pre><code>
15586 var types = Ext.data.Types; // allow shorthand type access
15587 Ext.define('Unit',
15588     extend: 'Ext.data.Model',
15589     fields: [
15590         { name: 'unitName', mapping: 'UnitName' },
15591         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
15592         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
15593         { name: 'longitude', mapping: 'long', type: types.FLOAT },
15594         { name: 'position', type: types.VELATLONG }
15595     ]
15596 });
15597 </code></pre>
15598  * @singleton
15599  */
15600 Ext.define('Ext.data.Types', {
15601     singleton: true,
15602     requires: ['Ext.data.SortTypes']
15603 }, function() {
15604     var st = Ext.data.SortTypes;
15605
15606     Ext.apply(Ext.data.Types, {
15607         /**
15608          * @property {RegExp} stripRe
15609          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
15610          * This should be overridden for localization.
15611          */
15612         stripRe: /[\$,%]/g,
15613
15614         /**
15615          * @property {Object} AUTO
15616          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
15617          */
15618         AUTO: {
15619             convert: function(v) {
15620                 return v;
15621             },
15622             sortType: st.none,
15623             type: 'auto'
15624         },
15625
15626         /**
15627          * @property {Object} STRING
15628          * This data type means that the raw data is converted into a String before it is placed into a Record.
15629          */
15630         STRING: {
15631             convert: function(v) {
15632                 var defaultValue = this.useNull ? null : '';
15633                 return (v === undefined || v === null) ? defaultValue : String(v);
15634             },
15635             sortType: st.asUCString,
15636             type: 'string'
15637         },
15638
15639         /**
15640          * @property {Object} INT
15641          * This data type means that the raw data is converted into an integer before it is placed into a Record.
15642          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
15643          */
15644         INT: {
15645             convert: function(v) {
15646                 return v !== undefined && v !== null && v !== '' ?
15647                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
15648             },
15649             sortType: st.none,
15650             type: 'int'
15651         },
15652
15653         /**
15654          * @property {Object} FLOAT
15655          * This data type means that the raw data is converted into a number before it is placed into a Record.
15656          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
15657          */
15658         FLOAT: {
15659             convert: function(v) {
15660                 return v !== undefined && v !== null && v !== '' ?
15661                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
15662             },
15663             sortType: st.none,
15664             type: 'float'
15665         },
15666
15667         /**
15668          * @property {Object} BOOL
15669          * <p>This data type means that the raw data is converted into a boolean before it is placed into
15670          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
15671          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
15672          */
15673         BOOL: {
15674             convert: function(v) {
15675                 if (this.useNull && (v === undefined || v === null || v === '')) {
15676                     return null;
15677                 }
15678                 return v === true || v === 'true' || v == 1;
15679             },
15680             sortType: st.none,
15681             type: 'bool'
15682         },
15683
15684         /**
15685          * @property {Object} DATE
15686          * This data type means that the raw data is converted into a Date before it is placed into a Record.
15687          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
15688          * being applied.
15689          */
15690         DATE: {
15691             convert: function(v) {
15692                 var df = this.dateFormat,
15693                     parsed;
15694
15695                 if (!v) {
15696                     return null;
15697                 }
15698                 if (Ext.isDate(v)) {
15699                     return v;
15700                 }
15701                 if (df) {
15702                     if (df == 'timestamp') {
15703                         return new Date(v*1000);
15704                     }
15705                     if (df == 'time') {
15706                         return new Date(parseInt(v, 10));
15707                     }
15708                     return Ext.Date.parse(v, df);
15709                 }
15710
15711                 parsed = Date.parse(v);
15712                 return parsed ? new Date(parsed) : null;
15713             },
15714             sortType: st.asDate,
15715             type: 'date'
15716         }
15717     });
15718
15719     Ext.apply(Ext.data.Types, {
15720         /**
15721          * @property {Object} BOOLEAN
15722          * <p>This data type means that the raw data is converted into a boolean before it is placed into
15723          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
15724          * <p>The synonym <code>BOOL</code> is equivalent.</p>
15725          */
15726         BOOLEAN: this.BOOL,
15727
15728         /**
15729          * @property {Object} INTEGER
15730          * This data type means that the raw data is converted into an integer before it is placed into a Record.
15731          * <p>The synonym <code>INT</code> is equivalent.</p>
15732          */
15733         INTEGER: this.INT,
15734
15735         /**
15736          * @property {Object} NUMBER
15737          * This data type means that the raw data is converted into a number before it is placed into a Record.
15738          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
15739          */
15740         NUMBER: this.FLOAT
15741     });
15742 });
15743
15744 /**
15745  * @author Ed Spencer
15746  *
15747  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
15748  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
15749  * Ext.data.Model Model}. For example, we might set up a model like this:
15750  *
15751  *     Ext.define('User', {
15752  *         extend: 'Ext.data.Model',
15753  *         fields: [
15754  *             'name', 'email',
15755  *             {name: 'age', type: 'int'},
15756  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
15757  *         ]
15758  *     });
15759  *
15760  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
15761  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
15762  * up with the 'auto' type. It's as if we'd done this instead:
15763  *
15764  *     Ext.define('User', {
15765  *         extend: 'Ext.data.Model',
15766  *         fields: [
15767  *             {name: 'name', type: 'auto'},
15768  *             {name: 'email', type: 'auto'},
15769  *             {name: 'age', type: 'int'},
15770  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
15771  *         ]
15772  *     });
15773  *
15774  * # Types and conversion
15775  *
15776  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
15777  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
15778  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
15779  *
15780  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
15781  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
15782  *
15783  *     Ext.define('User', {
15784  *         extend: 'Ext.data.Model',
15785  *         fields: [
15786  *             'name', 'email',
15787  *             {name: 'age', type: 'int'},
15788  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
15789  *
15790  *             {
15791  *                 name: 'firstName',
15792  *                 convert: function(value, record) {
15793  *                     var fullName  = record.get('name'),
15794  *                         splits    = fullName.split(" "),
15795  *                         firstName = splits[0];
15796  *
15797  *                     return firstName;
15798  *                 }
15799  *             }
15800  *         ]
15801  *     });
15802  *
15803  * Now when we create a new User, the firstName is populated automatically based on the name:
15804  *
15805  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
15806  *
15807  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
15808  *
15809  * In fact, if we log out all of the data inside ed, we'll see this:
15810  *
15811  *     console.log(ed.data);
15812  *
15813  *     //outputs this:
15814  *     {
15815  *         age: 0,
15816  *         email: "",
15817  *         firstName: "Ed",
15818  *         gender: "Unknown",
15819  *         name: "Ed Spencer"
15820  *     }
15821  *
15822  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
15823  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
15824  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
15825  *
15826  *     ed.set('gender', 'Male');
15827  *     ed.get('gender'); //returns 'Male'
15828  *
15829  *     ed.set('age', 25.4);
15830  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
15831  */
15832 Ext.define('Ext.data.Field', {
15833     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
15834     alias: 'data.field',
15835     
15836     constructor : function(config) {
15837         if (Ext.isString(config)) {
15838             config = {name: config};
15839         }
15840         Ext.apply(this, config);
15841         
15842         var types = Ext.data.Types,
15843             st = this.sortType,
15844             t;
15845
15846         if (this.type) {
15847             if (Ext.isString(this.type)) {
15848                 this.type = types[this.type.toUpperCase()] || types.AUTO;
15849             }
15850         } else {
15851             this.type = types.AUTO;
15852         }
15853
15854         // named sortTypes are supported, here we look them up
15855         if (Ext.isString(st)) {
15856             this.sortType = Ext.data.SortTypes[st];
15857         } else if(Ext.isEmpty(st)) {
15858             this.sortType = this.type.sortType;
15859         }
15860
15861         if (!this.convert) {
15862             this.convert = this.type.convert;
15863         }
15864     },
15865     
15866     /**
15867      * @cfg {String} name
15868      *
15869      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
15870      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
15871      *
15872      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
15873      * just a String for the field name.
15874      */
15875     
15876     /**
15877      * @cfg {String/Object} type
15878      *
15879      * The data type for automatic conversion from received data to the *stored* value if
15880      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
15881      * Possible values are
15882      *
15883      * - auto (Default, implies no conversion)
15884      * - string
15885      * - int
15886      * - float
15887      * - boolean
15888      * - date
15889      *
15890      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
15891      *
15892      * Developers may create their own application-specific data types by defining new members of the {@link
15893      * Ext.data.Types} class.
15894      */
15895     
15896     /**
15897      * @cfg {Function} convert
15898      *
15899      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
15900      * It is passed the following parameters:
15901      *
15902      * - **v** : Mixed
15903      *
15904      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
15905      *   defaultValue}`.
15906      *
15907      * - **rec** : Ext.data.Model
15908      *
15909      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
15910      *   at this point as the fields are read in the order that they are defined in your
15911      *   {@link Ext.data.Model#fields fields} array.
15912      *
15913      * Example of convert functions:
15914      *
15915      *     function fullName(v, record){
15916      *         return record.name.last + ', ' + record.name.first;
15917      *     }
15918      *
15919      *     function location(v, record){
15920      *         return !record.city ? '' : (record.city + ', ' + record.state);
15921      *     }
15922      *
15923      *     Ext.define('Dude', {
15924      *         extend: 'Ext.data.Model',
15925      *         fields: [
15926      *             {name: 'fullname',  convert: fullName},
15927      *             {name: 'firstname', mapping: 'name.first'},
15928      *             {name: 'lastname',  mapping: 'name.last'},
15929      *             {name: 'city', defaultValue: 'homeless'},
15930      *             'state',
15931      *             {name: 'location',  convert: location}
15932      *         ]
15933      *     });
15934      *
15935      *     // create the data store
15936      *     var store = Ext.create('Ext.data.Store', {
15937      *         reader: {
15938      *             type: 'json',
15939      *             model: 'Dude',
15940      *             idProperty: 'key',
15941      *             root: 'daRoot',
15942      *             totalProperty: 'total'
15943      *         }
15944      *     });
15945      *
15946      *     var myData = [
15947      *         { key: 1,
15948      *           name: { first: 'Fat',    last:  'Albert' }
15949      *           // notice no city, state provided in data object
15950      *         },
15951      *         { key: 2,
15952      *           name: { first: 'Barney', last:  'Rubble' },
15953      *           city: 'Bedrock', state: 'Stoneridge'
15954      *         },
15955      *         { key: 3,
15956      *           name: { first: 'Cliff',  last:  'Claven' },
15957      *           city: 'Boston',  state: 'MA'
15958      *         }
15959      *     ];
15960      */
15961
15962     /**
15963      * @cfg {String} dateFormat
15964      *
15965      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
15966      *
15967      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
15968      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
15969      * timestamp. See {@link Ext.Date}.
15970      */
15971     dateFormat: null,
15972     
15973     /**
15974      * @cfg {Boolean} useNull
15975      *
15976      * Use when converting received data into a Number type (either int or float). If the value cannot be
15977      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
15978      */
15979     useNull: false,
15980     
15981     /**
15982      * @cfg {Object} defaultValue
15983      *
15984      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
15985      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
15986      * (i.e. undefined). Defaults to "".
15987      */
15988     defaultValue: "",
15989
15990     /**
15991      * @cfg {String/Number} mapping
15992      *
15993      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
15994      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
15995      * as the field name, the mapping may be omitted.
15996      *
15997      * The form of the mapping expression depends on the Reader being used.
15998      *
15999      * - {@link Ext.data.reader.Json}
16000      *
16001      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
16002      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
16003      *
16004      * - {@link Ext.data.reader.Xml}
16005      *
16006      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
16007      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
16008      *
16009      * - {@link Ext.data.reader.Array}
16010      *
16011      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
16012      *   Array position.
16013      *
16014      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
16015      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
16016      * return the desired data.
16017      */
16018     mapping: null,
16019
16020     /**
16021      * @cfg {Function} sortType
16022      *
16023      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
16024      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
16025      *
16026      *     // current sort     after sort we want
16027      *     // +-+------+          +-+------+
16028      *     // |1|First |          |1|First |
16029      *     // |2|Last  |          |3|Second|
16030      *     // |3|Second|          |2|Last  |
16031      *     // +-+------+          +-+------+
16032      *
16033      *     sortType: function(value) {
16034      *        switch (value.toLowerCase()) // native toLowerCase():
16035      *        {
16036      *           case 'first': return 1;
16037      *           case 'second': return 2;
16038      *           default: return 3;
16039      *        }
16040      *     }
16041      */
16042     sortType : null,
16043
16044     /**
16045      * @cfg {String} sortDir
16046      *
16047      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
16048      */
16049     sortDir : "ASC",
16050
16051     /**
16052      * @cfg {Boolean} allowBlank
16053      * @private
16054      *
16055      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
16056      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
16057      */
16058     allowBlank : true,
16059
16060     /**
16061      * @cfg {Boolean} persist
16062      *
16063      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
16064      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
16065      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
16066      */
16067     persist: true
16068 });
16069
16070 /**
16071  * @class Ext.util.AbstractMixedCollection
16072  * @private
16073  */
16074 Ext.define('Ext.util.AbstractMixedCollection', {
16075     requires: ['Ext.util.Filter'],
16076
16077     mixins: {
16078         observable: 'Ext.util.Observable'
16079     },
16080
16081     constructor: function(allowFunctions, keyFn) {
16082         var me = this;
16083
16084         me.items = [];
16085         me.map = {};
16086         me.keys = [];
16087         me.length = 0;
16088
16089         me.addEvents(
16090             /**
16091              * @event clear
16092              * Fires when the collection is cleared.
16093              */
16094             'clear',
16095
16096             /**
16097              * @event add
16098              * Fires when an item is added to the collection.
16099              * @param {Number} index The index at which the item was added.
16100              * @param {Object} o The item added.
16101              * @param {String} key The key associated with the added item.
16102              */
16103             'add',
16104
16105             /**
16106              * @event replace
16107              * Fires when an item is replaced in the collection.
16108              * @param {String} key he key associated with the new added.
16109              * @param {Object} old The item being replaced.
16110              * @param {Object} new The new item.
16111              */
16112             'replace',
16113
16114             /**
16115              * @event remove
16116              * Fires when an item is removed from the collection.
16117              * @param {Object} o The item being removed.
16118              * @param {String} key (optional) The key associated with the removed item.
16119              */
16120             'remove'
16121         );
16122
16123         me.allowFunctions = allowFunctions === true;
16124
16125         if (keyFn) {
16126             me.getKey = keyFn;
16127         }
16128
16129         me.mixins.observable.constructor.call(me);
16130     },
16131
16132     /**
16133      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
16134      * function should add function references to the collection. Defaults to
16135      * <tt>false</tt>.
16136      */
16137     allowFunctions : false,
16138
16139     /**
16140      * Adds an item to the collection. Fires the {@link #add} event when complete.
16141      * @param {String} key <p>The key to associate with the item, or the new item.</p>
16142      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
16143      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
16144      * the MixedCollection will be able to <i>derive</i> the key for the new item.
16145      * In this case just pass the new item in this parameter.</p>
16146      * @param {Object} o The item to add.
16147      * @return {Object} The item added.
16148      */
16149     add : function(key, obj){
16150         var me = this,
16151             myObj = obj,
16152             myKey = key,
16153             old;
16154
16155         if (arguments.length == 1) {
16156             myObj = myKey;
16157             myKey = me.getKey(myObj);
16158         }
16159         if (typeof myKey != 'undefined' && myKey !== null) {
16160             old = me.map[myKey];
16161             if (typeof old != 'undefined') {
16162                 return me.replace(myKey, myObj);
16163             }
16164             me.map[myKey] = myObj;
16165         }
16166         me.length++;
16167         me.items.push(myObj);
16168         me.keys.push(myKey);
16169         me.fireEvent('add', me.length - 1, myObj, myKey);
16170         return myObj;
16171     },
16172
16173     /**
16174       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
16175       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
16176       * to return a different value as in the following examples:<pre><code>
16177 // normal way
16178 var mc = new Ext.util.MixedCollection();
16179 mc.add(someEl.dom.id, someEl);
16180 mc.add(otherEl.dom.id, otherEl);
16181 //and so on
16182
16183 // using getKey
16184 var mc = new Ext.util.MixedCollection();
16185 mc.getKey = function(el){
16186    return el.dom.id;
16187 };
16188 mc.add(someEl);
16189 mc.add(otherEl);
16190
16191 // or via the constructor
16192 var mc = new Ext.util.MixedCollection(false, function(el){
16193    return el.dom.id;
16194 });
16195 mc.add(someEl);
16196 mc.add(otherEl);
16197      * </code></pre>
16198      * @param {Object} item The item for which to find the key.
16199      * @return {Object} The key for the passed item.
16200      */
16201     getKey : function(o){
16202          return o.id;
16203     },
16204
16205     /**
16206      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
16207      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
16208      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
16209      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
16210      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
16211      * with one having the same key value, then just pass the replacement item in this parameter.</p>
16212      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
16213      * with that key.
16214      * @return {Object}  The new item.
16215      */
16216     replace : function(key, o){
16217         var me = this,
16218             old,
16219             index;
16220
16221         if (arguments.length == 1) {
16222             o = arguments[0];
16223             key = me.getKey(o);
16224         }
16225         old = me.map[key];
16226         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
16227              return me.add(key, o);
16228         }
16229         index = me.indexOfKey(key);
16230         me.items[index] = o;
16231         me.map[key] = o;
16232         me.fireEvent('replace', key, old, o);
16233         return o;
16234     },
16235
16236     /**
16237      * Adds all elements of an Array or an Object to the collection.
16238      * @param {Object/Array} objs An Object containing properties which will be added
16239      * to the collection, or an Array of values, each of which are added to the collection.
16240      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
16241      * has been set to <tt>true</tt>.
16242      */
16243     addAll : function(objs){
16244         var me = this,
16245             i = 0,
16246             args,
16247             len,
16248             key;
16249
16250         if (arguments.length > 1 || Ext.isArray(objs)) {
16251             args = arguments.length > 1 ? arguments : objs;
16252             for (len = args.length; i < len; i++) {
16253                 me.add(args[i]);
16254             }
16255         } else {
16256             for (key in objs) {
16257                 if (objs.hasOwnProperty(key)) {
16258                     if (me.allowFunctions || typeof objs[key] != 'function') {
16259                         me.add(key, objs[key]);
16260                     }
16261                 }
16262             }
16263         }
16264     },
16265
16266     /**
16267      * Executes the specified function once for every item in the collection, passing the following arguments:
16268      * <div class="mdetail-params"><ul>
16269      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
16270      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
16271      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
16272      * </ul></div>
16273      * The function should return a boolean value. Returning false from the function will stop the iteration.
16274      * @param {Function} fn The function to execute for each item.
16275      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
16276      */
16277     each : function(fn, scope){
16278         var items = [].concat(this.items), // each safe for removal
16279             i = 0,
16280             len = items.length,
16281             item;
16282
16283         for (; i < len; i++) {
16284             item = items[i];
16285             if (fn.call(scope || item, item, i, len) === false) {
16286                 break;
16287             }
16288         }
16289     },
16290
16291     /**
16292      * Executes the specified function once for every key in the collection, passing each
16293      * key, and its associated item as the first two parameters.
16294      * @param {Function} fn The function to execute for each item.
16295      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
16296      */
16297     eachKey : function(fn, scope){
16298         var keys = this.keys,
16299             items = this.items,
16300             i = 0,
16301             len = keys.length;
16302
16303         for (; i < len; i++) {
16304             fn.call(scope || window, keys[i], items[i], i, len);
16305         }
16306     },
16307
16308     /**
16309      * Returns the first item in the collection which elicits a true return value from the
16310      * passed selection function.
16311      * @param {Function} fn The selection function to execute for each item.
16312      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
16313      * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
16314      */
16315     findBy : function(fn, scope) {
16316         var keys = this.keys,
16317             items = this.items,
16318             i = 0,
16319             len = items.length;
16320
16321         for (; i < len; i++) {
16322             if (fn.call(scope || window, items[i], keys[i])) {
16323                 return items[i];
16324             }
16325         }
16326         return null;
16327     },
16328
16329     //<deprecated since="0.99">
16330     find : function() {
16331         if (Ext.isDefined(Ext.global.console)) {
16332             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
16333         }
16334         return this.findBy.apply(this, arguments);
16335     },
16336     //</deprecated>
16337
16338     /**
16339      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
16340      * @param {Number} index The index to insert the item at.
16341      * @param {String} key The key to associate with the new item, or the item itself.
16342      * @param {Object} o (optional) If the second parameter was a key, the new item.
16343      * @return {Object} The item inserted.
16344      */
16345     insert : function(index, key, obj){
16346         var me = this,
16347             myKey = key,
16348             myObj = obj;
16349
16350         if (arguments.length == 2) {
16351             myObj = myKey;
16352             myKey = me.getKey(myObj);
16353         }
16354         if (me.containsKey(myKey)) {
16355             me.suspendEvents();
16356             me.removeAtKey(myKey);
16357             me.resumeEvents();
16358         }
16359         if (index >= me.length) {
16360             return me.add(myKey, myObj);
16361         }
16362         me.length++;
16363         Ext.Array.splice(me.items, index, 0, myObj);
16364         if (typeof myKey != 'undefined' && myKey !== null) {
16365             me.map[myKey] = myObj;
16366         }
16367         Ext.Array.splice(me.keys, index, 0, myKey);
16368         me.fireEvent('add', index, myObj, myKey);
16369         return myObj;
16370     },
16371
16372     /**
16373      * Remove an item from the collection.
16374      * @param {Object} o The item to remove.
16375      * @return {Object} The item removed or false if no item was removed.
16376      */
16377     remove : function(o){
16378         return this.removeAt(this.indexOf(o));
16379     },
16380
16381     /**
16382      * Remove all items in the passed array from the collection.
16383      * @param {Array} items An array of items to be removed.
16384      * @return {Ext.util.MixedCollection} this object
16385      */
16386     removeAll : function(items){
16387         Ext.each(items || [], function(item) {
16388             this.remove(item);
16389         }, this);
16390
16391         return this;
16392     },
16393
16394     /**
16395      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
16396      * @param {Number} index The index within the collection of the item to remove.
16397      * @return {Object} The item removed or false if no item was removed.
16398      */
16399     removeAt : function(index){
16400         var me = this,
16401             o,
16402             key;
16403
16404         if (index < me.length && index >= 0) {
16405             me.length--;
16406             o = me.items[index];
16407             Ext.Array.erase(me.items, index, 1);
16408             key = me.keys[index];
16409             if (typeof key != 'undefined') {
16410                 delete me.map[key];
16411             }
16412             Ext.Array.erase(me.keys, index, 1);
16413             me.fireEvent('remove', o, key);
16414             return o;
16415         }
16416         return false;
16417     },
16418
16419     /**
16420      * Removed an item associated with the passed key fom the collection.
16421      * @param {String} key The key of the item to remove.
16422      * @return {Object} The item removed or false if no item was removed.
16423      */
16424     removeAtKey : function(key){
16425         return this.removeAt(this.indexOfKey(key));
16426     },
16427
16428     /**
16429      * Returns the number of items in the collection.
16430      * @return {Number} the number of items in the collection.
16431      */
16432     getCount : function(){
16433         return this.length;
16434     },
16435
16436     /**
16437      * Returns index within the collection of the passed Object.
16438      * @param {Object} o The item to find the index of.
16439      * @return {Number} index of the item. Returns -1 if not found.
16440      */
16441     indexOf : function(o){
16442         return Ext.Array.indexOf(this.items, o);
16443     },
16444
16445     /**
16446      * Returns index within the collection of the passed key.
16447      * @param {String} key The key to find the index of.
16448      * @return {Number} index of the key.
16449      */
16450     indexOfKey : function(key){
16451         return Ext.Array.indexOf(this.keys, key);
16452     },
16453
16454     /**
16455      * Returns the item associated with the passed key OR index.
16456      * Key has priority over index.  This is the equivalent
16457      * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
16458      * @param {String/Number} key The key or index of the item.
16459      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
16460      * If an item was found, but is a Class, returns <tt>null</tt>.
16461      */
16462     get : function(key) {
16463         var me = this,
16464             mk = me.map[key],
16465             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
16466         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
16467     },
16468
16469     /**
16470      * Returns the item at the specified index.
16471      * @param {Number} index The index of the item.
16472      * @return {Object} The item at the specified index.
16473      */
16474     getAt : function(index) {
16475         return this.items[index];
16476     },
16477
16478     /**
16479      * Returns the item associated with the passed key.
16480      * @param {String/Number} key The key of the item.
16481      * @return {Object} The item associated with the passed key.
16482      */
16483     getByKey : function(key) {
16484         return this.map[key];
16485     },
16486
16487     /**
16488      * Returns true if the collection contains the passed Object as an item.
16489      * @param {Object} o  The Object to look for in the collection.
16490      * @return {Boolean} True if the collection contains the Object as an item.
16491      */
16492     contains : function(o){
16493         return Ext.Array.contains(this.items, o);
16494     },
16495
16496     /**
16497      * Returns true if the collection contains the passed Object as a key.
16498      * @param {String} key The key to look for in the collection.
16499      * @return {Boolean} True if the collection contains the Object as a key.
16500      */
16501     containsKey : function(key){
16502         return typeof this.map[key] != 'undefined';
16503     },
16504
16505     /**
16506      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
16507      */
16508     clear : function(){
16509         var me = this;
16510
16511         me.length = 0;
16512         me.items = [];
16513         me.keys = [];
16514         me.map = {};
16515         me.fireEvent('clear');
16516     },
16517
16518     /**
16519      * Returns the first item in the collection.
16520      * @return {Object} the first item in the collection..
16521      */
16522     first : function() {
16523         return this.items[0];
16524     },
16525
16526     /**
16527      * Returns the last item in the collection.
16528      * @return {Object} the last item in the collection..
16529      */
16530     last : function() {
16531         return this.items[this.length - 1];
16532     },
16533
16534     /**
16535      * Collects all of the values of the given property and returns their sum
16536      * @param {String} property The property to sum by
16537      * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
16538      * summing fields in records, where the fields are all stored inside the 'data' object
16539      * @param {Number} [start=0] The record index to start at
16540      * @param {Number} [end=-1] The record index to end at
16541      * @return {Number} The total
16542      */
16543     sum: function(property, root, start, end) {
16544         var values = this.extractValues(property, root),
16545             length = values.length,
16546             sum    = 0,
16547             i;
16548
16549         start = start || 0;
16550         end   = (end || end === 0) ? end : length - 1;
16551
16552         for (i = start; i <= end; i++) {
16553             sum += values[i];
16554         }
16555
16556         return sum;
16557     },
16558
16559     /**
16560      * Collects unique values of a particular property in this MixedCollection
16561      * @param {String} property The property to collect on
16562      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
16563      * summing fields in records, where the fields are all stored inside the 'data' object
16564      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
16565      * @return {Array} The unique values
16566      */
16567     collect: function(property, root, allowNull) {
16568         var values = this.extractValues(property, root),
16569             length = values.length,
16570             hits   = {},
16571             unique = [],
16572             value, strValue, i;
16573
16574         for (i = 0; i < length; i++) {
16575             value = values[i];
16576             strValue = String(value);
16577
16578             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
16579                 hits[strValue] = true;
16580                 unique.push(value);
16581             }
16582         }
16583
16584         return unique;
16585     },
16586
16587     /**
16588      * @private
16589      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
16590      * functions like sum and collect.
16591      * @param {String} property The property to extract
16592      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
16593      * extracting field data from Model instances, where the fields are stored inside the 'data' object
16594      * @return {Array} The extracted values
16595      */
16596     extractValues: function(property, root) {
16597         var values = this.items;
16598
16599         if (root) {
16600             values = Ext.Array.pluck(values, root);
16601         }
16602
16603         return Ext.Array.pluck(values, property);
16604     },
16605
16606     /**
16607      * Returns a range of items in this collection
16608      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
16609      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
16610      * @return {Array} An array of items
16611      */
16612     getRange : function(start, end){
16613         var me = this,
16614             items = me.items,
16615             range = [],
16616             i;
16617
16618         if (items.length < 1) {
16619             return range;
16620         }
16621
16622         start = start || 0;
16623         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
16624         if (start <= end) {
16625             for (i = start; i <= end; i++) {
16626                 range[range.length] = items[i];
16627             }
16628         } else {
16629             for (i = start; i >= end; i--) {
16630                 range[range.length] = items[i];
16631             }
16632         }
16633         return range;
16634     },
16635
16636     /**
16637      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
16638      * property/value pair with optional parameters for substring matching and case sensitivity. See
16639      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
16640      * MixedCollection can be easily filtered by property like this:</p>
16641 <pre><code>
16642 //create a simple store with a few people defined
16643 var people = new Ext.util.MixedCollection();
16644 people.addAll([
16645     {id: 1, age: 25, name: 'Ed'},
16646     {id: 2, age: 24, name: 'Tommy'},
16647     {id: 3, age: 24, name: 'Arne'},
16648     {id: 4, age: 26, name: 'Aaron'}
16649 ]);
16650
16651 //a new MixedCollection containing only the items where age == 24
16652 var middleAged = people.filter('age', 24);
16653 </code></pre>
16654      *
16655      *
16656      * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
16657      * @param {String/RegExp} value Either string that the property values
16658      * should start with or a RegExp to test against the property
16659      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
16660      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
16661      * @return {Ext.util.MixedCollection} The new filtered collection
16662      */
16663     filter : function(property, value, anyMatch, caseSensitive) {
16664         var filters = [],
16665             filterFn;
16666
16667         //support for the simple case of filtering by property/value
16668         if (Ext.isString(property)) {
16669             filters.push(Ext.create('Ext.util.Filter', {
16670                 property     : property,
16671                 value        : value,
16672                 anyMatch     : anyMatch,
16673                 caseSensitive: caseSensitive
16674             }));
16675         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
16676             filters = filters.concat(property);
16677         }
16678
16679         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
16680         //so here we construct a function that combines these filters by ANDing them together
16681         filterFn = function(record) {
16682             var isMatch = true,
16683                 length = filters.length,
16684                 i;
16685
16686             for (i = 0; i < length; i++) {
16687                 var filter = filters[i],
16688                     fn     = filter.filterFn,
16689                     scope  = filter.scope;
16690
16691                 isMatch = isMatch && fn.call(scope, record);
16692             }
16693
16694             return isMatch;
16695         };
16696
16697         return this.filterBy(filterFn);
16698     },
16699
16700     /**
16701      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
16702      * The passed function will be called with each object in the collection.
16703      * If the function returns true, the value is included otherwise it is filtered.
16704      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
16705      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
16706      * @return {Ext.util.MixedCollection} The new filtered collection
16707      */
16708     filterBy : function(fn, scope) {
16709         var me = this,
16710             newMC  = new this.self(),
16711             keys   = me.keys,
16712             items  = me.items,
16713             length = items.length,
16714             i;
16715
16716         newMC.getKey = me.getKey;
16717
16718         for (i = 0; i < length; i++) {
16719             if (fn.call(scope || me, items[i], keys[i])) {
16720                 newMC.add(keys[i], items[i]);
16721             }
16722         }
16723
16724         return newMC;
16725     },
16726
16727     /**
16728      * Finds the index of the first matching object in this collection by a specific property/value.
16729      * @param {String} property The name of a property on your objects.
16730      * @param {String/RegExp} value A string that the property values
16731      * should start with or a RegExp to test against the property.
16732      * @param {Number} [start=0] The index to start searching at.
16733      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
16734      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
16735      * @return {Number} The matched index or -1
16736      */
16737     findIndex : function(property, value, start, anyMatch, caseSensitive){
16738         if(Ext.isEmpty(value, false)){
16739             return -1;
16740         }
16741         value = this.createValueMatcher(value, anyMatch, caseSensitive);
16742         return this.findIndexBy(function(o){
16743             return o && value.test(o[property]);
16744         }, null, start);
16745     },
16746
16747     /**
16748      * Find the index of the first matching object in this collection by a function.
16749      * If the function returns <i>true</i> it is considered a match.
16750      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
16751      * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
16752      * @param {Number} [start=0] The index to start searching at.
16753      * @return {Number} The matched index or -1
16754      */
16755     findIndexBy : function(fn, scope, start){
16756         var me = this,
16757             keys = me.keys,
16758             items = me.items,
16759             i = start || 0,
16760             len = items.length;
16761
16762         for (; i < len; i++) {
16763             if (fn.call(scope || me, items[i], keys[i])) {
16764                 return i;
16765             }
16766         }
16767         return -1;
16768     },
16769
16770     /**
16771      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
16772      * and by Ext.data.Store#filter
16773      * @private
16774      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
16775      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
16776      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
16777      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
16778      */
16779     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
16780         if (!value.exec) { // not a regex
16781             var er = Ext.String.escapeRegex;
16782             value = String(value);
16783
16784             if (anyMatch === true) {
16785                 value = er(value);
16786             } else {
16787                 value = '^' + er(value);
16788                 if (exactMatch === true) {
16789                     value += '$';
16790                 }
16791             }
16792             value = new RegExp(value, caseSensitive ? '' : 'i');
16793         }
16794         return value;
16795     },
16796
16797     /**
16798      * Creates a shallow copy of this collection
16799      * @return {Ext.util.MixedCollection}
16800      */
16801     clone : function() {
16802         var me = this,
16803             copy = new this.self(),
16804             keys = me.keys,
16805             items = me.items,
16806             i = 0,
16807             len = items.length;
16808
16809         for(; i < len; i++){
16810             copy.add(keys[i], items[i]);
16811         }
16812         copy.getKey = me.getKey;
16813         return copy;
16814     }
16815 });
16816
16817 /**
16818  * @docauthor Tommy Maintz <tommy@sencha.com>
16819  *
16820  * 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}.
16821  *
16822  * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
16823  * is more likely you will want to use one of the component classes that import this mixin, such as
16824  * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
16825  */
16826 Ext.define("Ext.util.Sortable", {
16827     /**
16828      * @property {Boolean} isSortable
16829      * Flag denoting that this object is sortable. Always true.
16830      */
16831     isSortable: true,
16832
16833     /**
16834      * @property {String} defaultSortDirection
16835      * The default sort direction to use if one is not specified.
16836      */
16837     defaultSortDirection: "ASC",
16838
16839     requires: [
16840         'Ext.util.Sorter'
16841     ],
16842
16843     /**
16844      * @property {String} sortRoot
16845      * The property in each item that contains the data to sort.
16846      */
16847
16848     /**
16849      * Performs initialization of this mixin. Component classes using this mixin should call this method during their
16850      * own initialization.
16851      */
16852     initSortable: function() {
16853         var me = this,
16854             sorters = me.sorters;
16855
16856         /**
16857          * @property {Ext.util.MixedCollection} sorters
16858          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
16859          */
16860         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
16861             return item.id || item.property;
16862         });
16863
16864         if (sorters) {
16865             me.sorters.addAll(me.decodeSorters(sorters));
16866         }
16867     },
16868
16869     /**
16870      * Sorts the data in the Store by one or more of its properties. Example usage:
16871      *
16872      *     //sort by a single field
16873      *     myStore.sort('myField', 'DESC');
16874      *
16875      *     //sorting by multiple fields
16876      *     myStore.sort([
16877      *         {
16878      *             property : 'age',
16879      *             direction: 'ASC'
16880      *         },
16881      *         {
16882      *             property : 'name',
16883      *             direction: 'DESC'
16884      *         }
16885      *     ]);
16886      *
16887      * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
16888      * the actual sorting to its internal {@link Ext.util.MixedCollection}.
16889      *
16890      * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
16891      *
16892      *     store.sort('myField');
16893      *     store.sort('myField');
16894      *
16895      * Is equivalent to this code, because Store handles the toggling automatically:
16896      *
16897      *     store.sort('myField', 'ASC');
16898      *     store.sort('myField', 'DESC');
16899      *
16900      * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
16901      * {@link Ext.data.Model Model}, or an array of sorter configurations.
16902      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
16903      * @return {Ext.util.Sorter[]}
16904      */
16905     sort: function(sorters, direction, where, doSort) {
16906         var me = this,
16907             sorter, sorterFn,
16908             newSorters;
16909
16910         if (Ext.isArray(sorters)) {
16911             doSort = where;
16912             where = direction;
16913             newSorters = sorters;
16914         }
16915         else if (Ext.isObject(sorters)) {
16916             doSort = where;
16917             where = direction;
16918             newSorters = [sorters];
16919         }
16920         else if (Ext.isString(sorters)) {
16921             sorter = me.sorters.get(sorters);
16922
16923             if (!sorter) {
16924                 sorter = {
16925                     property : sorters,
16926                     direction: direction
16927                 };
16928                 newSorters = [sorter];
16929             }
16930             else if (direction === undefined) {
16931                 sorter.toggle();
16932             }
16933             else {
16934                 sorter.setDirection(direction);
16935             }
16936         }
16937
16938         if (newSorters && newSorters.length) {
16939             newSorters = me.decodeSorters(newSorters);
16940             if (Ext.isString(where)) {
16941                 if (where === 'prepend') {
16942                     sorters = me.sorters.clone().items;
16943
16944                     me.sorters.clear();
16945                     me.sorters.addAll(newSorters);
16946                     me.sorters.addAll(sorters);
16947                 }
16948                 else {
16949                     me.sorters.addAll(newSorters);
16950                 }
16951             }
16952             else {
16953                 me.sorters.clear();
16954                 me.sorters.addAll(newSorters);
16955             }
16956         }
16957
16958         if (doSort !== false) {
16959             me.onBeforeSort(newSorters);
16960             
16961             sorters = me.sorters.items;
16962             if (sorters.length) {
16963                 //construct an amalgamated sorter function which combines all of the Sorters passed
16964                 sorterFn = function(r1, r2) {
16965                     var result = sorters[0].sort(r1, r2),
16966                         length = sorters.length,
16967                         i;
16968
16969                         //if we have more than one sorter, OR any additional sorter functions together
16970                         for (i = 1; i < length; i++) {
16971                             result = result || sorters[i].sort.call(this, r1, r2);
16972                         }
16973
16974                     return result;
16975                 };
16976
16977                 me.doSort(sorterFn);
16978             }
16979         }
16980
16981         return sorters;
16982     },
16983
16984     onBeforeSort: Ext.emptyFn,
16985
16986     /**
16987      * @private
16988      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
16989      * @param {Object[]} sorters The sorters array
16990      * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
16991      */
16992     decodeSorters: function(sorters) {
16993         if (!Ext.isArray(sorters)) {
16994             if (sorters === undefined) {
16995                 sorters = [];
16996             } else {
16997                 sorters = [sorters];
16998             }
16999         }
17000
17001         var length = sorters.length,
17002             Sorter = Ext.util.Sorter,
17003             fields = this.model ? this.model.prototype.fields : null,
17004             field,
17005             config, i;
17006
17007         for (i = 0; i < length; i++) {
17008             config = sorters[i];
17009
17010             if (!(config instanceof Sorter)) {
17011                 if (Ext.isString(config)) {
17012                     config = {
17013                         property: config
17014                     };
17015                 }
17016
17017                 Ext.applyIf(config, {
17018                     root     : this.sortRoot,
17019                     direction: "ASC"
17020                 });
17021
17022                 //support for 3.x style sorters where a function can be defined as 'fn'
17023                 if (config.fn) {
17024                     config.sorterFn = config.fn;
17025                 }
17026
17027                 //support a function to be passed as a sorter definition
17028                 if (typeof config == 'function') {
17029                     config = {
17030                         sorterFn: config
17031                     };
17032                 }
17033
17034                 // ensure sortType gets pushed on if necessary
17035                 if (fields && !config.transform) {
17036                     field = fields.get(config.property);
17037                     config.transform = field ? field.sortType : undefined;
17038                 }
17039                 sorters[i] = Ext.create('Ext.util.Sorter', config);
17040             }
17041         }
17042
17043         return sorters;
17044     },
17045
17046     getSorters: function() {
17047         return this.sorters.items;
17048     }
17049 });
17050 /**
17051  * @class Ext.util.MixedCollection
17052  * <p>
17053  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
17054  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
17055  * collection can be accessed by index  or via the key. Newly added items are added to
17056  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
17057  * is heavier and provides more functionality. Sample usage:
17058  * <pre><code>
17059 var coll = new Ext.util.MixedCollection();
17060 coll.add('key1', 'val1');
17061 coll.add('key2', 'val2');
17062 coll.add('key3', 'val3');
17063
17064 console.log(coll.get('key1')); // prints 'val1'
17065 console.log(coll.indexOfKey('key3')); // prints 2
17066  * </code></pre>
17067  *
17068  * <p>
17069  * The MixedCollection also has support for sorting and filtering of the values in the collection.
17070  * <pre><code>
17071 var coll = new Ext.util.MixedCollection();
17072 coll.add('key1', 100);
17073 coll.add('key2', -100);
17074 coll.add('key3', 17);
17075 coll.add('key4', 0);
17076 var biggerThanZero = coll.filterBy(function(value){
17077     return value > 0;
17078 });
17079 console.log(biggerThanZero.getCount()); // prints 2
17080  * </code></pre>
17081  * </p>
17082  */
17083 Ext.define('Ext.util.MixedCollection', {
17084     extend: 'Ext.util.AbstractMixedCollection',
17085     mixins: {
17086         sortable: 'Ext.util.Sortable'
17087     },
17088
17089     /**
17090      * Creates new MixedCollection.
17091      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
17092      * function should add function references to the collection. Defaults to
17093      * <tt>false</tt>.
17094      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
17095      * and return the key value for that item.  This is used when available to look up the key on items that
17096      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
17097      * equivalent to providing an implementation for the {@link #getKey} method.
17098      */
17099     constructor: function() {
17100         var me = this;
17101         me.callParent(arguments);
17102         me.addEvents('sort');
17103         me.mixins.sortable.initSortable.call(me);
17104     },
17105
17106     doSort: function(sorterFn) {
17107         this.sortBy(sorterFn);
17108     },
17109
17110     /**
17111      * @private
17112      * Performs the actual sorting based on a direction and a sorting function. Internally,
17113      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
17114      * the sorted array data back into this.items and this.keys
17115      * @param {String} property Property to sort by ('key', 'value', or 'index')
17116      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
17117      * @param {Function} fn (optional) Comparison function that defines the sort order.
17118      * Defaults to sorting by numeric value.
17119      */
17120     _sort : function(property, dir, fn){
17121         var me = this,
17122             i, len,
17123             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
17124
17125             //this is a temporary array used to apply the sorting function
17126             c     = [],
17127             keys  = me.keys,
17128             items = me.items;
17129
17130         //default to a simple sorter function if one is not provided
17131         fn = fn || function(a, b) {
17132             return a - b;
17133         };
17134
17135         //copy all the items into a temporary array, which we will sort
17136         for(i = 0, len = items.length; i < len; i++){
17137             c[c.length] = {
17138                 key  : keys[i],
17139                 value: items[i],
17140                 index: i
17141             };
17142         }
17143
17144         //sort the temporary array
17145         Ext.Array.sort(c, function(a, b){
17146             var v = fn(a[property], b[property]) * dsc;
17147             if(v === 0){
17148                 v = (a.index < b.index ? -1 : 1);
17149             }
17150             return v;
17151         });
17152
17153         //copy the temporary array back into the main this.items and this.keys objects
17154         for(i = 0, len = c.length; i < len; i++){
17155             items[i] = c[i].value;
17156             keys[i]  = c[i].key;
17157         }
17158
17159         me.fireEvent('sort', me);
17160     },
17161
17162     /**
17163      * Sorts the collection by a single sorter function
17164      * @param {Function} sorterFn The function to sort by
17165      */
17166     sortBy: function(sorterFn) {
17167         var me     = this,
17168             items  = me.items,
17169             keys   = me.keys,
17170             length = items.length,
17171             temp   = [],
17172             i;
17173
17174         //first we create a copy of the items array so that we can sort it
17175         for (i = 0; i < length; i++) {
17176             temp[i] = {
17177                 key  : keys[i],
17178                 value: items[i],
17179                 index: i
17180             };
17181         }
17182
17183         Ext.Array.sort(temp, function(a, b) {
17184             var v = sorterFn(a.value, b.value);
17185             if (v === 0) {
17186                 v = (a.index < b.index ? -1 : 1);
17187             }
17188
17189             return v;
17190         });
17191
17192         //copy the temporary array back into the main this.items and this.keys objects
17193         for (i = 0; i < length; i++) {
17194             items[i] = temp[i].value;
17195             keys[i]  = temp[i].key;
17196         }
17197         
17198         me.fireEvent('sort', me, items, keys);
17199     },
17200
17201     /**
17202      * Reorders each of the items based on a mapping from old index to new index. Internally this
17203      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
17204      * @param {Object} mapping Mapping from old item index to new item index
17205      */
17206     reorder: function(mapping) {
17207         var me = this,
17208             items = me.items,
17209             index = 0,
17210             length = items.length,
17211             order = [],
17212             remaining = [],
17213             oldIndex;
17214
17215         me.suspendEvents();
17216
17217         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
17218         for (oldIndex in mapping) {
17219             order[mapping[oldIndex]] = items[oldIndex];
17220         }
17221
17222         for (index = 0; index < length; index++) {
17223             if (mapping[index] == undefined) {
17224                 remaining.push(items[index]);
17225             }
17226         }
17227
17228         for (index = 0; index < length; index++) {
17229             if (order[index] == undefined) {
17230                 order[index] = remaining.shift();
17231             }
17232         }
17233
17234         me.clear();
17235         me.addAll(order);
17236
17237         me.resumeEvents();
17238         me.fireEvent('sort', me);
17239     },
17240
17241     /**
17242      * Sorts this collection by <b>key</b>s.
17243      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
17244      * @param {Function} fn (optional) Comparison function that defines the sort order.
17245      * Defaults to sorting by case insensitive string.
17246      */
17247     sortByKey : function(dir, fn){
17248         this._sort('key', dir, fn || function(a, b){
17249             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
17250             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
17251         });
17252     }
17253 });
17254
17255 /**
17256  * @author Ed Spencer
17257  * @class Ext.data.Errors
17258  * @extends Ext.util.MixedCollection
17259  *
17260  * <p>Wraps a collection of validation error responses and provides convenient functions for
17261  * accessing and errors for specific fields.</p>
17262  *
17263  * <p>Usually this class does not need to be instantiated directly - instances are instead created
17264  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
17265  *
17266 <pre><code>
17267 //validate some existing model instance - in this case it returned 2 failures messages
17268 var errors = myModel.validate();
17269
17270 errors.isValid(); //false
17271
17272 errors.length; //2
17273 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
17274 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
17275 </code></pre>
17276  */
17277 Ext.define('Ext.data.Errors', {
17278     extend: 'Ext.util.MixedCollection',
17279
17280     /**
17281      * Returns true if there are no errors in the collection
17282      * @return {Boolean}
17283      */
17284     isValid: function() {
17285         return this.length === 0;
17286     },
17287
17288     /**
17289      * Returns all of the errors for the given field
17290      * @param {String} fieldName The field to get errors for
17291      * @return {Object[]} All errors for the given field
17292      */
17293     getByField: function(fieldName) {
17294         var errors = [],
17295             error, field, i;
17296
17297         for (i = 0; i < this.length; i++) {
17298             error = this.items[i];
17299
17300             if (error.field == fieldName) {
17301                 errors.push(error);
17302             }
17303         }
17304
17305         return errors;
17306     }
17307 });
17308
17309 /**
17310  * @author Ed Spencer
17311  *
17312  * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
17313  * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
17314  * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
17315  * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
17316  * 
17317  *     Ext.create('Ext.data.Store', {
17318  *         model: 'User',
17319  *         proxy: {
17320  *             type: 'ajax',
17321  *             url : 'users.json',
17322  *             reader: {
17323  *                 type: 'json',
17324  *                 root: 'users'
17325  *             }
17326  *         },
17327  *     });
17328  *     
17329  * The above reader is configured to consume a JSON string that looks something like this:
17330  *  
17331  *     {
17332  *         "success": true,
17333  *         "users": [
17334  *             { "name": "User 1" },
17335  *             { "name": "User 2" }
17336  *         ]
17337  *     }
17338  * 
17339  *
17340  * # Loading Nested Data
17341  *
17342  * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
17343  * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
17344  * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
17345  *
17346  *     Ext.define("User", {
17347  *         extend: 'Ext.data.Model',
17348  *         fields: [
17349  *             'id', 'name'
17350  *         ],
17351  *
17352  *         hasMany: {model: 'Order', name: 'orders'},
17353  *
17354  *         proxy: {
17355  *             type: 'rest',
17356  *             url : 'users.json',
17357  *             reader: {
17358  *                 type: 'json',
17359  *                 root: 'users'
17360  *             }
17361  *         }
17362  *     });
17363  *
17364  *     Ext.define("Order", {
17365  *         extend: 'Ext.data.Model',
17366  *         fields: [
17367  *             'id', 'total'
17368  *         ],
17369  *
17370  *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
17371  *         belongsTo: 'User'
17372  *     });
17373  *
17374  *     Ext.define("OrderItem", {
17375  *         extend: 'Ext.data.Model',
17376  *         fields: [
17377  *             'id', 'price', 'quantity', 'order_id', 'product_id'
17378  *         ],
17379  *
17380  *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
17381  *     });
17382  *
17383  *     Ext.define("Product", {
17384  *         extend: 'Ext.data.Model',
17385  *         fields: [
17386  *             'id', 'name'
17387  *         ],
17388  *
17389  *         hasMany: 'OrderItem'
17390  *     });
17391  *
17392  * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
17393  * Finally, each OrderItem has a single Product. This allows us to consume data like this:
17394  *
17395  *     {
17396  *         "users": [
17397  *             {
17398  *                 "id": 123,
17399  *                 "name": "Ed",
17400  *                 "orders": [
17401  *                     {
17402  *                         "id": 50,
17403  *                         "total": 100,
17404  *                         "order_items": [
17405  *                             {
17406  *                                 "id"      : 20,
17407  *                                 "price"   : 40,
17408  *                                 "quantity": 2,
17409  *                                 "product" : {
17410  *                                     "id": 1000,
17411  *                                     "name": "MacBook Pro"
17412  *                                 }
17413  *                             },
17414  *                             {
17415  *                                 "id"      : 21,
17416  *                                 "price"   : 20,
17417  *                                 "quantity": 3,
17418  *                                 "product" : {
17419  *                                     "id": 1001,
17420  *                                     "name": "iPhone"
17421  *                                 }
17422  *                             }
17423  *                         ]
17424  *                     }
17425  *                 ]
17426  *             }
17427  *         ]
17428  *     }
17429  *
17430  * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
17431  * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
17432  * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
17433  *
17434  *     var store = Ext.create('Ext.data.Store', {
17435  *         model: "User"
17436  *     });
17437  *
17438  *     store.load({
17439  *         callback: function() {
17440  *             //the user that was loaded
17441  *             var user = store.first();
17442  *
17443  *             console.log("Orders for " + user.get('name') + ":")
17444  *
17445  *             //iterate over the Orders for each User
17446  *             user.orders().each(function(order) {
17447  *                 console.log("Order ID: " + order.getId() + ", which contains items:");
17448  *
17449  *                 //iterate over the OrderItems for each Order
17450  *                 order.orderItems().each(function(orderItem) {
17451  *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct
17452  *                     //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
17453  *                     var product = orderItem.getProduct();
17454  *
17455  *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
17456  *                 });
17457  *             });
17458  *         }
17459  *     });
17460  *
17461  * Running the code above results in the following:
17462  *
17463  *     Orders for Ed:
17464  *     Order ID: 50, which contains items:
17465  *     2 orders of MacBook Pro
17466  *     3 orders of iPhone
17467  */
17468 Ext.define('Ext.data.reader.Reader', {
17469     requires: ['Ext.data.ResultSet'],
17470     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
17471     
17472     /**
17473      * @cfg {String} idProperty
17474      * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
17475      * model. If an idProperty is explicitly specified it will override that of the one specified on the model
17476      */
17477
17478     /**
17479      * @cfg {String} totalProperty
17480      * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
17481      * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
17482      */
17483     totalProperty: 'total',
17484
17485     /**
17486      * @cfg {String} successProperty
17487      * Name of the property from which to retrieve the success attribute. Defaults to success. See
17488      * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
17489      */
17490     successProperty: 'success',
17491
17492     /**
17493      * @cfg {String} root
17494      * The name of the property which contains the Array of row objects.  For JSON reader it's dot-separated list
17495      * of property names.  For XML reader it's a CSS selector.  For array reader it's not applicable.
17496      * 
17497      * By default the natural root of the data will be used.  The root Json array, the root XML element, or the array.
17498      *
17499      * The data packet value for this property should be an empty array to clear the data or show no data.
17500      */
17501     root: '',
17502     
17503     /**
17504      * @cfg {String} messageProperty
17505      * The name of the property which contains a response message. This property is optional.
17506      */
17507     
17508     /**
17509      * @cfg {Boolean} implicitIncludes
17510      * True to automatically parse models nested within other models in a response object. See the
17511      * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
17512      */
17513     implicitIncludes: true,
17514     
17515     isReader: true,
17516     
17517     /**
17518      * Creates new Reader.
17519      * @param {Object} config (optional) Config object.
17520      */
17521     constructor: function(config) {
17522         var me = this;
17523         
17524         Ext.apply(me, config || {});
17525         me.fieldCount = 0;
17526         me.model = Ext.ModelManager.getModel(config.model);
17527         if (me.model) {
17528             me.buildExtractors();
17529         }
17530     },
17531
17532     /**
17533      * Sets a new model for the reader.
17534      * @private
17535      * @param {Object} model The model to set.
17536      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
17537      */
17538     setModel: function(model, setOnProxy) {
17539         var me = this;
17540         
17541         me.model = Ext.ModelManager.getModel(model);
17542         me.buildExtractors(true);
17543         
17544         if (setOnProxy && me.proxy) {
17545             me.proxy.setModel(me.model, true);
17546         }
17547     },
17548
17549     /**
17550      * Reads the given response object. This method normalizes the different types of response object that may be passed
17551      * to it, before handing off the reading of records to the {@link #readRecords} function.
17552      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
17553      * @return {Ext.data.ResultSet} The parsed ResultSet object
17554      */
17555     read: function(response) {
17556         var data = response;
17557         
17558         if (response && response.responseText) {
17559             data = this.getResponseData(response);
17560         }
17561         
17562         if (data) {
17563             return this.readRecords(data);
17564         } else {
17565             return this.nullResultSet;
17566         }
17567     },
17568
17569     /**
17570      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
17571      * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
17572      * processing should not be needed.
17573      * @param {Object} data The raw data object
17574      * @return {Ext.data.ResultSet} A ResultSet object
17575      */
17576     readRecords: function(data) {
17577         var me  = this;
17578         
17579         /*
17580          * We check here whether the number of fields has changed since the last read.
17581          * This works around an issue when a Model is used for both a Tree and another
17582          * source, because the tree decorates the model with extra fields and it causes
17583          * issues because the readers aren't notified.
17584          */
17585         if (me.fieldCount !== me.getFields().length) {
17586             me.buildExtractors(true);
17587         }
17588         
17589         /**
17590          * @property {Object} rawData
17591          * The raw data object that was last passed to readRecords. Stored for further processing if needed
17592          */
17593         me.rawData = data;
17594
17595         data = me.getData(data);
17596
17597         // If we pass an array as the data, we dont use getRoot on the data.
17598         // Instead the root equals to the data.
17599         var root    = Ext.isArray(data) ? data : me.getRoot(data),
17600             success = true,
17601             recordCount = 0,
17602             total, value, records, message;
17603             
17604         if (root) {
17605             total = root.length;
17606         }
17607
17608         if (me.totalProperty) {
17609             value = parseInt(me.getTotal(data), 10);
17610             if (!isNaN(value)) {
17611                 total = value;
17612             }
17613         }
17614
17615         if (me.successProperty) {
17616             value = me.getSuccess(data);
17617             if (value === false || value === 'false') {
17618                 success = false;
17619             }
17620         }
17621         
17622         if (me.messageProperty) {
17623             message = me.getMessage(data);
17624         }
17625         
17626         if (root) {
17627             records = me.extractData(root);
17628             recordCount = records.length;
17629         } else {
17630             recordCount = 0;
17631             records = [];
17632         }
17633
17634         return Ext.create('Ext.data.ResultSet', {
17635             total  : total || recordCount,
17636             count  : recordCount,
17637             records: records,
17638             success: success,
17639             message: message
17640         });
17641     },
17642
17643     /**
17644      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
17645      * @param {Object[]/Object} root from server response
17646      * @private
17647      */
17648     extractData : function(root) {
17649         var me = this,
17650             values  = [],
17651             records = [],
17652             Model   = me.model,
17653             i       = 0,
17654             length  = root.length,
17655             idProp  = me.getIdProperty(),
17656             node, id, record;
17657             
17658         if (!root.length && Ext.isObject(root)) {
17659             root = [root];
17660             length = 1;
17661         }
17662
17663         for (; i < length; i++) {
17664             node   = root[i];
17665             values = me.extractValues(node);
17666             id     = me.getId(node);
17667
17668             
17669             record = new Model(values, id, node);
17670             records.push(record);
17671                 
17672             if (me.implicitIncludes) {
17673                 me.readAssociated(record, node);
17674             }
17675         }
17676
17677         return records;
17678     },
17679     
17680     /**
17681      * @private
17682      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
17683      * on the record provided.
17684      * @param {Ext.data.Model} record The record to load associations for
17685      * @param {Object} data The data object
17686      * @return {String} Return value description
17687      */
17688     readAssociated: function(record, data) {
17689         var associations = record.associations.items,
17690             i            = 0,
17691             length       = associations.length,
17692             association, associationData, proxy, reader;
17693         
17694         for (; i < length; i++) {
17695             association     = associations[i];
17696             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
17697             
17698             if (associationData) {
17699                 reader = association.getReader();
17700                 if (!reader) {
17701                     proxy = association.associatedModel.proxy;
17702                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
17703                     if (proxy) {
17704                         reader = proxy.getReader();
17705                     } else {
17706                         reader = new this.constructor({
17707                             model: association.associatedName
17708                         });
17709                     }
17710                 }
17711                 association.read(record, reader, associationData);
17712             }  
17713         }
17714     },
17715     
17716     /**
17717      * @private
17718      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
17719      * record, this should return the relevant part of that data for the given association name. This is only really
17720      * needed to support the XML Reader, which has to do a query to get the associated data object
17721      * @param {Object} data The raw data object
17722      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
17723      * @return {Object} The root
17724      */
17725     getAssociatedDataRoot: function(data, associationName) {
17726         return data[associationName];
17727     },
17728     
17729     getFields: function() {
17730         return this.model.prototype.fields.items;
17731     },
17732
17733     /**
17734      * @private
17735      * Given an object representing a single model instance's data, iterates over the model's fields and
17736      * builds an object with the value for each field.
17737      * @param {Object} data The data object to convert
17738      * @return {Object} Data object suitable for use with a model constructor
17739      */
17740     extractValues: function(data) {
17741         var fields = this.getFields(),
17742             i      = 0,
17743             length = fields.length,
17744             output = {},
17745             field, value;
17746
17747         for (; i < length; i++) {
17748             field = fields[i];
17749             value = this.extractorFunctions[i](data);
17750
17751             output[field.name] = value;
17752         }
17753
17754         return output;
17755     },
17756
17757     /**
17758      * @private
17759      * By default this function just returns what is passed to it. It can be overridden in a subclass
17760      * to return something else. See XmlReader for an example.
17761      * @param {Object} data The data object
17762      * @return {Object} The normalized data object
17763      */
17764     getData: function(data) {
17765         return data;
17766     },
17767
17768     /**
17769      * @private
17770      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
17771      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
17772      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
17773      * @param {Object} data The data object
17774      * @return {Object} The same data object
17775      */
17776     getRoot: function(data) {
17777         return data;
17778     },
17779
17780     /**
17781      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
17782      * implemented by each subclass
17783      * @param {Object} response The responce object
17784      * @return {Object} The useful data from the response
17785      */
17786     getResponseData: function(response) {
17787         //<debug>
17788         Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
17789         //</debug>
17790     },
17791
17792     /**
17793      * @private
17794      * Reconfigures the meta data tied to this Reader
17795      */
17796     onMetaChange : function(meta) {
17797         var fields = meta.fields,
17798             newModel;
17799         
17800         Ext.apply(this, meta);
17801         
17802         if (fields) {
17803             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
17804                 extend: 'Ext.data.Model',
17805                 fields: fields
17806             });
17807             this.setModel(newModel, true);
17808         } else {
17809             this.buildExtractors(true);
17810         }
17811     },
17812     
17813     /**
17814      * Get the idProperty to use for extracting data
17815      * @private
17816      * @return {String} The id property
17817      */
17818     getIdProperty: function(){
17819         var prop = this.idProperty;
17820         if (Ext.isEmpty(prop)) {
17821             prop = this.model.prototype.idProperty;
17822         }
17823         return prop;
17824     },
17825
17826     /**
17827      * @private
17828      * This builds optimized functions for retrieving record data and meta data from an object.
17829      * Subclasses may need to implement their own getRoot function.
17830      * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
17831      */
17832     buildExtractors: function(force) {
17833         var me          = this,
17834             idProp      = me.getIdProperty(),
17835             totalProp   = me.totalProperty,
17836             successProp = me.successProperty,
17837             messageProp = me.messageProperty,
17838             accessor;
17839             
17840         if (force === true) {
17841             delete me.extractorFunctions;
17842         }
17843         
17844         if (me.extractorFunctions) {
17845             return;
17846         }   
17847
17848         //build the extractors for all the meta data
17849         if (totalProp) {
17850             me.getTotal = me.createAccessor(totalProp);
17851         }
17852
17853         if (successProp) {
17854             me.getSuccess = me.createAccessor(successProp);
17855         }
17856
17857         if (messageProp) {
17858             me.getMessage = me.createAccessor(messageProp);
17859         }
17860
17861         if (idProp) {
17862             accessor = me.createAccessor(idProp);
17863
17864             me.getId = function(record) {
17865                 var id = accessor.call(me, record);
17866                 return (id === undefined || id === '') ? null : id;
17867             };
17868         } else {
17869             me.getId = function() {
17870                 return null;
17871             };
17872         }
17873         me.buildFieldExtractors();
17874     },
17875
17876     /**
17877      * @private
17878      */
17879     buildFieldExtractors: function() {
17880         //now build the extractors for all the fields
17881         var me = this,
17882             fields = me.getFields(),
17883             ln = fields.length,
17884             i  = 0,
17885             extractorFunctions = [],
17886             field, map;
17887
17888         for (; i < ln; i++) {
17889             field = fields[i];
17890             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
17891
17892             extractorFunctions.push(me.createAccessor(map));
17893         }
17894         me.fieldCount = ln;
17895
17896         me.extractorFunctions = extractorFunctions;
17897     }
17898 }, function() {
17899     Ext.apply(this, {
17900         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
17901         nullResultSet: Ext.create('Ext.data.ResultSet', {
17902             total  : 0,
17903             count  : 0,
17904             records: [],
17905             success: true
17906         })
17907     });
17908 });
17909 /**
17910  * @author Ed Spencer
17911  * @class Ext.data.reader.Json
17912  * @extends Ext.data.reader.Reader
17913  *
17914  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
17915  * happens as a result of loading a Store - for example we might create something like this:</p>
17916  *
17917 <pre><code>
17918 Ext.define('User', {
17919     extend: 'Ext.data.Model',
17920     fields: ['id', 'name', 'email']
17921 });
17922
17923 var store = Ext.create('Ext.data.Store', {
17924     model: 'User',
17925     proxy: {
17926         type: 'ajax',
17927         url : 'users.json',
17928         reader: {
17929             type: 'json'
17930         }
17931     }
17932 });
17933 </code></pre>
17934  *
17935  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
17936  * not already familiar with them.</p>
17937  *
17938  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
17939  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
17940  * Store, so it is as if we passed this instead:
17941  *
17942 <pre><code>
17943 reader: {
17944     type : 'json',
17945     model: 'User'
17946 }
17947 </code></pre>
17948  *
17949  * <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>
17950  *
17951 <pre><code>
17952 [
17953     {
17954         "id": 1,
17955         "name": "Ed Spencer",
17956         "email": "ed@sencha.com"
17957     },
17958     {
17959         "id": 2,
17960         "name": "Abe Elias",
17961         "email": "abe@sencha.com"
17962     }
17963 ]
17964 </code></pre>
17965  *
17966  * <p><u>Reading other JSON formats</u></p>
17967  *
17968  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
17969  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
17970  * {@link #root} configuration to parse data that comes back like this:</p>
17971  *
17972 <pre><code>
17973 {
17974     "users": [
17975        {
17976            "id": 1,
17977            "name": "Ed Spencer",
17978            "email": "ed@sencha.com"
17979        },
17980        {
17981            "id": 2,
17982            "name": "Abe Elias",
17983            "email": "abe@sencha.com"
17984        }
17985     ]
17986 }
17987 </code></pre>
17988  *
17989  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
17990  *
17991 <pre><code>
17992 reader: {
17993     type: 'json',
17994     root: 'users'
17995 }
17996 </code></pre>
17997  *
17998  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
17999  * around each record inside a nested structure like this:</p>
18000  *
18001 <pre><code>
18002 {
18003     "total": 122,
18004     "offset": 0,
18005     "users": [
18006         {
18007             "id": "ed-spencer-1",
18008             "value": 1,
18009             "user": {
18010                 "id": 1,
18011                 "name": "Ed Spencer",
18012                 "email": "ed@sencha.com"
18013             }
18014         }
18015     ]
18016 }
18017 </code></pre>
18018  *
18019  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
18020  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
18021  * JSON above we need to specify the {@link #record} configuration like this:</p>
18022  *
18023 <pre><code>
18024 reader: {
18025     type  : 'json',
18026     root  : 'users',
18027     record: 'user'
18028 }
18029 </code></pre>
18030  *
18031  * <p><u>Response metadata</u></p>
18032  *
18033  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
18034  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
18035  * like this:</p>
18036  *
18037 <pre><code>
18038 {
18039     "total": 100,
18040     "success": true,
18041     "users": [
18042         {
18043             "id": 1,
18044             "name": "Ed Spencer",
18045             "email": "ed@sencha.com"
18046         }
18047     ]
18048 }
18049 </code></pre>
18050  *
18051  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
18052  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
18053  * options:</p>
18054  *
18055 <pre><code>
18056 reader: {
18057     type : 'json',
18058     root : 'users',
18059     totalProperty  : 'total',
18060     successProperty: 'success'
18061 }
18062 </code></pre>
18063  *
18064  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
18065  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
18066  * returned.</p>
18067  */
18068 Ext.define('Ext.data.reader.Json', {
18069     extend: 'Ext.data.reader.Reader',
18070     alternateClassName: 'Ext.data.JsonReader',
18071     alias : 'reader.json',
18072
18073     root: '',
18074
18075     /**
18076      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
18077      * See the JsonReader intro docs for more details. This is not often needed.
18078      */
18079
18080     /**
18081      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
18082      * reading values. Defalts to <tt>false</tt>.
18083      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
18084      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
18085      * "foo.bar.baz" direct from the root object.
18086      */
18087     useSimpleAccessors: false,
18088
18089     /**
18090      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
18091      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
18092      * @param {Object} data The raw JSON data
18093      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
18094      */
18095     readRecords: function(data) {
18096         //this has to be before the call to super because we use the meta data in the superclass readRecords
18097         if (data.metaData) {
18098             this.onMetaChange(data.metaData);
18099         }
18100
18101         /**
18102          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
18103          * @property {Object} jsonData
18104          */
18105         this.jsonData = data;
18106         return this.callParent([data]);
18107     },
18108
18109     //inherit docs
18110     getResponseData: function(response) {
18111         var data;
18112         try {
18113             data = Ext.decode(response.responseText);
18114         }
18115         catch (ex) {
18116             Ext.Error.raise({
18117                 response: response,
18118                 json: response.responseText,
18119                 parseError: ex,
18120                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
18121             });
18122         }
18123         //<debug>
18124         if (!data) {
18125             Ext.Error.raise('JSON object not found');
18126         }
18127         //</debug>
18128
18129         return data;
18130     },
18131
18132     //inherit docs
18133     buildExtractors : function() {
18134         var me = this;
18135
18136         me.callParent(arguments);
18137
18138         if (me.root) {
18139             me.getRoot = me.createAccessor(me.root);
18140         } else {
18141             me.getRoot = function(root) {
18142                 return root;
18143             };
18144         }
18145     },
18146
18147     /**
18148      * @private
18149      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
18150      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
18151      * @param {Object} root The JSON root node
18152      * @return {Ext.data.Model[]} The records
18153      */
18154     extractData: function(root) {
18155         var recordName = this.record,
18156             data = [],
18157             length, i;
18158
18159         if (recordName) {
18160             length = root.length;
18161             
18162             if (!length && Ext.isObject(root)) {
18163                 length = 1;
18164                 root = [root];
18165             }
18166
18167             for (i = 0; i < length; i++) {
18168                 data[i] = root[i][recordName];
18169             }
18170         } else {
18171             data = root;
18172         }
18173         return this.callParent([data]);
18174     },
18175
18176     /**
18177      * @private
18178      * Returns an accessor function for the given property string. Gives support for properties such as the following:
18179      * 'someProperty'
18180      * 'some.property'
18181      * 'some["property"]'
18182      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
18183      */
18184     createAccessor: function() {
18185         var re = /[\[\.]/;
18186
18187         return function(expr) {
18188             if (Ext.isEmpty(expr)) {
18189                 return Ext.emptyFn;
18190             }
18191             if (Ext.isFunction(expr)) {
18192                 return expr;
18193             }
18194             if (this.useSimpleAccessors !== true) {
18195                 var i = String(expr).search(re);
18196                 if (i >= 0) {
18197                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
18198                 }
18199             }
18200             return function(obj) {
18201                 return obj[expr];
18202             };
18203         };
18204     }()
18205 });
18206 /**
18207  * @class Ext.data.writer.Json
18208  * @extends Ext.data.writer.Writer
18209
18210 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
18211 The {@link #allowSingle} configuration can be set to false to force the records to always be
18212 encoded in an array, even if there is only a single record being sent.
18213
18214  * @markdown
18215  */
18216 Ext.define('Ext.data.writer.Json', {
18217     extend: 'Ext.data.writer.Writer',
18218     alternateClassName: 'Ext.data.JsonWriter',
18219     alias: 'writer.json',
18220     
18221     /**
18222      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
18223      * Example generated request, using root: 'records':
18224 <pre><code>
18225 {'records': [{name: 'my record'}, {name: 'another record'}]}
18226 </code></pre>
18227      */
18228     root: undefined,
18229     
18230     /**
18231      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
18232      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
18233      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
18234      * sent to the server.
18235      */
18236     encode: false,
18237     
18238     /**
18239      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
18240      * one record being sent. When there is more than one record, they will always be encoded into an array.
18241      * Defaults to <tt>true</tt>. Example:
18242      * <pre><code>
18243 // with allowSingle: true
18244 "root": {
18245     "first": "Mark",
18246     "last": "Corrigan"
18247 }
18248
18249 // with allowSingle: false
18250 "root": [{
18251     "first": "Mark",
18252     "last": "Corrigan"
18253 }]
18254      * </code></pre>
18255      */
18256     allowSingle: true,
18257     
18258     //inherit docs
18259     writeRecords: function(request, data) {
18260         var root = this.root;
18261         
18262         if (this.allowSingle && data.length == 1) {
18263             // convert to single object format
18264             data = data[0];
18265         }
18266         
18267         if (this.encode) {
18268             if (root) {
18269                 // sending as a param, need to encode
18270                 request.params[root] = Ext.encode(data);
18271             } else {
18272                 //<debug>
18273                 Ext.Error.raise('Must specify a root when using encode');
18274                 //</debug>
18275             }
18276         } else {
18277             // send as jsonData
18278             request.jsonData = request.jsonData || {};
18279             if (root) {
18280                 request.jsonData[root] = data;
18281             } else {
18282                 request.jsonData = data;
18283             }
18284         }
18285         return request;
18286     }
18287 });
18288
18289 /**
18290  * @author Ed Spencer
18291  *
18292  * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
18293  * data. Usually developers will not need to create or interact with proxies directly.
18294  *
18295  * # Types of Proxy
18296  *
18297  * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
18298  * The Client proxies save their data locally and include the following subclasses:
18299  *
18300  * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
18301  * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
18302  * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
18303  *
18304  * The Server proxies save their data by sending requests to some remote server. These proxies include:
18305  *
18306  * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
18307  * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
18308  * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
18309  *
18310  * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
18311  * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
18312  * respectively. Each Proxy subclass implements these functions.
18313  *
18314  * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
18315  * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
18316  * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
18317  * method also accepts a callback function to be called asynchronously on completion.
18318  *
18319  * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
18320  * method.
18321  */
18322 Ext.define('Ext.data.proxy.Proxy', {
18323     alias: 'proxy.proxy',
18324     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
18325     requires: [
18326         'Ext.data.reader.Json',
18327         'Ext.data.writer.Json'
18328     ],
18329     uses: [
18330         'Ext.data.Batch', 
18331         'Ext.data.Operation', 
18332         'Ext.data.Model'
18333     ],
18334     mixins: {
18335         observable: 'Ext.util.Observable'
18336     },
18337     
18338     /**
18339      * @cfg {String} batchOrder
18340      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
18341      * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
18342      */
18343     batchOrder: 'create,update,destroy',
18344     
18345     /**
18346      * @cfg {Boolean} batchActions
18347      * True to batch actions of a particular type when synchronizing the store. Defaults to true.
18348      */
18349     batchActions: true,
18350     
18351     /**
18352      * @cfg {String} defaultReaderType
18353      * The default registered reader type. Defaults to 'json'.
18354      * @private
18355      */
18356     defaultReaderType: 'json',
18357     
18358     /**
18359      * @cfg {String} defaultWriterType
18360      * The default registered writer type. Defaults to 'json'.
18361      * @private
18362      */
18363     defaultWriterType: 'json',
18364     
18365     /**
18366      * @cfg {String/Ext.data.Model} model
18367      * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
18368      * Model constructor. Required.
18369      */
18370     
18371     /**
18372      * @cfg {Object/String/Ext.data.reader.Reader} reader
18373      * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
18374      * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
18375      */
18376     
18377     /**
18378      * @cfg {Object/String/Ext.data.writer.Writer} writer
18379      * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
18380      * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
18381      */
18382     
18383     isProxy: true,
18384     
18385     /**
18386      * Creates the Proxy
18387      * @param {Object} config (optional) Config object.
18388      */
18389     constructor: function(config) {
18390         config = config || {};
18391         
18392         if (config.model === undefined) {
18393             delete config.model;
18394         }
18395
18396         this.mixins.observable.constructor.call(this, config);
18397         
18398         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
18399             this.setModel(this.model);
18400         }
18401     },
18402     
18403     /**
18404      * Sets the model associated with this proxy. This will only usually be called by a Store
18405      *
18406      * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
18407      * or a reference to the model's constructor
18408      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
18409      */
18410     setModel: function(model, setOnStore) {
18411         this.model = Ext.ModelManager.getModel(model);
18412         
18413         var reader = this.reader,
18414             writer = this.writer;
18415         
18416         this.setReader(reader);
18417         this.setWriter(writer);
18418         
18419         if (setOnStore && this.store) {
18420             this.store.setModel(this.model);
18421         }
18422     },
18423     
18424     /**
18425      * Returns the model attached to this Proxy
18426      * @return {Ext.data.Model} The model
18427      */
18428     getModel: function() {
18429         return this.model;
18430     },
18431     
18432     /**
18433      * Sets the Proxy's Reader by string, config object or Reader instance
18434      *
18435      * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
18436      * a configuration object or an Ext.data.reader.Reader instance
18437      * @return {Ext.data.reader.Reader} The attached Reader object
18438      */
18439     setReader: function(reader) {
18440         var me = this;
18441         
18442         if (reader === undefined || typeof reader == 'string') {
18443             reader = {
18444                 type: reader
18445             };
18446         }
18447
18448         if (reader.isReader) {
18449             reader.setModel(me.model);
18450         } else {
18451             Ext.applyIf(reader, {
18452                 proxy: me,
18453                 model: me.model,
18454                 type : me.defaultReaderType
18455             });
18456
18457             reader = Ext.createByAlias('reader.' + reader.type, reader);
18458         }
18459         
18460         me.reader = reader;
18461         return me.reader;
18462     },
18463     
18464     /**
18465      * Returns the reader currently attached to this proxy instance
18466      * @return {Ext.data.reader.Reader} The Reader instance
18467      */
18468     getReader: function() {
18469         return this.reader;
18470     },
18471     
18472     /**
18473      * Sets the Proxy's Writer by string, config object or Writer instance
18474      *
18475      * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
18476      * a configuration object or an Ext.data.writer.Writer instance
18477      * @return {Ext.data.writer.Writer} The attached Writer object
18478      */
18479     setWriter: function(writer) {
18480         if (writer === undefined || typeof writer == 'string') {
18481             writer = {
18482                 type: writer
18483             };
18484         }
18485
18486         if (!(writer instanceof Ext.data.writer.Writer)) {
18487             Ext.applyIf(writer, {
18488                 model: this.model,
18489                 type : this.defaultWriterType
18490             });
18491
18492             writer = Ext.createByAlias('writer.' + writer.type, writer);
18493         }
18494         
18495         this.writer = writer;
18496         
18497         return this.writer;
18498     },
18499     
18500     /**
18501      * Returns the writer currently attached to this proxy instance
18502      * @return {Ext.data.writer.Writer} The Writer instance
18503      */
18504     getWriter: function() {
18505         return this.writer;
18506     },
18507     
18508     /**
18509      * Performs the given create operation.
18510      * @param {Ext.data.Operation} operation The Operation to perform
18511      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
18512      * @param {Object} scope Scope to execute the callback function in
18513      * @method
18514      */
18515     create: Ext.emptyFn,
18516     
18517     /**
18518      * Performs the given read operation.
18519      * @param {Ext.data.Operation} operation The Operation to perform
18520      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
18521      * @param {Object} scope Scope to execute the callback function in
18522      * @method
18523      */
18524     read: Ext.emptyFn,
18525     
18526     /**
18527      * Performs the given update operation.
18528      * @param {Ext.data.Operation} operation The Operation to perform
18529      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
18530      * @param {Object} scope Scope to execute the callback function in
18531      * @method
18532      */
18533     update: Ext.emptyFn,
18534     
18535     /**
18536      * Performs the given destroy operation.
18537      * @param {Ext.data.Operation} operation The Operation to perform
18538      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
18539      * @param {Object} scope Scope to execute the callback function in
18540      * @method
18541      */
18542     destroy: Ext.emptyFn,
18543     
18544     /**
18545      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
18546      * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
18547      *
18548      *     myProxy.batch({
18549      *         create : [myModel1, myModel2],
18550      *         update : [myModel3],
18551      *         destroy: [myModel4, myModel5]
18552      *     });
18553      *
18554      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
18555      * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
18556      * saved but should now be destroyed.
18557      *
18558      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
18559      * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
18560      * see {@link Ext.data.Batch}
18561      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
18562      */
18563     batch: function(operations, listeners) {
18564         var me = this,
18565             batch = Ext.create('Ext.data.Batch', {
18566                 proxy: me,
18567                 listeners: listeners || {}
18568             }),
18569             useBatch = me.batchActions, 
18570             records;
18571         
18572         Ext.each(me.batchOrder.split(','), function(action) {
18573             records = operations[action];
18574             if (records) {
18575                 if (useBatch) {
18576                     batch.add(Ext.create('Ext.data.Operation', {
18577                         action: action,
18578                         records: records
18579                     }));
18580                 } else {
18581                     Ext.each(records, function(record){
18582                         batch.add(Ext.create('Ext.data.Operation', {
18583                             action : action, 
18584                             records: [record]
18585                         }));
18586                     });
18587                 }
18588             }
18589         }, me);
18590         
18591         batch.start();
18592         return batch;
18593     }
18594 }, function() {
18595     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
18596     
18597     //backwards compatibility
18598     Ext.data.DataProxy = this;
18599     // Ext.deprecate('platform', '2.0', function() {
18600     //     Ext.data.DataProxy = this;
18601     // }, this);
18602 });
18603
18604 /**
18605  * @author Ed Spencer
18606  *
18607  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
18608  * would not usually be used directly.
18609  *
18610  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
18611  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
18612  * an alias of AjaxProxy).
18613  * @private
18614  */
18615 Ext.define('Ext.data.proxy.Server', {
18616     extend: 'Ext.data.proxy.Proxy',
18617     alias : 'proxy.server',
18618     alternateClassName: 'Ext.data.ServerProxy',
18619     uses  : ['Ext.data.Request'],
18620
18621     /**
18622      * @cfg {String} url
18623      * The URL from which to request the data object.
18624      */
18625
18626     /**
18627      * @cfg {String} pageParam
18628      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
18629      * want to send a page parameter.
18630      */
18631     pageParam: 'page',
18632
18633     /**
18634      * @cfg {String} startParam
18635      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
18636      * want to send a start parameter.
18637      */
18638     startParam: 'start',
18639
18640     /**
18641      * @cfg {String} limitParam
18642      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
18643      * want to send a limit parameter.
18644      */
18645     limitParam: 'limit',
18646
18647     /**
18648      * @cfg {String} groupParam
18649      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
18650      * want to send a group parameter.
18651      */
18652     groupParam: 'group',
18653
18654     /**
18655      * @cfg {String} sortParam
18656      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
18657      * want to send a sort parameter.
18658      */
18659     sortParam: 'sort',
18660
18661     /**
18662      * @cfg {String} filterParam
18663      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
18664      * want to send a filter parameter.
18665      */
18666     filterParam: 'filter',
18667
18668     /**
18669      * @cfg {String} directionParam
18670      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
18671      * true.** Defaults to 'dir'.
18672      */
18673     directionParam: 'dir',
18674
18675     /**
18676      * @cfg {Boolean} simpleSortMode
18677      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
18678      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
18679      * or 'DESC'.
18680      */
18681     simpleSortMode: false,
18682
18683     /**
18684      * @cfg {Boolean} noCache
18685      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
18686      */
18687     noCache : true,
18688
18689     /**
18690      * @cfg {String} cacheString
18691      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
18692      */
18693     cacheString: "_dc",
18694
18695     /**
18696      * @cfg {Number} timeout
18697      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
18698      */
18699     timeout : 30000,
18700
18701     /**
18702      * @cfg {Object} api
18703      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
18704      *
18705      *     api: {
18706      *         create  : undefined,
18707      *         read    : undefined,
18708      *         update  : undefined,
18709      *         destroy : undefined
18710      *     }
18711      *
18712      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
18713      * {@link #api} property, or if undefined default to the configured
18714      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
18715      *
18716      * For example:
18717      *
18718      *     api: {
18719      *         create  : '/controller/new',
18720      *         read    : '/controller/load',
18721      *         update  : '/controller/update',
18722      *         destroy : '/controller/destroy_action'
18723      *     }
18724      *
18725      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
18726      * configured {@link Ext.data.proxy.Server#url url}.
18727      */
18728
18729     constructor: function(config) {
18730         var me = this;
18731
18732         config = config || {};
18733         this.addEvents(
18734             /**
18735              * @event exception
18736              * Fires when the server returns an exception
18737              * @param {Ext.data.proxy.Proxy} this
18738              * @param {Object} response The response from the AJAX request
18739              * @param {Ext.data.Operation} operation The operation that triggered request
18740              */
18741             'exception'
18742         );
18743         me.callParent([config]);
18744
18745         /**
18746          * @cfg {Object} extraParams
18747          * Extra parameters that will be included on every request. Individual requests with params of the same name
18748          * will override these params when they are in conflict.
18749          */
18750         me.extraParams = config.extraParams || {};
18751
18752         me.api = config.api || {};
18753
18754         //backwards compatibility, will be deprecated in 5.0
18755         me.nocache = me.noCache;
18756     },
18757
18758     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
18759     create: function() {
18760         return this.doRequest.apply(this, arguments);
18761     },
18762
18763     read: function() {
18764         return this.doRequest.apply(this, arguments);
18765     },
18766
18767     update: function() {
18768         return this.doRequest.apply(this, arguments);
18769     },
18770
18771     destroy: function() {
18772         return this.doRequest.apply(this, arguments);
18773     },
18774
18775     /**
18776      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
18777      * that this Proxy is attached to.
18778      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
18779      * @return {Ext.data.Request} The request object
18780      */
18781     buildRequest: function(operation) {
18782         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
18783             request;
18784
18785         //copy any sorters, filters etc into the params so they can be sent over the wire
18786         params = Ext.applyIf(params, this.getParams(operation));
18787
18788         if (operation.id && !params.id) {
18789             params.id = operation.id;
18790         }
18791
18792         request = Ext.create('Ext.data.Request', {
18793             params   : params,
18794             action   : operation.action,
18795             records  : operation.records,
18796             operation: operation,
18797             url      : operation.url
18798         });
18799
18800         request.url = this.buildUrl(request);
18801
18802         /*
18803          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
18804          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
18805          */
18806         operation.request = request;
18807
18808         return request;
18809     },
18810
18811     // Should this be documented as protected method?
18812     processResponse: function(success, operation, request, response, callback, scope){
18813         var me = this,
18814             reader,
18815             result;
18816
18817         if (success === true) {
18818             reader = me.getReader();
18819             result = reader.read(me.extractResponseData(response));
18820
18821             if (result.success !== false) {
18822                 //see comment in buildRequest for why we include the response object here
18823                 Ext.apply(operation, {
18824                     response: response,
18825                     resultSet: result
18826                 });
18827
18828                 operation.commitRecords(result.records);
18829                 operation.setCompleted();
18830                 operation.setSuccessful();
18831             } else {
18832                 operation.setException(result.message);
18833                 me.fireEvent('exception', this, response, operation);
18834             }
18835         } else {
18836             me.setException(operation, response);
18837             me.fireEvent('exception', this, response, operation);
18838         }
18839
18840         //this callback is the one that was passed to the 'read' or 'write' function above
18841         if (typeof callback == 'function') {
18842             callback.call(scope || me, operation);
18843         }
18844
18845         me.afterRequest(request, success);
18846     },
18847
18848     /**
18849      * Sets up an exception on the operation
18850      * @private
18851      * @param {Ext.data.Operation} operation The operation
18852      * @param {Object} response The response
18853      */
18854     setException: function(operation, response){
18855         operation.setException({
18856             status: response.status,
18857             statusText: response.statusText
18858         });
18859     },
18860
18861     /**
18862      * Template method to allow subclasses to specify how to get the response for the reader.
18863      * @template
18864      * @private
18865      * @param {Object} response The server response
18866      * @return {Object} The response data to be used by the reader
18867      */
18868     extractResponseData: function(response){
18869         return response;
18870     },
18871
18872     /**
18873      * Encode any values being sent to the server. Can be overridden in subclasses.
18874      * @private
18875      * @param {Array} An array of sorters/filters.
18876      * @return {Object} The encoded value
18877      */
18878     applyEncoding: function(value){
18879         return Ext.encode(value);
18880     },
18881
18882     /**
18883      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
18884      * this simply JSON-encodes the sorter data
18885      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
18886      * @return {String} The encoded sorters
18887      */
18888     encodeSorters: function(sorters) {
18889         var min = [],
18890             length = sorters.length,
18891             i = 0;
18892
18893         for (; i < length; i++) {
18894             min[i] = {
18895                 property : sorters[i].property,
18896                 direction: sorters[i].direction
18897             };
18898         }
18899         return this.applyEncoding(min);
18900
18901     },
18902
18903     /**
18904      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
18905      * this simply JSON-encodes the filter data
18906      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
18907      * @return {String} The encoded filters
18908      */
18909     encodeFilters: function(filters) {
18910         var min = [],
18911             length = filters.length,
18912             i = 0;
18913
18914         for (; i < length; i++) {
18915             min[i] = {
18916                 property: filters[i].property,
18917                 value   : filters[i].value
18918             };
18919         }
18920         return this.applyEncoding(min);
18921     },
18922
18923     /**
18924      * @private
18925      * Copy any sorters, filters etc into the params so they can be sent over the wire
18926      */
18927     getParams: function(operation) {
18928         var me             = this,
18929             params         = {},
18930             isDef          = Ext.isDefined,
18931             groupers       = operation.groupers,
18932             sorters        = operation.sorters,
18933             filters        = operation.filters,
18934             page           = operation.page,
18935             start          = operation.start,
18936             limit          = operation.limit,
18937
18938             simpleSortMode = me.simpleSortMode,
18939
18940             pageParam      = me.pageParam,
18941             startParam     = me.startParam,
18942             limitParam     = me.limitParam,
18943             groupParam     = me.groupParam,
18944             sortParam      = me.sortParam,
18945             filterParam    = me.filterParam,
18946             directionParam = me.directionParam;
18947
18948         if (pageParam && isDef(page)) {
18949             params[pageParam] = page;
18950         }
18951
18952         if (startParam && isDef(start)) {
18953             params[startParam] = start;
18954         }
18955
18956         if (limitParam && isDef(limit)) {
18957             params[limitParam] = limit;
18958         }
18959
18960         if (groupParam && groupers && groupers.length > 0) {
18961             // Grouper is a subclass of sorter, so we can just use the sorter method
18962             params[groupParam] = me.encodeSorters(groupers);
18963         }
18964
18965         if (sortParam && sorters && sorters.length > 0) {
18966             if (simpleSortMode) {
18967                 params[sortParam] = sorters[0].property;
18968                 params[directionParam] = sorters[0].direction;
18969             } else {
18970                 params[sortParam] = me.encodeSorters(sorters);
18971             }
18972
18973         }
18974
18975         if (filterParam && filters && filters.length > 0) {
18976             params[filterParam] = me.encodeFilters(filters);
18977         }
18978
18979         return params;
18980     },
18981
18982     /**
18983      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
18984      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
18985      * @param {Ext.data.Request} request The request object
18986      * @return {String} The url
18987      */
18988     buildUrl: function(request) {
18989         var me = this,
18990             url = me.getUrl(request);
18991
18992         //<debug>
18993         if (!url) {
18994             Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
18995         }
18996         //</debug>
18997
18998         if (me.noCache) {
18999             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
19000         }
19001
19002         return url;
19003     },
19004
19005     /**
19006      * Get the url for the request taking into account the order of priority,
19007      * - The request
19008      * - The api
19009      * - The url
19010      * @private
19011      * @param {Ext.data.Request} request The request
19012      * @return {String} The url
19013      */
19014     getUrl: function(request){
19015         return request.url || this.api[request.action] || this.url;
19016     },
19017
19018     /**
19019      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
19020      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
19021      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
19022      * each of the methods that delegate to it.
19023      *
19024      * @param {Ext.data.Operation} operation The Ext.data.Operation object
19025      * @param {Function} callback The callback function to call when the Operation has completed
19026      * @param {Object} scope The scope in which to execute the callback
19027      */
19028     doRequest: function(operation, callback, scope) {
19029         //<debug>
19030         Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
19031         //</debug>
19032     },
19033
19034     /**
19035      * Optional callback function which can be used to clean up after a request has been completed.
19036      * @param {Ext.data.Request} request The Request object
19037      * @param {Boolean} success True if the request was successful
19038      * @method
19039      */
19040     afterRequest: Ext.emptyFn,
19041
19042     onDestroy: function() {
19043         Ext.destroy(this.reader, this.writer);
19044     }
19045 });
19046
19047 /**
19048  * @author Ed Spencer
19049  *
19050  * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
19051  * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
19052  * 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
19053  * Model}:
19054  *
19055  *     Ext.define('User', {
19056  *         extend: 'Ext.data.Model',
19057  *         fields: ['id', 'name', 'email']
19058  *     });
19059  *
19060  *     //The Store contains the AjaxProxy as an inline configuration
19061  *     var store = Ext.create('Ext.data.Store', {
19062  *         model: 'User',
19063  *         proxy: {
19064  *             type: 'ajax',
19065  *             url : 'users.json'
19066  *         }
19067  *     });
19068  *
19069  *     store.load();
19070  *
19071  * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
19072  * the fields that we expect the server to return. Next we set up the Store itself, along with a
19073  * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
19074  * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
19075  * It's as if we'd done this:
19076  *
19077  *     new Ext.data.proxy.Ajax({
19078  *         url: 'users.json',
19079  *         model: 'User',
19080  *         reader: 'json'
19081  *     });
19082  *
19083  * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
19084  * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
19085  * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
19086  *
19087  * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
19088  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
19089  * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
19090  * will be sent as a POST request).
19091  *
19092  * # Limitations
19093  *
19094  * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
19095  * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
19096  * talking to each other via AJAX.
19097  *
19098  * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
19099  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
19100  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
19101  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
19102  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
19103  *
19104  * # Readers and Writers
19105  *
19106  * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
19107  * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
19108  * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
19109  * Ext.data.reader.Reader Reader} instance:
19110  *
19111  *     var proxy = new Ext.data.proxy.Ajax({
19112  *         model: 'User',
19113  *         reader: {
19114  *             type: 'xml',
19115  *             root: 'users'
19116  *         }
19117  *     });
19118  *
19119  *     proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
19120  *
19121  * # Url generation
19122  *
19123  * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
19124  * each request. These are controlled with the following configuration options:
19125  *
19126  * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
19127  * - {@link #sortParam} - controls how sort information is sent to the server
19128  * - {@link #groupParam} - controls how grouping information is sent to the server
19129  * - {@link #filterParam} - controls how filter information is sent to the server
19130  *
19131  * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
19132  * the generated urls, let's say we're loading the Proxy with the following Operation:
19133  *
19134  *     var operation = new Ext.data.Operation({
19135  *         action: 'read',
19136  *         page  : 2
19137  *     });
19138  *
19139  * Now we'll issue the request for this Operation by calling {@link #read}:
19140  *
19141  *     var proxy = new Ext.data.proxy.Ajax({
19142  *         url: '/users'
19143  *     });
19144  *
19145  *     proxy.read(operation); //GET /users?page=2
19146  *
19147  * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
19148  * to the server:
19149  *
19150  *     var proxy = new Ext.data.proxy.Ajax({
19151  *         url: '/users',
19152  *         pagePage: 'pageNumber'
19153  *     });
19154  *
19155  *     proxy.read(operation); //GET /users?pageNumber=2
19156  *
19157  * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
19158  *
19159  *     var operation = new Ext.data.Operation({
19160  *         action: 'read',
19161  *         start : 50,
19162  *         limit : 25
19163  *     });
19164  *
19165  *     var proxy = new Ext.data.proxy.Ajax({
19166  *         url: '/users'
19167  *     });
19168  *
19169  *     proxy.read(operation); //GET /users?start=50&limit;=25
19170  *
19171  * Again we can customize this url:
19172  *
19173  *     var proxy = new Ext.data.proxy.Ajax({
19174  *         url: '/users',
19175  *         startParam: 'startIndex',
19176  *         limitParam: 'limitIndex'
19177  *     });
19178  *
19179  *     proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
19180  *
19181  * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
19182  * expressive Operation object:
19183  *
19184  *     var operation = new Ext.data.Operation({
19185  *         action: 'read',
19186  *         sorters: [
19187  *             new Ext.util.Sorter({
19188  *                 property : 'name',
19189  *                 direction: 'ASC'
19190  *             }),
19191  *             new Ext.util.Sorter({
19192  *                 property : 'age',
19193  *                 direction: 'DESC'
19194  *             })
19195  *         ],
19196  *         filters: [
19197  *             new Ext.util.Filter({
19198  *                 property: 'eyeColor',
19199  *                 value   : 'brown'
19200  *             })
19201  *         ]
19202  *     });
19203  *
19204  * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
19205  * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
19206  * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
19207  *
19208  *     var proxy = new Ext.data.proxy.Ajax({
19209  *         url: '/users'
19210  *     });
19211  *
19212  *     proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
19213  *
19214  * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
19215  * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
19216  * that format like this:
19217  *
19218  *      var proxy = new Ext.data.proxy.Ajax({
19219  *          url: '/users',
19220  *          sortParam: 'sortBy',
19221  *          filterParam: 'filterBy',
19222  *
19223  *          //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
19224  *          encodeSorters: function(sorters) {
19225  *              var length   = sorters.length,
19226  *                  sortStrs = [],
19227  *                  sorter, i;
19228  *
19229  *              for (i = 0; i < length; i++) {
19230  *                  sorter = sorters[i];
19231  *
19232  *                  sortStrs[i] = sorter.property + '#' + sorter.direction
19233  *              }
19234  *
19235  *              return sortStrs.join(",");
19236  *          }
19237  *      });
19238  *
19239  *      proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
19240  *
19241  * We can also provide a custom {@link #encodeFilters} function to encode our filters.
19242  *
19243  * @constructor
19244  * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
19245  * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
19246  * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
19247  *
19248  * @param {Object} config (optional) Config object.
19249  * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
19250  */
19251 Ext.define('Ext.data.proxy.Ajax', {
19252     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
19253     extend: 'Ext.data.proxy.Server',
19254     alias: 'proxy.ajax',
19255     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
19256     
19257     /**
19258      * @property {Object} actionMethods
19259      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
19260      * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
19261      * correct RESTful methods.
19262      */
19263     actionMethods: {
19264         create : 'POST',
19265         read   : 'GET',
19266         update : 'POST',
19267         destroy: 'POST'
19268     },
19269     
19270     /**
19271      * @cfg {Object} headers
19272      * Any headers to add to the Ajax request. Defaults to undefined.
19273      */
19274     
19275     /**
19276      * @ignore
19277      */
19278     doRequest: function(operation, callback, scope) {
19279         var writer  = this.getWriter(),
19280             request = this.buildRequest(operation, callback, scope);
19281             
19282         if (operation.allowWrite()) {
19283             request = writer.write(request);
19284         }
19285         
19286         Ext.apply(request, {
19287             headers       : this.headers,
19288             timeout       : this.timeout,
19289             scope         : this,
19290             callback      : this.createRequestCallback(request, operation, callback, scope),
19291             method        : this.getMethod(request),
19292             disableCaching: false // explicitly set it to false, ServerProxy handles caching
19293         });
19294         
19295         Ext.Ajax.request(request);
19296         
19297         return request;
19298     },
19299     
19300     /**
19301      * Returns the HTTP method name for a given request. By default this returns based on a lookup on
19302      * {@link #actionMethods}.
19303      * @param {Ext.data.Request} request The request object
19304      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
19305      */
19306     getMethod: function(request) {
19307         return this.actionMethods[request.action];
19308     },
19309     
19310     /**
19311      * @private
19312      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
19313      * of code duplication inside the returned function so we need to find a way to DRY this up.
19314      * @param {Ext.data.Request} request The Request object
19315      * @param {Ext.data.Operation} operation The Operation being executed
19316      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
19317      * passed to doRequest
19318      * @param {Object} scope The scope in which to execute the callback function
19319      * @return {Function} The callback function
19320      */
19321     createRequestCallback: function(request, operation, callback, scope) {
19322         var me = this;
19323         
19324         return function(options, success, response) {
19325             me.processResponse(success, operation, request, response, callback, scope);
19326         };
19327     }
19328 }, function() {
19329     //backwards compatibility, remove in Ext JS 5.0
19330     Ext.data.HttpProxy = this;
19331 });
19332
19333 /**
19334  * @author Ed Spencer
19335  *
19336  * A Model represents some object that your application manages. For example, one might define a Model for Users,
19337  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
19338  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
19339  * of the data-bound components in Ext.
19340  *
19341  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
19342  *
19343  *     Ext.define('User', {
19344  *         extend: 'Ext.data.Model',
19345  *         fields: [
19346  *             {name: 'name',  type: 'string'},
19347  *             {name: 'age',   type: 'int'},
19348  *             {name: 'phone', type: 'string'},
19349  *             {name: 'alive', type: 'boolean', defaultValue: true}
19350  *         ],
19351  *
19352  *         changeName: function() {
19353  *             var oldName = this.get('name'),
19354  *                 newName = oldName + " The Barbarian";
19355  *
19356  *             this.set('name', newName);
19357  *         }
19358  *     });
19359  *
19360  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
19361  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
19362  *
19363  * Now we can create instances of our User model and call any model logic we defined:
19364  *
19365  *     var user = Ext.create('User', {
19366  *         name : 'Conan',
19367  *         age  : 24,
19368  *         phone: '555-555-5555'
19369  *     });
19370  *
19371  *     user.changeName();
19372  *     user.get('name'); //returns "Conan The Barbarian"
19373  *
19374  * # Validations
19375  *
19376  * Models have built-in support for validations, which are executed against the validator functions in {@link
19377  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
19378  * models:
19379  *
19380  *     Ext.define('User', {
19381  *         extend: 'Ext.data.Model',
19382  *         fields: [
19383  *             {name: 'name',     type: 'string'},
19384  *             {name: 'age',      type: 'int'},
19385  *             {name: 'phone',    type: 'string'},
19386  *             {name: 'gender',   type: 'string'},
19387  *             {name: 'username', type: 'string'},
19388  *             {name: 'alive',    type: 'boolean', defaultValue: true}
19389  *         ],
19390  *
19391  *         validations: [
19392  *             {type: 'presence',  field: 'age'},
19393  *             {type: 'length',    field: 'name',     min: 2},
19394  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
19395  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
19396  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
19397  *         ]
19398  *     });
19399  *
19400  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
19401  * object:
19402  *
19403  *     var instance = Ext.create('User', {
19404  *         name: 'Ed',
19405  *         gender: 'Male',
19406  *         username: 'edspencer'
19407  *     });
19408  *
19409  *     var errors = instance.validate();
19410  *
19411  * # Associations
19412  *
19413  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
19414  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
19415  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
19416  * this:
19417  *
19418  *     Ext.define('Post', {
19419  *         extend: 'Ext.data.Model',
19420  *         fields: ['id', 'user_id'],
19421  *
19422  *         belongsTo: 'User',
19423  *         hasMany  : {model: 'Comment', name: 'comments'}
19424  *     });
19425  *
19426  *     Ext.define('Comment', {
19427  *         extend: 'Ext.data.Model',
19428  *         fields: ['id', 'user_id', 'post_id'],
19429  *
19430  *         belongsTo: 'Post'
19431  *     });
19432  *
19433  *     Ext.define('User', {
19434  *         extend: 'Ext.data.Model',
19435  *         fields: ['id'],
19436  *
19437  *         hasMany: [
19438  *             'Post',
19439  *             {model: 'Comment', name: 'comments'}
19440  *         ]
19441  *     });
19442  *
19443  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
19444  * usage and configuration of associations. Note that associations can also be specified like this:
19445  *
19446  *     Ext.define('User', {
19447  *         extend: 'Ext.data.Model',
19448  *         fields: ['id'],
19449  *
19450  *         associations: [
19451  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
19452  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
19453  *         ]
19454  *     });
19455  *
19456  * # Using a Proxy
19457  *
19458  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
19459  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
19460  * can be set directly on the Model:
19461  *
19462  *     Ext.define('User', {
19463  *         extend: 'Ext.data.Model',
19464  *         fields: ['id', 'name', 'email'],
19465  *
19466  *         proxy: {
19467  *             type: 'rest',
19468  *             url : '/users'
19469  *         }
19470  *     });
19471  *
19472  * 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
19473  * RESTful backend. Let's see how this works:
19474  *
19475  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
19476  *
19477  *     user.save(); //POST /users
19478  *
19479  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
19480  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
19481  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
19482  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
19483  *
19484  * Loading data via the Proxy is equally easy:
19485  *
19486  *     //get a reference to the User model class
19487  *     var User = Ext.ModelManager.getModel('User');
19488  *
19489  *     //Uses the configured RestProxy to make a GET request to /users/123
19490  *     User.load(123, {
19491  *         success: function(user) {
19492  *             console.log(user.getId()); //logs 123
19493  *         }
19494  *     });
19495  *
19496  * Models can also be updated and destroyed easily:
19497  *
19498  *     //the user Model we loaded in the last snippet:
19499  *     user.set('name', 'Edward Spencer');
19500  *
19501  *     //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
19502  *     user.save({
19503  *         success: function() {
19504  *             console.log('The User was updated');
19505  *         }
19506  *     });
19507  *
19508  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
19509  *     user.destroy({
19510  *         success: function() {
19511  *             console.log('The User was destroyed!');
19512  *         }
19513  *     });
19514  *
19515  * # Usage in Stores
19516  *
19517  * 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
19518  * creating a {@link Ext.data.Store Store}:
19519  *
19520  *     var store = Ext.create('Ext.data.Store', {
19521  *         model: 'User'
19522  *     });
19523  *
19524  *     //uses the Proxy we set up on Model to load the Store data
19525  *     store.load();
19526  *
19527  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
19528  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
19529  * Ext.data.Store Store docs} for more information on Stores.
19530  *
19531  * @constructor
19532  * Creates new Model instance.
19533  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
19534  * @param {Number} id (optional) Unique ID to assign to this model instance
19535  */
19536 Ext.define('Ext.data.Model', {
19537     alternateClassName: 'Ext.data.Record',
19538
19539     mixins: {
19540         observable: 'Ext.util.Observable'
19541     },
19542
19543     requires: [
19544         'Ext.ModelManager',
19545         'Ext.data.IdGenerator',
19546         'Ext.data.Field',
19547         'Ext.data.Errors',
19548         'Ext.data.Operation',
19549         'Ext.data.validations',
19550         'Ext.data.proxy.Ajax',
19551         'Ext.util.MixedCollection'
19552     ],
19553
19554     onClassExtended: function(cls, data) {
19555         var onBeforeClassCreated = data.onBeforeClassCreated;
19556
19557         data.onBeforeClassCreated = function(cls, data) {
19558             var me = this,
19559                 name = Ext.getClassName(cls),
19560                 prototype = cls.prototype,
19561                 superCls = cls.prototype.superclass,
19562
19563                 validations = data.validations || [],
19564                 fields = data.fields || [],
19565                 associations = data.associations || [],
19566                 belongsTo = data.belongsTo,
19567                 hasMany = data.hasMany,
19568                 idgen = data.idgen,
19569
19570                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
19571                     return field.name;
19572                 }),
19573
19574                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
19575                     return association.name;
19576                 }),
19577
19578                 superValidations = superCls.validations,
19579                 superFields = superCls.fields,
19580                 superAssociations = superCls.associations,
19581
19582                 association, i, ln,
19583                 dependencies = [];
19584
19585             // Save modelName on class and its prototype
19586             cls.modelName = name;
19587             prototype.modelName = name;
19588
19589             // Merge the validations of the superclass and the new subclass
19590             if (superValidations) {
19591                 validations = superValidations.concat(validations);
19592             }
19593
19594             data.validations = validations;
19595
19596             // Merge the fields of the superclass and the new subclass
19597             if (superFields) {
19598                 fields = superFields.items.concat(fields);
19599             }
19600
19601             for (i = 0, ln = fields.length; i < ln; ++i) {
19602                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
19603             }
19604
19605             data.fields = fieldsMixedCollection;
19606
19607             if (idgen) {
19608                 data.idgen = Ext.data.IdGenerator.get(idgen);
19609             }
19610
19611             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
19612             //we support that here
19613             if (belongsTo) {
19614                 belongsTo = Ext.Array.from(belongsTo);
19615
19616                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
19617                     association = belongsTo[i];
19618
19619                     if (!Ext.isObject(association)) {
19620                         association = {model: association};
19621                     }
19622
19623                     association.type = 'belongsTo';
19624                     associations.push(association);
19625                 }
19626
19627                 delete data.belongsTo;
19628             }
19629
19630             if (hasMany) {
19631                 hasMany = Ext.Array.from(hasMany);
19632                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
19633                     association = hasMany[i];
19634
19635                     if (!Ext.isObject(association)) {
19636                         association = {model: association};
19637                     }
19638
19639                     association.type = 'hasMany';
19640                     associations.push(association);
19641                 }
19642
19643                 delete data.hasMany;
19644             }
19645
19646             if (superAssociations) {
19647                 associations = superAssociations.items.concat(associations);
19648             }
19649
19650             for (i = 0, ln = associations.length; i < ln; ++i) {
19651                 dependencies.push('association.' + associations[i].type.toLowerCase());
19652             }
19653
19654             if (data.proxy) {
19655                 if (typeof data.proxy === 'string') {
19656                     dependencies.push('proxy.' + data.proxy);
19657                 }
19658                 else if (typeof data.proxy.type === 'string') {
19659                     dependencies.push('proxy.' + data.proxy.type);
19660                 }
19661             }
19662
19663             Ext.require(dependencies, function() {
19664                 Ext.ModelManager.registerType(name, cls);
19665
19666                 for (i = 0, ln = associations.length; i < ln; ++i) {
19667                     association = associations[i];
19668
19669                     Ext.apply(association, {
19670                         ownerModel: name,
19671                         associatedModel: association.model
19672                     });
19673
19674                     if (Ext.ModelManager.getModel(association.model) === undefined) {
19675                         Ext.ModelManager.registerDeferredAssociation(association);
19676                     } else {
19677                         associationsMixedCollection.add(Ext.data.Association.create(association));
19678                     }
19679                 }
19680
19681                 data.associations = associationsMixedCollection;
19682
19683                 onBeforeClassCreated.call(me, cls, data);
19684
19685                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
19686
19687                 // Fire the onModelDefined template method on ModelManager
19688                 Ext.ModelManager.onModelDefined(cls);
19689             });
19690         };
19691     },
19692
19693     inheritableStatics: {
19694         /**
19695          * Sets the Proxy to use for this model. Accepts any options that can be accepted by
19696          * {@link Ext#createByAlias Ext.createByAlias}.
19697          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
19698          * @return {Ext.data.proxy.Proxy}
19699          * @static
19700          * @inheritable
19701          */
19702         setProxy: function(proxy) {
19703             //make sure we have an Ext.data.proxy.Proxy object
19704             if (!proxy.isProxy) {
19705                 if (typeof proxy == "string") {
19706                     proxy = {
19707                         type: proxy
19708                     };
19709                 }
19710                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
19711             }
19712             proxy.setModel(this);
19713             this.proxy = this.prototype.proxy = proxy;
19714
19715             return proxy;
19716         },
19717
19718         /**
19719          * Returns the configured Proxy for this Model
19720          * @return {Ext.data.proxy.Proxy} The proxy
19721          * @static
19722          * @inheritable
19723          */
19724         getProxy: function() {
19725             return this.proxy;
19726         },
19727
19728         /**
19729          * Asynchronously loads a model instance by id. Sample usage:
19730          *
19731          *     MyApp.User = Ext.define('User', {
19732          *         extend: 'Ext.data.Model',
19733          *         fields: [
19734          *             {name: 'id', type: 'int'},
19735          *             {name: 'name', type: 'string'}
19736          *         ]
19737          *     });
19738          *
19739          *     MyApp.User.load(10, {
19740          *         scope: this,
19741          *         failure: function(record, operation) {
19742          *             //do something if the load failed
19743          *         },
19744          *         success: function(record, operation) {
19745          *             //do something if the load succeeded
19746          *         },
19747          *         callback: function(record, operation) {
19748          *             //do something whether the load succeeded or failed
19749          *         }
19750          *     });
19751          *
19752          * @param {Number} id The id of the model to load
19753          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
19754          * optional scope
19755          * @static
19756          * @inheritable
19757          */
19758         load: function(id, config) {
19759             config = Ext.apply({}, config);
19760             config = Ext.applyIf(config, {
19761                 action: 'read',
19762                 id    : id
19763             });
19764
19765             var operation  = Ext.create('Ext.data.Operation', config),
19766                 scope      = config.scope || this,
19767                 record     = null,
19768                 callback;
19769
19770             callback = function(operation) {
19771                 if (operation.wasSuccessful()) {
19772                     record = operation.getRecords()[0];
19773                     Ext.callback(config.success, scope, [record, operation]);
19774                 } else {
19775                     Ext.callback(config.failure, scope, [record, operation]);
19776                 }
19777                 Ext.callback(config.callback, scope, [record, operation]);
19778             };
19779
19780             this.proxy.read(operation, callback, this);
19781         }
19782     },
19783
19784     statics: {
19785         PREFIX : 'ext-record',
19786         AUTO_ID: 1,
19787         EDIT   : 'edit',
19788         REJECT : 'reject',
19789         COMMIT : 'commit',
19790
19791         /**
19792          * Generates a sequential id. This method is typically called when a record is {@link Ext#create
19793          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
19794          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
19795          *
19796          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
19797          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
19798          *
19799          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
19800          * @return {String} auto-generated string id, `"ext-record-i++"`;
19801          * @static
19802          */
19803         id: function(rec) {
19804             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
19805             rec.phantom = true;
19806             rec.internalId = id;
19807             return id;
19808         }
19809     },
19810
19811     /**
19812      * @cfg {String/Object} idgen
19813      * The id generator to use for this model. The default id generator does not generate
19814      * values for the {@link #idProperty}.
19815      *
19816      * This can be overridden at the model level to provide a custom generator for a model.
19817      * The simplest form of this would be:
19818      *
19819      *      Ext.define('MyApp.data.MyModel', {
19820      *          extend: 'Ext.data.Model',
19821      *          requires: ['Ext.data.SequentialIdGenerator'],
19822      *          idgen: 'sequential',
19823      *          ...
19824      *      });
19825      *
19826      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
19827      * as 1, 2, 3 etc..
19828      *
19829      * Another useful id generator is {@link Ext.data.UuidGenerator}:
19830      *
19831      *      Ext.define('MyApp.data.MyModel', {
19832      *          extend: 'Ext.data.Model',
19833      *          requires: ['Ext.data.UuidGenerator'],
19834      *          idgen: 'uuid',
19835      *          ...
19836      *      });
19837      *
19838      * An id generation can also be further configured:
19839      *
19840      *      Ext.define('MyApp.data.MyModel', {
19841      *          extend: 'Ext.data.Model',
19842      *          idgen: {
19843      *              type: 'sequential',
19844      *              seed: 1000,
19845      *              prefix: 'ID_'
19846      *          }
19847      *      });
19848      *
19849      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
19850      *
19851      * If multiple models share an id space, a single generator can be shared:
19852      *
19853      *      Ext.define('MyApp.data.MyModelX', {
19854      *          extend: 'Ext.data.Model',
19855      *          idgen: {
19856      *              type: 'sequential',
19857      *              id: 'xy'
19858      *          }
19859      *      });
19860      *
19861      *      Ext.define('MyApp.data.MyModelY', {
19862      *          extend: 'Ext.data.Model',
19863      *          idgen: {
19864      *              type: 'sequential',
19865      *              id: 'xy'
19866      *          }
19867      *      });
19868      *
19869      * For more complex, shared id generators, a custom generator is the best approach.
19870      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
19871      *
19872      * @markdown
19873      */
19874     idgen: {
19875         isGenerator: true,
19876         type: 'default',
19877
19878         generate: function () {
19879             return null;
19880         },
19881         getRecId: function (rec) {
19882             return rec.modelName + '-' + rec.internalId;
19883         }
19884     },
19885
19886     /**
19887      * @property {Boolean} editing
19888      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
19889      */
19890     editing : false,
19891
19892     /**
19893      * @property {Boolean} dirty
19894      * True if this Record has been modified. Read-only.
19895      */
19896     dirty : false,
19897
19898     /**
19899      * @cfg {String} persistenceProperty
19900      * The property on this Persistable object that its data is saved to. Defaults to 'data'
19901      * (e.g. all persistable data resides in this.data.)
19902      */
19903     persistenceProperty: 'data',
19904
19905     evented: false,
19906     isModel: true,
19907
19908     /**
19909      * @property {Boolean} phantom
19910      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
19911      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
19912      */
19913     phantom : false,
19914
19915     /**
19916      * @cfg {String} idProperty
19917      * The name of the field treated as this Model's unique id. Defaults to 'id'.
19918      */
19919     idProperty: 'id',
19920
19921     /**
19922      * @cfg {String} defaultProxyType
19923      * The string type of the default Model Proxy. Defaults to 'ajax'.
19924      */
19925     defaultProxyType: 'ajax',
19926
19927     // Fields config and property
19928     /**
19929      * @cfg {Object[]/String[]} fields
19930      * The fields for this model.
19931      */
19932     /**
19933      * @property {Ext.util.MixedCollection} fields
19934      * The fields defined on this model.
19935      */
19936
19937     /**
19938      * @cfg {Object[]} validations
19939      * An array of {@link Ext.data.validations validations} for this model.
19940      */
19941
19942     // Associations configs and properties
19943     /**
19944      * @cfg {Object[]} associations
19945      * An array of {@link Ext.data.Association associations} for this model.
19946      */
19947     /**
19948      * @cfg {String/Object/String[]/Object[]} hasMany
19949      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
19950      */
19951     /**
19952      * @cfg {String/Object/String[]/Object[]} belongsTo
19953      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
19954      */
19955     /**
19956      * @property {Ext.util.MixedCollection} associations
19957      * {@link Ext.data.Association Associations} defined on this model.
19958      */
19959
19960     /**
19961      * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
19962      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
19963      */
19964
19965     // raw not documented intentionally, meant to be used internally.
19966     constructor: function(data, id, raw) {
19967         data = data || {};
19968
19969         var me = this,
19970             fields,
19971             length,
19972             field,
19973             name,
19974             i,
19975             newId,
19976             isArray = Ext.isArray(data),
19977             newData = isArray ? {} : null; // to hold mapped array data if needed
19978
19979         /**
19980          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
19981          * @property internalId
19982          * @type String
19983          * @private
19984          */
19985         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
19986
19987         /**
19988          * @property {Object} raw The raw data used to create this model if created via a reader.
19989          */
19990         me.raw = raw;
19991
19992         Ext.applyIf(me, {
19993             data: {}
19994         });
19995
19996         /**
19997          * @property {Object} modified Key: value pairs of all fields whose values have changed
19998          */
19999         me.modified = {};
20000
20001         // Deal with spelling error in previous releases
20002         if (me.persistanceProperty) {
20003             //<debug>
20004             if (Ext.isDefined(Ext.global.console)) {
20005                 Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
20006             }
20007             //</debug>
20008             me.persistenceProperty = me.persistanceProperty;
20009         }
20010         me[me.persistenceProperty] = {};
20011
20012         me.mixins.observable.constructor.call(me);
20013
20014         //add default field values if present
20015         fields = me.fields.items;
20016         length = fields.length;
20017
20018         for (i = 0; i < length; i++) {
20019             field = fields[i];
20020             name  = field.name;
20021
20022             if (isArray){
20023                 // Have to map array data so the values get assigned to the named fields
20024                 // rather than getting set as the field names with undefined values.
20025                 newData[name] = data[i];
20026             }
20027             else if (data[name] === undefined) {
20028                 data[name] = field.defaultValue;
20029             }
20030         }
20031
20032         me.set(newData || data);
20033
20034         if (me.getId()) {
20035             me.phantom = false;
20036         } else if (me.phantom) {
20037             newId = me.idgen.generate();
20038             if (newId !== null) {
20039                 me.setId(newId);
20040             }
20041         }
20042
20043         // clear any dirty/modified since we're initializing
20044         me.dirty = false;
20045         me.modified = {};
20046
20047         if (typeof me.init == 'function') {
20048             me.init();
20049         }
20050
20051         me.id = me.idgen.getRecId(me);
20052     },
20053
20054     /**
20055      * Returns the value of the given field
20056      * @param {String} fieldName The field to fetch the value for
20057      * @return {Object} The value
20058      */
20059     get: function(field) {
20060         return this[this.persistenceProperty][field];
20061     },
20062
20063     /**
20064      * Sets the given field to the given value, marks the instance as dirty
20065      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
20066      * @param {Object} value The value to set
20067      */
20068     set: function(fieldName, value) {
20069         var me = this,
20070             fields = me.fields,
20071             modified = me.modified,
20072             convertFields = [],
20073             field, key, i, currentValue, notEditing, count, length;
20074
20075         /*
20076          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
20077          * set those last so that all other possible data is set before the convert function is called
20078          */
20079         if (arguments.length == 1 && Ext.isObject(fieldName)) {
20080             notEditing = !me.editing;
20081             count = 0;
20082             for (key in fieldName) {
20083                 if (fieldName.hasOwnProperty(key)) {
20084
20085                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
20086                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
20087                     field = fields.get(key);
20088                     if (field && field.convert !== field.type.convert) {
20089                         convertFields.push(key);
20090                         continue;
20091                     }
20092
20093                     if (!count && notEditing) {
20094                         me.beginEdit();
20095                     }
20096                     ++count;
20097                     me.set(key, fieldName[key]);
20098                 }
20099             }
20100
20101             length = convertFields.length;
20102             if (length) {
20103                 if (!count && notEditing) {
20104                     me.beginEdit();
20105                 }
20106                 count += length;
20107                 for (i = 0; i < length; i++) {
20108                     field = convertFields[i];
20109                     me.set(field, fieldName[field]);
20110                 }
20111             }
20112
20113             if (notEditing && count) {
20114                 me.endEdit();
20115             }
20116         } else {
20117             if (fields) {
20118                 field = fields.get(fieldName);
20119
20120                 if (field && field.convert) {
20121                     value = field.convert(value, me);
20122                 }
20123             }
20124             currentValue = me.get(fieldName);
20125             me[me.persistenceProperty][fieldName] = value;
20126
20127             if (field && field.persist && !me.isEqual(currentValue, value)) {
20128                 if (me.isModified(fieldName)) {
20129                     if (me.isEqual(modified[fieldName], value)) {
20130                         // the original value in me.modified equals the new value, so the
20131                         // field is no longer modified
20132                         delete modified[fieldName];
20133                         // we might have removed the last modified field, so check to see if
20134                         // there are any modified fields remaining and correct me.dirty:
20135                         me.dirty = false;
20136                         for (key in modified) {
20137                             if (modified.hasOwnProperty(key)){
20138                                 me.dirty = true;
20139                                 break;
20140                             }
20141                         }
20142                     }
20143                 } else {
20144                     me.dirty = true;
20145                     modified[fieldName] = currentValue;
20146                 }
20147             }
20148
20149             if (!me.editing) {
20150                 me.afterEdit();
20151             }
20152         }
20153     },
20154
20155     /**
20156      * Checks if two values are equal, taking into account certain
20157      * special factors, for example dates.
20158      * @private
20159      * @param {Object} a The first value
20160      * @param {Object} b The second value
20161      * @return {Boolean} True if the values are equal
20162      */
20163     isEqual: function(a, b){
20164         if (Ext.isDate(a) && Ext.isDate(b)) {
20165             return a.getTime() === b.getTime();
20166         }
20167         return a === b;
20168     },
20169
20170     /**
20171      * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
20172      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
20173      */
20174     beginEdit : function(){
20175         var me = this;
20176         if (!me.editing) {
20177             me.editing = true;
20178             me.dirtySave = me.dirty;
20179             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
20180             me.modifiedSave = Ext.apply({}, me.modified);
20181         }
20182     },
20183
20184     /**
20185      * Cancels all changes made in the current edit operation.
20186      */
20187     cancelEdit : function(){
20188         var me = this;
20189         if (me.editing) {
20190             me.editing = false;
20191             // reset the modified state, nothing changed since the edit began
20192             me.modified = me.modifiedSave;
20193             me[me.persistenceProperty] = me.dataSave;
20194             me.dirty = me.dirtySave;
20195             delete me.modifiedSave;
20196             delete me.dataSave;
20197             delete me.dirtySave;
20198         }
20199     },
20200
20201     /**
20202      * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
20203      * fire).
20204      * @param {Boolean} silent True to not notify the store of the change
20205      */
20206     endEdit : function(silent){
20207         var me = this,
20208             didChange;
20209             
20210         if (me.editing) {
20211             me.editing = false;
20212             didChange = me.dirty || me.changedWhileEditing();
20213             delete me.modifiedSave;
20214             delete me.dataSave;
20215             delete me.dirtySave;
20216             if (silent !== true && didChange) {
20217                 me.afterEdit();
20218             }
20219         }
20220     },
20221     
20222     /**
20223      * Checks if the underlying data has changed during an edit. This doesn't necessarily
20224      * mean the record is dirty, however we still need to notify the store since it may need
20225      * to update any views.
20226      * @private
20227      * @return {Boolean} True if the underlying data has changed during an edit.
20228      */
20229     changedWhileEditing: function(){
20230         var me = this,
20231             saved = me.dataSave,
20232             data = me[me.persistenceProperty],
20233             key;
20234             
20235         for (key in data) {
20236             if (data.hasOwnProperty(key)) {
20237                 if (!me.isEqual(data[key], saved[key])) {
20238                     return true;
20239                 }
20240             }
20241         }
20242         return false; 
20243     },
20244
20245     /**
20246      * Gets a hash of only the fields that have been modified since this Model was created or commited.
20247      * @return {Object}
20248      */
20249     getChanges : function(){
20250         var modified = this.modified,
20251             changes  = {},
20252             field;
20253
20254         for (field in modified) {
20255             if (modified.hasOwnProperty(field)){
20256                 changes[field] = this.get(field);
20257             }
20258         }
20259
20260         return changes;
20261     },
20262
20263     /**
20264      * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
20265      * @param {String} fieldName {@link Ext.data.Field#name}
20266      * @return {Boolean}
20267      */
20268     isModified : function(fieldName) {
20269         return this.modified.hasOwnProperty(fieldName);
20270     },
20271
20272     /**
20273      * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
20274      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
20275      *
20276      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
20277      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
20278      */
20279     setDirty : function() {
20280         var me = this,
20281             name;
20282
20283         me.dirty = true;
20284
20285         me.fields.each(function(field) {
20286             if (field.persist) {
20287                 name = field.name;
20288                 me.modified[name] = me.get(name);
20289             }
20290         }, me);
20291     },
20292
20293     //<debug>
20294     markDirty : function() {
20295         if (Ext.isDefined(Ext.global.console)) {
20296             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
20297         }
20298         return this.setDirty.apply(this, arguments);
20299     },
20300     //</debug>
20301
20302     /**
20303      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
20304      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
20305      * reverted to their original values.
20306      *
20307      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
20308      * operations.
20309      *
20310      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
20311      * Defaults to false.
20312      */
20313     reject : function(silent) {
20314         var me = this,
20315             modified = me.modified,
20316             field;
20317
20318         for (field in modified) {
20319             if (modified.hasOwnProperty(field)) {
20320                 if (typeof modified[field] != "function") {
20321                     me[me.persistenceProperty][field] = modified[field];
20322                 }
20323             }
20324         }
20325
20326         me.dirty = false;
20327         me.editing = false;
20328         me.modified = {};
20329
20330         if (silent !== true) {
20331             me.afterReject();
20332         }
20333     },
20334
20335     /**
20336      * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
20337      * instance since either creation or the last commit operation.
20338      *
20339      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
20340      * operations.
20341      *
20342      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
20343      * Defaults to false.
20344      */
20345     commit : function(silent) {
20346         var me = this;
20347
20348         me.phantom = me.dirty = me.editing = false;
20349         me.modified = {};
20350
20351         if (silent !== true) {
20352             me.afterCommit();
20353         }
20354     },
20355
20356     /**
20357      * Creates a copy (clone) of this Model instance.
20358      *
20359      * @param {String} [id] A new id, defaults to the id of the instance being copied.
20360      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
20361      *
20362      *     var rec = record.copy(); // clone the record
20363      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
20364      *
20365      * @return {Ext.data.Model}
20366      */
20367     copy : function(newId) {
20368         var me = this;
20369
20370         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
20371     },
20372
20373     /**
20374      * Sets the Proxy to use for this model. Accepts any options that can be accepted by
20375      * {@link Ext#createByAlias Ext.createByAlias}.
20376      *
20377      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
20378      * @return {Ext.data.proxy.Proxy}
20379      */
20380     setProxy: function(proxy) {
20381         //make sure we have an Ext.data.proxy.Proxy object
20382         if (!proxy.isProxy) {
20383             if (typeof proxy === "string") {
20384                 proxy = {
20385                     type: proxy
20386                 };
20387             }
20388             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
20389         }
20390         proxy.setModel(this.self);
20391         this.proxy = proxy;
20392
20393         return proxy;
20394     },
20395
20396     /**
20397      * Returns the configured Proxy for this Model.
20398      * @return {Ext.data.proxy.Proxy} The proxy
20399      */
20400     getProxy: function() {
20401         return this.proxy;
20402     },
20403
20404     /**
20405      * Validates the current data against all of its configured {@link #validations}.
20406      * @return {Ext.data.Errors} The errors object
20407      */
20408     validate: function() {
20409         var errors      = Ext.create('Ext.data.Errors'),
20410             validations = this.validations,
20411             validators  = Ext.data.validations,
20412             length, validation, field, valid, type, i;
20413
20414         if (validations) {
20415             length = validations.length;
20416
20417             for (i = 0; i < length; i++) {
20418                 validation = validations[i];
20419                 field = validation.field || validation.name;
20420                 type  = validation.type;
20421                 valid = validators[type](validation, this.get(field));
20422
20423                 if (!valid) {
20424                     errors.add({
20425                         field  : field,
20426                         message: validation.message || validators[type + 'Message']
20427                     });
20428                 }
20429             }
20430         }
20431
20432         return errors;
20433     },
20434
20435     /**
20436      * Checks if the model is valid. See {@link #validate}.
20437      * @return {Boolean} True if the model is valid.
20438      */
20439     isValid: function(){
20440         return this.validate().isValid();
20441     },
20442
20443     /**
20444      * Saves the model instance using the configured proxy.
20445      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
20446      * @return {Ext.data.Model} The Model instance
20447      */
20448     save: function(options) {
20449         options = Ext.apply({}, options);
20450
20451         var me     = this,
20452             action = me.phantom ? 'create' : 'update',
20453             record = null,
20454             scope  = options.scope || me,
20455             operation,
20456             callback;
20457
20458         Ext.apply(options, {
20459             records: [me],
20460             action : action
20461         });
20462
20463         operation = Ext.create('Ext.data.Operation', options);
20464
20465         callback = function(operation) {
20466             if (operation.wasSuccessful()) {
20467                 record = operation.getRecords()[0];
20468                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
20469                 //ModelCache is in place
20470                 me.set(record.data);
20471                 record.dirty = false;
20472
20473                 Ext.callback(options.success, scope, [record, operation]);
20474             } else {
20475                 Ext.callback(options.failure, scope, [record, operation]);
20476             }
20477
20478             Ext.callback(options.callback, scope, [record, operation]);
20479         };
20480
20481         me.getProxy()[action](operation, callback, me);
20482
20483         return me;
20484     },
20485
20486     /**
20487      * Destroys the model using the configured proxy.
20488      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
20489      * @return {Ext.data.Model} The Model instance
20490      */
20491     destroy: function(options){
20492         options = Ext.apply({}, options);
20493
20494         var me     = this,
20495             record = null,
20496             scope  = options.scope || me,
20497             operation,
20498             callback;
20499
20500         Ext.apply(options, {
20501             records: [me],
20502             action : 'destroy'
20503         });
20504
20505         operation = Ext.create('Ext.data.Operation', options);
20506         callback = function(operation) {
20507             if (operation.wasSuccessful()) {
20508                 Ext.callback(options.success, scope, [record, operation]);
20509             } else {
20510                 Ext.callback(options.failure, scope, [record, operation]);
20511             }
20512             Ext.callback(options.callback, scope, [record, operation]);
20513         };
20514
20515         me.getProxy().destroy(operation, callback, me);
20516         return me;
20517     },
20518
20519     /**
20520      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
20521      * @return {Number} The id
20522      */
20523     getId: function() {
20524         return this.get(this.idProperty);
20525     },
20526
20527     /**
20528      * Sets the model instance's id field to the given id.
20529      * @param {Number} id The new id
20530      */
20531     setId: function(id) {
20532         this.set(this.idProperty, id);
20533     },
20534
20535     /**
20536      * Tells this model instance that it has been added to a store.
20537      * @param {Ext.data.Store} store The store to which this model has been added.
20538      */
20539     join : function(store) {
20540         /**
20541          * @property {Ext.data.Store} store
20542          * The {@link Ext.data.Store Store} to which this Record belongs.
20543          */
20544         this.store = store;
20545     },
20546
20547     /**
20548      * Tells this model instance that it has been removed from the store.
20549      * @param {Ext.data.Store} store The store from which this model has been removed.
20550      */
20551     unjoin: function(store) {
20552         delete this.store;
20553     },
20554
20555     /**
20556      * @private
20557      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
20558      * afterEdit method is called
20559      */
20560     afterEdit : function() {
20561         this.callStore('afterEdit');
20562     },
20563
20564     /**
20565      * @private
20566      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
20567      * afterReject method is called
20568      */
20569     afterReject : function() {
20570         this.callStore("afterReject");
20571     },
20572
20573     /**
20574      * @private
20575      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
20576      * afterCommit method is called
20577      */
20578     afterCommit: function() {
20579         this.callStore('afterCommit');
20580     },
20581
20582     /**
20583      * @private
20584      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
20585      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
20586      * will always be called with the model instance as its single argument.
20587      * @param {String} fn The function to call on the store
20588      */
20589     callStore: function(fn) {
20590         var store = this.store;
20591
20592         if (store !== undefined && typeof store[fn] == "function") {
20593             store[fn](this);
20594         }
20595     },
20596
20597     /**
20598      * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
20599      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
20600      *
20601      *     {
20602      *         orders: [
20603      *             {
20604      *                 id: 123,
20605      *                 status: 'shipped',
20606      *                 orderItems: [
20607      *                     ...
20608      *                 ]
20609      *             }
20610      *         ]
20611      *     }
20612      *
20613      * @return {Object} The nested data set for the Model's loaded associations
20614      */
20615     getAssociatedData: function(){
20616         return this.prepareAssociatedData(this, [], null);
20617     },
20618
20619     /**
20620      * @private
20621      * This complex-looking method takes a given Model instance and returns an object containing all data from
20622      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
20623      * @param {Ext.data.Model} record The Model instance
20624      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
20625      * @param {String} associationType (optional) The name of the type of association to limit to.
20626      * @return {Object} The nested data set for the Model's loaded associations
20627      */
20628     prepareAssociatedData: function(record, ids, associationType) {
20629         //we keep track of all of the internalIds of the models that we have loaded so far in here
20630         var associations     = record.associations.items,
20631             associationCount = associations.length,
20632             associationData  = {},
20633             associatedStore, associatedName, associatedRecords, associatedRecord,
20634             associatedRecordCount, association, id, i, j, type, allow;
20635
20636         for (i = 0; i < associationCount; i++) {
20637             association = associations[i];
20638             type = association.type;
20639             allow = true;
20640             if (associationType) {
20641                 allow = type == associationType;
20642             }
20643             if (allow && type == 'hasMany') {
20644
20645                 //this is the hasMany store filled with the associated data
20646                 associatedStore = record[association.storeName];
20647
20648                 //we will use this to contain each associated record's data
20649                 associationData[association.name] = [];
20650
20651                 //if it's loaded, put it into the association data
20652                 if (associatedStore && associatedStore.data.length > 0) {
20653                     associatedRecords = associatedStore.data.items;
20654                     associatedRecordCount = associatedRecords.length;
20655
20656                     //now we're finally iterating over the records in the association. We do this recursively
20657                     for (j = 0; j < associatedRecordCount; j++) {
20658                         associatedRecord = associatedRecords[j];
20659                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
20660                         id = associatedRecord.id;
20661
20662                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
20663                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
20664                         if (Ext.Array.indexOf(ids, id) == -1) {
20665                             ids.push(id);
20666
20667                             associationData[association.name][j] = associatedRecord.data;
20668                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
20669                         }
20670                     }
20671                 }
20672             } else if (allow && type == 'belongsTo') {
20673                 associatedRecord = record[association.instanceName];
20674                 if (associatedRecord !== undefined) {
20675                     id = associatedRecord.id;
20676                     if (Ext.Array.indexOf(ids, id) == -1) {
20677                         ids.push(id);
20678                         associationData[association.name] = associatedRecord.data;
20679                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
20680                     }
20681                 }
20682             }
20683         }
20684
20685         return associationData;
20686     }
20687 });
20688
20689 /**
20690  * @docauthor Evan Trimboli <evan@sencha.com>
20691  *
20692  * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
20693  * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
20694  * referred to via it's identifier:
20695  *
20696  *     Ext.create('Ext.data.Store', {
20697  *         model: 'SomeModel',
20698  *         storeId: 'myStore'
20699  *     });
20700  *
20701  *     var store = Ext.data.StoreManager.lookup('myStore');
20702  *
20703  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
20704  *
20705  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
20706  * it with any Component that consumes data from a store:
20707  *
20708  *     Ext.create('Ext.data.Store', {
20709  *         model: 'SomeModel',
20710  *         storeId: 'myStore'
20711  *     });
20712  *
20713  *     Ext.create('Ext.view.View', {
20714  *         store: 'myStore',
20715  *         // other configuration here
20716  *     });
20717  *
20718  */
20719 Ext.define('Ext.data.StoreManager', {
20720     extend: 'Ext.util.MixedCollection',
20721     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
20722     singleton: true,
20723     uses: ['Ext.data.ArrayStore'],
20724     
20725     /**
20726      * @cfg {Object} listeners @hide
20727      */
20728
20729     /**
20730      * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
20731      * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
20732      * @param {Ext.data.Store...} stores Any number of Store instances
20733      */
20734     register : function() {
20735         for (var i = 0, s; (s = arguments[i]); i++) {
20736             this.add(s);
20737         }
20738     },
20739
20740     /**
20741      * Unregisters one or more Stores with the StoreManager
20742      * @param {String/Object...} stores Any number of Store instances or ID-s
20743      */
20744     unregister : function() {
20745         for (var i = 0, s; (s = arguments[i]); i++) {
20746             this.remove(this.lookup(s));
20747         }
20748     },
20749
20750     /**
20751      * Gets a registered Store by id
20752      * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
20753      * @return {Ext.data.Store}
20754      */
20755     lookup : function(store) {
20756         // handle the case when we are given an array or an array of arrays.
20757         if (Ext.isArray(store)) {
20758             var fields = ['field1'], 
20759                 expand = !Ext.isArray(store[0]),
20760                 data = store,
20761                 i,
20762                 len;
20763                 
20764             if(expand){
20765                 data = [];
20766                 for (i = 0, len = store.length; i < len; ++i) {
20767                     data.push([store[i]]);
20768                 }
20769             } else {
20770                 for(i = 2, len = store[0].length; i <= len; ++i){
20771                     fields.push('field' + i);
20772                 }
20773             }
20774             return Ext.create('Ext.data.ArrayStore', {
20775                 data  : data,
20776                 fields: fields,
20777                 autoDestroy: true,
20778                 autoCreated: true,
20779                 expanded: expand
20780             });
20781         }
20782         
20783         if (Ext.isString(store)) {
20784             // store id
20785             return this.get(store);
20786         } else {
20787             // store instance or store config
20788             return Ext.data.AbstractStore.create(store);
20789         }
20790     },
20791
20792     // getKey implementation for MixedCollection
20793     getKey : function(o) {
20794          return o.storeId;
20795     }
20796 }, function() {    
20797     /**
20798      * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
20799      * Sample usage:
20800      *
20801      *     Ext.regStore('AllUsers', {
20802      *         model: 'User'
20803      *     });
20804      *
20805      *     // the store can now easily be used throughout the application
20806      *     new Ext.List({
20807      *         store: 'AllUsers',
20808      *         ... other config
20809      *     });
20810      *
20811      * @param {String} id The id to set on the new store
20812      * @param {Object} config The store config
20813      * @member Ext
20814      * @method regStore
20815      */
20816     Ext.regStore = function(name, config) {
20817         var store;
20818
20819         if (Ext.isObject(name)) {
20820             config = name;
20821         } else {
20822             config.storeId = name;
20823         }
20824
20825         if (config instanceof Ext.data.Store) {
20826             store = config;
20827         } else {
20828             store = Ext.create('Ext.data.Store', config);
20829         }
20830
20831         return Ext.data.StoreManager.register(store);
20832     };
20833
20834     /**
20835      * Shortcut to {@link Ext.data.StoreManager#lookup}.
20836      * @member Ext
20837      * @method getStore
20838      * @alias Ext.data.StoreManager#lookup
20839      */
20840     Ext.getStore = function(name) {
20841         return Ext.data.StoreManager.lookup(name);
20842     };
20843 });
20844
20845 /**
20846  * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
20847  * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
20848  * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
20849  * at the time the Container is created, or they may be added dynamically via the
20850  * {@link Ext.container.Container#add add} method.
20851  *
20852  * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
20853  *
20854  * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
20855  * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
20856  *
20857  * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
20858  * subclass Component.
20859  *
20860  * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
20861  * to create custom Components.
20862  *
20863  * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
20864  * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
20865  * Component hierarchy.
20866  *
20867  * This is the list of all valid xtypes:
20868  *
20869  *     xtype            Class
20870  *     -------------    ------------------
20871  *     button           {@link Ext.button.Button}
20872  *     buttongroup      {@link Ext.container.ButtonGroup}
20873  *     colorpalette     {@link Ext.picker.Color}
20874  *     component        {@link Ext.Component}
20875  *     container        {@link Ext.container.Container}
20876  *     cycle            {@link Ext.button.Cycle}
20877  *     dataview         {@link Ext.view.View}
20878  *     datepicker       {@link Ext.picker.Date}
20879  *     editor           {@link Ext.Editor}
20880  *     editorgrid       {@link Ext.grid.plugin.Editing}
20881  *     grid             {@link Ext.grid.Panel}
20882  *     multislider      {@link Ext.slider.Multi}
20883  *     panel            {@link Ext.panel.Panel}
20884  *     progressbar      {@link Ext.ProgressBar}
20885  *     slider           {@link Ext.slider.Single}
20886  *     splitbutton      {@link Ext.button.Split}
20887  *     tabpanel         {@link Ext.tab.Panel}
20888  *     treepanel        {@link Ext.tree.Panel}
20889  *     viewport         {@link Ext.container.Viewport}
20890  *     window           {@link Ext.window.Window}
20891  *
20892  *     Toolbar components
20893  *     ---------------------------------------
20894  *     pagingtoolbar    {@link Ext.toolbar.Paging}
20895  *     toolbar          {@link Ext.toolbar.Toolbar}
20896  *     tbfill           {@link Ext.toolbar.Fill}
20897  *     tbitem           {@link Ext.toolbar.Item}
20898  *     tbseparator      {@link Ext.toolbar.Separator}
20899  *     tbspacer         {@link Ext.toolbar.Spacer}
20900  *     tbtext           {@link Ext.toolbar.TextItem}
20901  *
20902  *     Menu components
20903  *     ---------------------------------------
20904  *     menu             {@link Ext.menu.Menu}
20905  *     menucheckitem    {@link Ext.menu.CheckItem}
20906  *     menuitem         {@link Ext.menu.Item}
20907  *     menuseparator    {@link Ext.menu.Separator}
20908  *     menutextitem     {@link Ext.menu.Item}
20909  *
20910  *     Form components
20911  *     ---------------------------------------
20912  *     form             {@link Ext.form.Panel}
20913  *     checkbox         {@link Ext.form.field.Checkbox}
20914  *     combo            {@link Ext.form.field.ComboBox}
20915  *     datefield        {@link Ext.form.field.Date}
20916  *     displayfield     {@link Ext.form.field.Display}
20917  *     field            {@link Ext.form.field.Base}
20918  *     fieldset         {@link Ext.form.FieldSet}
20919  *     hidden           {@link Ext.form.field.Hidden}
20920  *     htmleditor       {@link Ext.form.field.HtmlEditor}
20921  *     label            {@link Ext.form.Label}
20922  *     numberfield      {@link Ext.form.field.Number}
20923  *     radio            {@link Ext.form.field.Radio}
20924  *     radiogroup       {@link Ext.form.RadioGroup}
20925  *     textarea         {@link Ext.form.field.TextArea}
20926  *     textfield        {@link Ext.form.field.Text}
20927  *     timefield        {@link Ext.form.field.Time}
20928  *     trigger          {@link Ext.form.field.Trigger}
20929  *
20930  *     Chart components
20931  *     ---------------------------------------
20932  *     chart            {@link Ext.chart.Chart}
20933  *     barchart         {@link Ext.chart.series.Bar}
20934  *     columnchart      {@link Ext.chart.series.Column}
20935  *     linechart        {@link Ext.chart.series.Line}
20936  *     piechart         {@link Ext.chart.series.Pie}
20937  *
20938  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
20939  * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
20940  * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
20941  *
20942  *     @example
20943  *     Ext.create('Ext.Component', {
20944  *         html: 'Hello world!',
20945  *         width: 300,
20946  *         height: 200,
20947  *         padding: 20,
20948  *         style: {
20949  *             color: '#FFFFFF',
20950  *             backgroundColor:'#000000'
20951  *         },
20952  *         renderTo: Ext.getBody()
20953  *     });
20954  *
20955  * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
20956  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
20957  * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
20958  * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
20959  *
20960  * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
20961  */
20962 Ext.define('Ext.Component', {
20963
20964     /* Begin Definitions */
20965
20966     alias: ['widget.component', 'widget.box'],
20967
20968     extend: 'Ext.AbstractComponent',
20969
20970     requires: [
20971         'Ext.util.DelayedTask'
20972     ],
20973
20974     uses: [
20975         'Ext.Layer',
20976         'Ext.resizer.Resizer',
20977         'Ext.util.ComponentDragger'
20978     ],
20979
20980     mixins: {
20981         floating: 'Ext.util.Floating'
20982     },
20983
20984     statics: {
20985         // Collapse/expand directions
20986         DIRECTION_TOP: 'top',
20987         DIRECTION_RIGHT: 'right',
20988         DIRECTION_BOTTOM: 'bottom',
20989         DIRECTION_LEFT: 'left',
20990
20991         VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
20992
20993         // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
20994         // This includes dot, comma and whitespace
20995         INVALID_ID_CHARS_Re: /[\.,\s]/g
20996     },
20997
20998     /* End Definitions */
20999
21000     /**
21001      * @cfg {Boolean/Object} resizable
21002      * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
21003      *
21004      * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
21005      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
21006      * `{@link Ext.resizer.Resizer#dynamic}: false`
21007      */
21008
21009     /**
21010      * @cfg {String} resizeHandles
21011      * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
21012      */
21013     resizeHandles: 'all',
21014
21015     /**
21016      * @cfg {Boolean} [autoScroll=false]
21017      * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
21018      * `false` to clip any overflowing content.
21019      */
21020
21021     /**
21022      * @cfg {Boolean} floating
21023      * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
21024      *
21025      * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
21026      *
21027      * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
21028      * the global {@link Ext.WindowManager ZIndexManager}
21029      *
21030      * ### Floating Components as child items of a Container
21031      *
21032      * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
21033      * a ZIndexManager by examining the ownerCt chain.
21034      *
21035      * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
21036      * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
21037      * of its stack when the Component's {@link #toFront} method is called.
21038      *
21039      * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
21040      * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
21041      * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
21042      * ancestor Container.
21043      *
21044      * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
21045      * ZIndexManager}.
21046      *
21047      * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
21048      * you explicitly {@link #show} them.
21049      *
21050      * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
21051      * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
21052      * not be set.
21053      */
21054     floating: false,
21055
21056     /**
21057      * @cfg {Boolean} toFrontOnShow
21058      * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
21059      * floating component.
21060      */
21061     toFrontOnShow: true,
21062
21063     /**
21064      * @property {Ext.ZIndexManager} zIndexManager
21065      * Only present for {@link #floating} Components after they have been rendered.
21066      *
21067      * A reference to the ZIndexManager which is managing this Component's z-index.
21068      *
21069      * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
21070      * a single modal mask which is insert just beneath the topmost visible modal floating Component.
21071      *
21072      * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
21073      * z-index stack.
21074      *
21075      * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
21076      * programatically {@link Ext.Component#render rendered}.
21077      *
21078      * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
21079      * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
21080      * used.
21081      *
21082      * See {@link #floating} and {@link #floatParent}
21083      */
21084
21085     /**
21086      * @property {Ext.Container} floatParent
21087      * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
21088      *
21089      * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
21090      * property.
21091      *
21092      * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
21093      * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
21094      * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
21095      * Components.
21096      *
21097      * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
21098      * Window as its `floatParent`
21099      *
21100      * See {@link #floating} and {@link #zIndexManager}
21101      */
21102
21103     /**
21104      * @cfg {Boolean/Object} [draggable=false]
21105      * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
21106      * the drag handle.
21107      *
21108      * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
21109      * instantiated to perform dragging.
21110      *
21111      * For example to create a Component which may only be dragged around using a certain internal element as the drag
21112      * handle, use the delegate option:
21113      *
21114      *     new Ext.Component({
21115      *         constrain: true,
21116      *         floating: true,
21117      *         style: {
21118      *             backgroundColor: '#fff',
21119      *             border: '1px solid black'
21120      *         },
21121      *         html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
21122      *         draggable: {
21123      *             delegate: 'h1'
21124      *         }
21125      *     }).show();
21126      */
21127
21128     /**
21129      * @cfg {Boolean} [maintainFlex=false]
21130      * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
21131      * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
21132      *
21133      * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
21134      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
21135      */
21136
21137     hideMode: 'display',
21138     // Deprecate 5.0
21139     hideParent: false,
21140
21141     ariaRole: 'presentation',
21142
21143     bubbleEvents: [],
21144
21145     actionMode: 'el',
21146     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
21147
21148     //renderTpl: new Ext.XTemplate(
21149     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
21150     //        compiled: true,
21151     //        disableFormats: true
21152     //    }
21153     //),
21154
21155     /**
21156      * Creates new Component.
21157      * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
21158      *
21159      * - **an element** : it is set as the internal element and its id used as the component id
21160      * - **a string** : it is assumed to be the id of an existing element and is used as the component id
21161      * - **anything else** : it is assumed to be a standard config object and is applied to the component
21162      */
21163     constructor: function(config) {
21164         var me = this;
21165
21166         config = config || {};
21167         if (config.initialConfig) {
21168
21169             // Being initialized from an Ext.Action instance...
21170             if (config.isAction) {
21171                 me.baseAction = config;
21172             }
21173             config = config.initialConfig;
21174             // component cloning / action set up
21175         }
21176         else if (config.tagName || config.dom || Ext.isString(config)) {
21177             // element object
21178             config = {
21179                 applyTo: config,
21180                 id: config.id || config
21181             };
21182         }
21183
21184         me.callParent([config]);
21185
21186         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
21187         // register this Component as one of its items
21188         if (me.baseAction){
21189             me.baseAction.addComponent(me);
21190         }
21191     },
21192
21193     /**
21194      * The initComponent template method is an important initialization step for a Component. It is intended to be
21195      * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
21196      * initComponent method of the class being created is called first, with each initComponent method
21197      * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
21198      * if needed, override the constructor logic of the Component at any step in the hierarchy.
21199      *
21200      * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
21201      * to ensure that the parent class' initComponent method is also called.
21202      *
21203      * The following example demonstrates using a dynamic string for the text of a button at the time of
21204      * instantiation of the class.
21205      *
21206      *     Ext.define('DynamicButtonText', {
21207      *         extend: 'Ext.button.Button',
21208      *
21209      *         initComponent: function() {
21210      *             this.text = new Date();
21211      *             this.renderTo = Ext.getBody();
21212      *             this.callParent();
21213      *         }
21214      *     });
21215      *
21216      *     Ext.onReady(function() {
21217      *         Ext.create('DynamicButtonText');
21218      *     });
21219      *
21220      * @template
21221      */
21222     initComponent: function() {
21223         var me = this;
21224
21225         me.callParent();
21226
21227         if (me.listeners) {
21228             me.on(me.listeners);
21229             delete me.listeners;
21230         }
21231         me.enableBubble(me.bubbleEvents);
21232         me.mons = [];
21233     },
21234
21235     // private
21236     afterRender: function() {
21237         var me = this,
21238             resizable = me.resizable;
21239
21240         if (me.floating) {
21241             me.makeFloating(me.floating);
21242         } else {
21243             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
21244         }
21245
21246         if (Ext.isDefined(me.autoScroll)) {
21247             me.setAutoScroll(me.autoScroll);
21248         }
21249         me.callParent();
21250
21251         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
21252             me.setPagePosition(me.pageX, me.pageY);
21253         }
21254
21255         if (resizable) {
21256             me.initResizable(resizable);
21257         }
21258
21259         if (me.draggable) {
21260             me.initDraggable();
21261         }
21262
21263         me.initAria();
21264     },
21265
21266     initAria: function() {
21267         var actionEl = this.getActionEl(),
21268             role = this.ariaRole;
21269         if (role) {
21270             actionEl.dom.setAttribute('role', role);
21271         }
21272     },
21273
21274     /**
21275      * Sets the overflow on the content element of the component.
21276      * @param {Boolean} scroll True to allow the Component to auto scroll.
21277      * @return {Ext.Component} this
21278      */
21279     setAutoScroll : function(scroll){
21280         var me = this,
21281             targetEl;
21282         scroll = !!scroll;
21283         if (me.rendered) {
21284             targetEl = me.getTargetEl();
21285             targetEl.setStyle('overflow', scroll ? 'auto' : '');
21286             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
21287                 // The scrollable container element must be non-statically positioned or IE6/7 will make
21288                 // positioned children stay in place rather than scrolling with the rest of the content
21289                 targetEl.position();
21290             }
21291         }
21292         me.autoScroll = scroll;
21293         return me;
21294     },
21295
21296     // private
21297     makeFloating : function(cfg){
21298         this.mixins.floating.constructor.call(this, cfg);
21299     },
21300
21301     initResizable: function(resizable) {
21302         var me = this;
21303
21304         resizable = Ext.apply({
21305             target: me,
21306             dynamic: false,
21307             constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
21308             handles: me.resizeHandles
21309         }, resizable);
21310         resizable.target = me;
21311         me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
21312     },
21313
21314     getDragEl: function() {
21315         return this.el;
21316     },
21317
21318     initDraggable: function() {
21319         var me = this,
21320             ddConfig = Ext.applyIf({
21321                 el: me.getDragEl(),
21322                 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
21323             }, me.draggable);
21324
21325         // Add extra configs if Component is specified to be constrained
21326         if (me.constrain || me.constrainDelegate) {
21327             ddConfig.constrain = me.constrain;
21328             ddConfig.constrainDelegate = me.constrainDelegate;
21329         }
21330
21331         me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
21332     },
21333
21334     /**
21335      * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
21336      * method fires the {@link #move} event.
21337      * @param {Number} left The new left
21338      * @param {Number} top The new top
21339      * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
21340      * animation configuration.
21341      * @return {Ext.Component} this
21342      */
21343     setPosition: function(x, y, animate) {
21344         var me = this,
21345             el = me.el,
21346             to = {},
21347             adj, adjX, adjY, xIsNumber, yIsNumber;
21348
21349         if (Ext.isArray(x)) {
21350             animate = y;
21351             y = x[1];
21352             x = x[0];
21353         }
21354         me.x = x;
21355         me.y = y;
21356
21357         if (!me.rendered) {
21358             return me;
21359         }
21360
21361         adj = me.adjustPosition(x, y);
21362         adjX = adj.x;
21363         adjY = adj.y;
21364         xIsNumber = Ext.isNumber(adjX);
21365         yIsNumber = Ext.isNumber(adjY);
21366
21367         if (xIsNumber || yIsNumber) {
21368             if (animate) {
21369                 if (xIsNumber) {
21370                     to.left = adjX;
21371                 }
21372                 if (yIsNumber) {
21373                     to.top = adjY;
21374                 }
21375
21376                 me.stopAnimation();
21377                 me.animate(Ext.apply({
21378                     duration: 1000,
21379                     listeners: {
21380                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
21381                     },
21382                     to: to
21383                 }, animate));
21384             }
21385             else {
21386                 if (!xIsNumber) {
21387                     el.setTop(adjY);
21388                 }
21389                 else if (!yIsNumber) {
21390                     el.setLeft(adjX);
21391                 }
21392                 else {
21393                     el.setLeftTop(adjX, adjY);
21394                 }
21395                 me.afterSetPosition(adjX, adjY);
21396             }
21397         }
21398         return me;
21399     },
21400
21401     /**
21402      * @private
21403      * @template
21404      * Template method called after a Component has been positioned.
21405      */
21406     afterSetPosition: function(ax, ay) {
21407         this.onPosition(ax, ay);
21408         this.fireEvent('move', this, ax, ay);
21409     },
21410
21411     /**
21412      * Displays component at specific xy position.
21413      * A floating component (like a menu) is positioned relative to its ownerCt if any.
21414      * Useful for popping up a context menu:
21415      *
21416      *     listeners: {
21417      *         itemcontextmenu: function(view, record, item, index, event, options) {
21418      *             Ext.create('Ext.menu.Menu', {
21419      *                 width: 100,
21420      *                 height: 100,
21421      *                 margin: '0 0 10 0',
21422      *                 items: [{
21423      *                     text: 'regular item 1'
21424      *                 },{
21425      *                     text: 'regular item 2'
21426      *                 },{
21427      *                     text: 'regular item 3'
21428      *                 }]
21429      *             }).showAt(event.getXY());
21430      *         }
21431      *     }
21432      *
21433      * @param {Number} x The new x position
21434      * @param {Number} y The new y position
21435      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
21436      * animation configuration.
21437      */
21438     showAt: function(x, y, animate) {
21439         var me = this;
21440
21441         if (me.floating) {
21442             me.setPosition(x, y, animate);
21443         } else {
21444             me.setPagePosition(x, y, animate);
21445         }
21446         me.show();
21447     },
21448
21449     /**
21450      * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
21451      * This method fires the {@link #move} event.
21452      * @param {Number} x The new x position
21453      * @param {Number} y The new y position
21454      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
21455      * animation configuration.
21456      * @return {Ext.Component} this
21457      */
21458     setPagePosition: function(x, y, animate) {
21459         var me = this,
21460             p;
21461
21462         if (Ext.isArray(x)) {
21463             y = x[1];
21464             x = x[0];
21465         }
21466         me.pageX = x;
21467         me.pageY = y;
21468         if (me.floating && me.floatParent) {
21469             // Floating Components being positioned in their ownerCt have to be made absolute
21470             p = me.floatParent.getTargetEl().getViewRegion();
21471             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
21472                 x -= p.left;
21473             }
21474             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
21475                 y -= p.top;
21476             }
21477             me.setPosition(x, y, animate);
21478         }
21479         else {
21480             p = me.el.translatePoints(x, y);
21481             me.setPosition(p.left, p.top, animate);
21482         }
21483         return me;
21484     },
21485
21486     /**
21487      * Gets the current box measurements of the component's underlying element.
21488      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
21489      * @return {Object} box An object in the format {x, y, width, height}
21490      */
21491     getBox : function(local){
21492         var pos = this.getPosition(local),
21493             size = this.getSize();
21494
21495         size.x = pos[0];
21496         size.y = pos[1];
21497         return size;
21498     },
21499
21500     /**
21501      * Sets the current box measurements of the component's underlying element.
21502      * @param {Object} box An object in the format {x, y, width, height}
21503      * @return {Ext.Component} this
21504      */
21505     updateBox : function(box){
21506         this.setSize(box.width, box.height);
21507         this.setPagePosition(box.x, box.y);
21508         return this;
21509     },
21510
21511     // Include margins
21512     getOuterSize: function() {
21513         var el = this.el;
21514         return {
21515             width: el.getWidth() + el.getMargin('lr'),
21516             height: el.getHeight() + el.getMargin('tb')
21517         };
21518     },
21519
21520     // private
21521     adjustPosition: function(x, y) {
21522
21523         // Floating Components being positioned in their ownerCt have to be made absolute
21524         if (this.floating && this.floatParent) {
21525             var o = this.floatParent.getTargetEl().getViewRegion();
21526             x += o.left;
21527             y += o.top;
21528         }
21529
21530         return {
21531             x: x,
21532             y: y
21533         };
21534     },
21535
21536     /**
21537      * Gets the current XY position of the component's underlying element.
21538      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
21539      * @return {Number[]} The XY position of the element (e.g., [100, 200])
21540      */
21541     getPosition: function(local) {
21542         var me = this,
21543             el = me.el,
21544             xy,
21545             o;
21546
21547         // Floating Components which were just rendered with no ownerCt return local position.
21548         if ((local === true) || (me.floating && !me.floatParent)) {
21549             return [el.getLeft(true), el.getTop(true)];
21550         }
21551         xy = me.xy || el.getXY();
21552
21553         // Floating Components in an ownerCt have to have their positions made relative
21554         if (me.floating) {
21555             o = me.floatParent.getTargetEl().getViewRegion();
21556             xy[0] -= o.left;
21557             xy[1] -= o.top;
21558         }
21559         return xy;
21560     },
21561
21562     getId: function() {
21563         var me = this,
21564             xtype;
21565
21566         if (!me.id) {
21567             xtype = me.getXType();
21568             xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
21569             me.id = xtype + '-' + me.getAutoId();
21570         }
21571         return me.id;
21572     },
21573
21574     onEnable: function() {
21575         var actionEl = this.getActionEl();
21576         actionEl.dom.removeAttribute('aria-disabled');
21577         actionEl.dom.disabled = false;
21578         this.callParent();
21579     },
21580
21581     onDisable: function() {
21582         var actionEl = this.getActionEl();
21583         actionEl.dom.setAttribute('aria-disabled', true);
21584         actionEl.dom.disabled = true;
21585         this.callParent();
21586     },
21587
21588     /**
21589      * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
21590      *
21591      * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
21592      * brought to the front of its {@link #zIndexManager z-index stack}.
21593      *
21594      * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
21595      * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
21596      * with `floating: true`.** The target from which the Component should animate from while opening.
21597      * @param {Function} [callback] A callback function to call after the Component is displayed.
21598      * Only necessary if animation was specified.
21599      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
21600      * Defaults to this Component.
21601      * @return {Ext.Component} this
21602      */
21603     show: function(animateTarget, cb, scope) {
21604         var me = this;
21605
21606         if (me.rendered && me.isVisible()) {
21607             if (me.toFrontOnShow && me.floating) {
21608                 me.toFront();
21609             }
21610         } else if (me.fireEvent('beforeshow', me) !== false) {
21611             me.hidden = false;
21612
21613             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
21614             if (!me.rendered && (me.autoRender || me.floating)) {
21615                 me.doAutoRender();
21616             }
21617             if (me.rendered) {
21618                 me.beforeShow();
21619                 me.onShow.apply(me, arguments);
21620
21621                 // Notify any owning Container unless it's suspended.
21622                 // Floating Components do not participate in layouts.
21623                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
21624                     me.ownerCt.doLayout();
21625                 }
21626                 me.afterShow.apply(me, arguments);
21627             }
21628         }
21629         return me;
21630     },
21631
21632     beforeShow: Ext.emptyFn,
21633
21634     // Private. Override in subclasses where more complex behaviour is needed.
21635     onShow: function() {
21636         var me = this;
21637
21638         me.el.show();
21639         me.callParent(arguments);
21640         if (me.floating && me.constrain) {
21641             me.doConstrain();
21642         }
21643     },
21644
21645     afterShow: function(animateTarget, cb, scope) {
21646         var me = this,
21647             fromBox,
21648             toBox,
21649             ghostPanel;
21650
21651         // Default to configured animate target if none passed
21652         animateTarget = animateTarget || me.animateTarget;
21653
21654         // Need to be able to ghost the Component
21655         if (!me.ghost) {
21656             animateTarget = null;
21657         }
21658         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
21659         if (animateTarget) {
21660             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
21661             toBox = me.el.getBox();
21662             fromBox = animateTarget.getBox();
21663             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
21664             ghostPanel = me.ghost();
21665             ghostPanel.el.stopAnimation();
21666
21667             // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
21668             ghostPanel.el.setX(-10000);
21669
21670             ghostPanel.el.animate({
21671                 from: fromBox,
21672                 to: toBox,
21673                 listeners: {
21674                     afteranimate: function() {
21675                         delete ghostPanel.componentLayout.lastComponentSize;
21676                         me.unghost();
21677                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
21678                         me.onShowComplete(cb, scope);
21679                     }
21680                 }
21681             });
21682         }
21683         else {
21684             me.onShowComplete(cb, scope);
21685         }
21686     },
21687
21688     onShowComplete: function(cb, scope) {
21689         var me = this;
21690         if (me.floating) {
21691             me.toFront();
21692         }
21693         Ext.callback(cb, scope || me);
21694         me.fireEvent('show', me);
21695     },
21696
21697     /**
21698      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
21699      * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
21700      * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
21701      * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
21702      * @param {Function} [callback] A callback function to call after the Component is hidden.
21703      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
21704      * Defaults to this Component.
21705      * @return {Ext.Component} this
21706      */
21707     hide: function() {
21708         var me = this;
21709
21710         // Clear the flag which is set if a floatParent was hidden while this is visible.
21711         // If a hide operation was subsequently called, that pending show must be hidden.
21712         me.showOnParentShow = false;
21713
21714         if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
21715             me.hidden = true;
21716             if (me.rendered) {
21717                 me.onHide.apply(me, arguments);
21718
21719                 // Notify any owning Container unless it's suspended.
21720                 // Floating Components do not participate in layouts.
21721                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
21722                     me.ownerCt.doLayout();
21723                 }
21724             }
21725         }
21726         return me;
21727     },
21728
21729     // Possibly animate down to a target element.
21730     onHide: function(animateTarget, cb, scope) {
21731         var me = this,
21732             ghostPanel,
21733             toBox;
21734
21735         // Default to configured animate target if none passed
21736         animateTarget = animateTarget || me.animateTarget;
21737
21738         // Need to be able to ghost the Component
21739         if (!me.ghost) {
21740             animateTarget = null;
21741         }
21742         // If we're animating, kick off an animation of the ghost down to the target
21743         if (animateTarget) {
21744             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
21745             ghostPanel = me.ghost();
21746             ghostPanel.el.stopAnimation();
21747             toBox = animateTarget.getBox();
21748             toBox.width += 'px';
21749             toBox.height += 'px';
21750             ghostPanel.el.animate({
21751                 to: toBox,
21752                 listeners: {
21753                     afteranimate: function() {
21754                         delete ghostPanel.componentLayout.lastComponentSize;
21755                         ghostPanel.el.hide();
21756                         me.afterHide(cb, scope);
21757                     }
21758                 }
21759             });
21760         }
21761         me.el.hide();
21762         if (!animateTarget) {
21763             me.afterHide(cb, scope);
21764         }
21765     },
21766
21767     afterHide: function(cb, scope) {
21768         Ext.callback(cb, scope || this);
21769         this.fireEvent('hide', this);
21770     },
21771
21772     /**
21773      * @private
21774      * @template
21775      * Template method to contribute functionality at destroy time.
21776      */
21777     onDestroy: function() {
21778         var me = this;
21779
21780         // Ensure that any ancillary components are destroyed.
21781         if (me.rendered) {
21782             Ext.destroy(
21783                 me.proxy,
21784                 me.proxyWrap,
21785                 me.resizer
21786             );
21787             // Different from AbstractComponent
21788             if (me.actionMode == 'container' || me.removeMode == 'container') {
21789                 me.container.remove();
21790             }
21791         }
21792         delete me.focusTask;
21793         me.callParent();
21794     },
21795
21796     deleteMembers: function() {
21797         var args = arguments,
21798             len = args.length,
21799             i = 0;
21800         for (; i < len; ++i) {
21801             delete this[args[i]];
21802         }
21803     },
21804
21805     /**
21806      * Try to focus this component.
21807      * @param {Boolean} [selectText] If applicable, true to also select the text in this component
21808      * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
21809      * @return {Ext.Component} this
21810      */
21811     focus: function(selectText, delay) {
21812         var me = this,
21813                 focusEl;
21814
21815         if (delay) {
21816             if (!me.focusTask) {
21817                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
21818             }
21819             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
21820             return me;
21821         }
21822
21823         if (me.rendered && !me.isDestroyed) {
21824             // getFocusEl could return a Component.
21825             focusEl = me.getFocusEl();
21826             focusEl.focus();
21827             if (focusEl.dom && selectText === true) {
21828                 focusEl.dom.select();
21829             }
21830
21831             // Focusing a floating Component brings it to the front of its stack.
21832             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
21833             if (me.floating) {
21834                 me.toFront(true);
21835             }
21836         }
21837         return me;
21838     },
21839
21840     /**
21841      * @private
21842      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
21843      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
21844      * by the {@link #focus} method.
21845      * @returns {Ext.Element} the focus holing element.
21846      */
21847     getFocusEl: function() {
21848         return this.el;
21849     },
21850
21851     // private
21852     blur: function() {
21853         if (this.rendered) {
21854             this.getFocusEl().blur();
21855         }
21856         return this;
21857     },
21858
21859     getEl: function() {
21860         return this.el;
21861     },
21862
21863     // Deprecate 5.0
21864     getResizeEl: function() {
21865         return this.el;
21866     },
21867
21868     // Deprecate 5.0
21869     getPositionEl: function() {
21870         return this.el;
21871     },
21872
21873     // Deprecate 5.0
21874     getActionEl: function() {
21875         return this.el;
21876     },
21877
21878     // Deprecate 5.0
21879     getVisibilityEl: function() {
21880         return this.el;
21881     },
21882
21883     // Deprecate 5.0
21884     onResize: Ext.emptyFn,
21885
21886     // private
21887     getBubbleTarget: function() {
21888         return this.ownerCt;
21889     },
21890
21891     // private
21892     getContentTarget: function() {
21893         return this.el;
21894     },
21895
21896     /**
21897      * Clone the current component using the original config values passed into this instance by default.
21898      * @param {Object} overrides A new config containing any properties to override in the cloned version.
21899      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
21900      * @return {Ext.Component} clone The cloned copy of this component
21901      */
21902     cloneConfig: function(overrides) {
21903         overrides = overrides || {};
21904         var id = overrides.id || Ext.id(),
21905             cfg = Ext.applyIf(overrides, this.initialConfig),
21906             self;
21907
21908         cfg.id = id;
21909
21910         self = Ext.getClass(this);
21911
21912         // prevent dup id
21913         return new self(cfg);
21914     },
21915
21916     /**
21917      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
21918      * xtypes, see the {@link Ext.Component} header. Example usage:
21919      *
21920      *     var t = new Ext.form.field.Text();
21921      *     alert(t.getXType());  // alerts 'textfield'
21922      *
21923      * @return {String} The xtype
21924      */
21925     getXType: function() {
21926         return this.self.xtype;
21927     },
21928
21929     /**
21930      * Find a container above this component at any level by a custom function. If the passed function returns true, the
21931      * container will be returned.
21932      * @param {Function} fn The custom function to call with the arguments (container, this component).
21933      * @return {Ext.container.Container} The first Container for which the custom function returns true
21934      */
21935     findParentBy: function(fn) {
21936         var p;
21937
21938         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
21939         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
21940         return p || null;
21941     },
21942
21943     /**
21944      * Find a container above this component at any level by xtype or class
21945      *
21946      * See also the {@link Ext.Component#up up} method.
21947      *
21948      * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
21949      * @return {Ext.container.Container} The first Container which matches the given xtype or class
21950      */
21951     findParentByType: function(xtype) {
21952         return Ext.isFunction(xtype) ?
21953             this.findParentBy(function(p) {
21954                 return p.constructor === xtype;
21955             })
21956         :
21957             this.up(xtype);
21958     },
21959
21960     /**
21961      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
21962      * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
21963      * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
21964      *
21965      * @param {Function} fn The function to call
21966      * @param {Object} [scope] The scope of the function. Defaults to current node.
21967      * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
21968      * @return {Ext.Component} this
21969      */
21970     bubble: function(fn, scope, args) {
21971         var p = this;
21972         while (p) {
21973             if (fn.apply(scope || p, args || [p]) === false) {
21974                 break;
21975             }
21976             p = p.ownerCt;
21977         }
21978         return this;
21979     },
21980
21981     getProxy: function() {
21982         var me = this,
21983             target;
21984
21985         if (!me.proxy) {
21986             target = Ext.getBody();
21987             if (Ext.scopeResetCSS) {
21988                 me.proxyWrap = target = Ext.getBody().createChild({
21989                     cls: Ext.baseCSSPrefix + 'reset'
21990                 });
21991             }
21992             me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
21993         }
21994         return me.proxy;
21995     }
21996
21997 });
21998
21999 /**
22000  * @class Ext.layout.container.AbstractContainer
22001  * @extends Ext.layout.Layout
22002  * Please refer to sub classes documentation
22003  * @private
22004  */
22005 Ext.define('Ext.layout.container.AbstractContainer', {
22006
22007     /* Begin Definitions */
22008
22009     extend: 'Ext.layout.Layout',
22010
22011     /* End Definitions */
22012
22013     type: 'container',
22014
22015     /**
22016      * @cfg {Boolean} bindToOwnerCtComponent
22017      * Flag to notify the ownerCt Component on afterLayout of a change
22018      */
22019     bindToOwnerCtComponent: false,
22020
22021     /**
22022      * @cfg {Boolean} bindToOwnerCtContainer
22023      * Flag to notify the ownerCt Container on afterLayout of a change
22024      */
22025     bindToOwnerCtContainer: false,
22026
22027     /**
22028      * @cfg {String} itemCls
22029      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
22030      * customized styles to the container or any of its children using standard CSS rules. See
22031      * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
22032      * </p>
22033      */
22034
22035     /**
22036     * Set the size of an item within the Container.  We should always use setCalculatedSize.
22037     * @private
22038     */
22039     setItemSize: function(item, width, height) {
22040         if (Ext.isObject(width)) {
22041             height = width.height;
22042             width = width.width;
22043         }
22044         item.setCalculatedSize(width, height, this.owner);
22045     },
22046
22047     /**
22048      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
22049      * base class), or the layout phase (onLayout).</p>
22050      * @return {Ext.Component[]} of child components
22051      */
22052     getLayoutItems: function() {
22053         return this.owner && this.owner.items && this.owner.items.items || [];
22054     },
22055
22056     /**
22057      * Containers should not lay out child components when collapsed.
22058      */
22059     beforeLayout: function() {
22060         return !this.owner.collapsed && this.callParent(arguments);
22061     },
22062
22063     afterLayout: function() {
22064         this.owner.afterLayout(this);
22065     },
22066     /**
22067      * Returns the owner component's resize element.
22068      * @return {Ext.Element}
22069      */
22070      getTarget: function() {
22071          return this.owner.getTargetEl();
22072      },
22073     /**
22074      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
22075      * May be overridden in layout managers which implement an inner element.
22076      * @return {Ext.Element}
22077      */
22078      getRenderTarget: function() {
22079          return this.owner.getTargetEl();
22080      }
22081 });
22082
22083 /**
22084 * @class Ext.layout.container.Container
22085 * @extends Ext.layout.container.AbstractContainer
22086 * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
22087 * configuration property.  See {@link Ext.container.Container#layout} for additional details.</p>
22088 */
22089 Ext.define('Ext.layout.container.Container', {
22090
22091     /* Begin Definitions */
22092
22093     extend: 'Ext.layout.container.AbstractContainer',
22094     alternateClassName: 'Ext.layout.ContainerLayout',
22095
22096     /* End Definitions */
22097
22098     layoutItem: function(item, box) {
22099         if (box) {
22100             item.doComponentLayout(box.width, box.height);
22101         } else {
22102             item.doComponentLayout();
22103         }
22104     },
22105
22106     getLayoutTargetSize : function() {
22107         var target = this.getTarget(),
22108             ret;
22109
22110         if (target) {
22111             ret = target.getViewSize();
22112
22113             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
22114             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
22115             // with getViewSize
22116             if (Ext.isIE && ret.width == 0){
22117                 ret = target.getStyleSize();
22118             }
22119
22120             ret.width -= target.getPadding('lr');
22121             ret.height -= target.getPadding('tb');
22122         }
22123         return ret;
22124     },
22125
22126     beforeLayout: function() {
22127         if (this.owner.beforeLayout(arguments) !== false) {
22128             return this.callParent(arguments);
22129         }
22130         else {
22131             return false;
22132         }
22133     },
22134
22135     /**
22136      * @protected
22137      * Returns all items that are rendered
22138      * @return {Array} All matching items
22139      */
22140     getRenderedItems: function() {
22141         var me = this,
22142             target = me.getTarget(),
22143             items = me.getLayoutItems(),
22144             ln = items.length,
22145             renderedItems = [],
22146             i, item;
22147
22148         for (i = 0; i < ln; i++) {
22149             item = items[i];
22150             if (item.rendered && me.isValidParent(item, target, i)) {
22151                 renderedItems.push(item);
22152             }
22153         }
22154
22155         return renderedItems;
22156     },
22157
22158     /**
22159      * @protected
22160      * Returns all items that are both rendered and visible
22161      * @return {Array} All matching items
22162      */
22163     getVisibleItems: function() {
22164         var target   = this.getTarget(),
22165             items = this.getLayoutItems(),
22166             ln = items.length,
22167             visibleItems = [],
22168             i, item;
22169
22170         for (i = 0; i < ln; i++) {
22171             item = items[i];
22172             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
22173                 visibleItems.push(item);
22174             }
22175         }
22176
22177         return visibleItems;
22178     }
22179 });
22180 /**
22181  * @class Ext.layout.container.Auto
22182  * @extends Ext.layout.container.Container
22183  *
22184  * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
22185  * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
22186  * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
22187  * to any child containers.
22188  *
22189  *     @example
22190  *     Ext.create('Ext.Panel', {
22191  *         width: 500,
22192  *         height: 280,
22193  *         title: "AutoLayout Panel",
22194  *         layout: 'auto',
22195  *         renderTo: document.body,
22196  *         items: [{
22197  *             xtype: 'panel',
22198  *             title: 'Top Inner Panel',
22199  *             width: '75%',
22200  *             height: 90
22201  *         },
22202  *         {
22203  *             xtype: 'panel',
22204  *             title: 'Bottom Inner Panel',
22205  *             width: '75%',
22206  *             height: 90
22207  *         }]
22208  *     });
22209  */
22210 Ext.define('Ext.layout.container.Auto', {
22211
22212     /* Begin Definitions */
22213
22214     alias: ['layout.auto', 'layout.autocontainer'],
22215
22216     extend: 'Ext.layout.container.Container',
22217
22218     /* End Definitions */
22219
22220     type: 'autocontainer',
22221
22222     bindToOwnerCtComponent: true,
22223
22224     // @private
22225     onLayout : function(owner, target) {
22226         var me = this,
22227             items = me.getLayoutItems(),
22228             ln = items.length,
22229             i;
22230
22231         // Ensure the Container is only primed with the clear element if there are child items.
22232         if (ln) {
22233             // Auto layout uses natural HTML flow to arrange the child items.
22234             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
22235             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
22236             if (!me.clearEl) {
22237                 me.clearEl = me.getRenderTarget().createChild({
22238                     cls: Ext.baseCSSPrefix + 'clear',
22239                     role: 'presentation'
22240                 });
22241             }
22242
22243             // Auto layout allows CSS to size its child items.
22244             for (i = 0; i < ln; i++) {
22245                 me.setItemSize(items[i]);
22246             }
22247         }
22248     },
22249
22250     configureItem: function(item) {
22251         this.callParent(arguments);
22252
22253         // Auto layout does not manage any dimensions.
22254         item.layoutManagedHeight = 2;
22255         item.layoutManagedWidth = 2;
22256     }
22257 });
22258 /**
22259  * @class Ext.container.AbstractContainer
22260  * @extends Ext.Component
22261  * An abstract base class which provides shared methods for Containers across the Sencha product line.
22262  * @private
22263  */
22264 Ext.define('Ext.container.AbstractContainer', {
22265
22266     /* Begin Definitions */
22267
22268     extend: 'Ext.Component',
22269
22270     requires: [
22271         'Ext.util.MixedCollection',
22272         'Ext.layout.container.Auto',
22273         'Ext.ZIndexManager'
22274     ],
22275
22276     /* End Definitions */
22277     /**
22278      * @cfg {String/Object} layout
22279      * <p><b>Important</b>: In order for child items to be correctly sized and
22280      * positioned, typically a layout manager <b>must</b> be specified through
22281      * the <code>layout</code> configuration option.</p>
22282      * <p>The sizing and positioning of child {@link #items} is the responsibility of
22283      * the Container's layout manager which creates and manages the type of layout
22284      * you have in mind.  For example:</p>
22285      * <p>If the {@link #layout} configuration is not explicitly specified for
22286      * a general purpose container (e.g. Container or Panel) the
22287      * {@link Ext.layout.container.Auto default layout manager} will be used
22288      * which does nothing but render child components sequentially into the
22289      * Container (no sizing or positioning will be performed in this situation).</p>
22290      * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
22291      * <div><ul class="mdetail-params">
22292      * <li><u>Specify as an Object</u></li>
22293      * <div><ul class="mdetail-params">
22294      * <li>Example usage:</li>
22295      * <pre><code>
22296 layout: {
22297     type: 'vbox',
22298     align: 'left'
22299 }
22300        </code></pre>
22301      *
22302      * <li><code><b>type</b></code></li>
22303      * <br/><p>The layout type to be used for this container.  If not specified,
22304      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
22305      * <p>Valid layout <code>type</code> values are:</p>
22306      * <div class="sub-desc"><ul class="mdetail-params">
22307      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
22308      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
22309      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
22310      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
22311      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
22312      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
22313      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
22314      * </ul></div>
22315      *
22316      * <li>Layout specific configuration properties</li>
22317      * <p>Additional layout specific configuration properties may also be
22318      * specified. For complete details regarding the valid config options for
22319      * each layout type, see the layout class corresponding to the <code>type</code>
22320      * specified.</p>
22321      *
22322      * </ul></div>
22323      *
22324      * <li><u>Specify as a String</u></li>
22325      * <div><ul class="mdetail-params">
22326      * <li>Example usage:</li>
22327      * <pre><code>
22328 layout: 'vbox'
22329        </code></pre>
22330      * <li><code><b>layout</b></code></li>
22331      * <p>The layout <code>type</code> to be used for this container (see list
22332      * of valid layout type values above).</p>
22333      * <p>Additional layout specific configuration properties. For complete
22334      * details regarding the valid config options for each layout type, see the
22335      * layout class corresponding to the <code>layout</code> specified.</p>
22336      * </ul></div></ul></div>
22337      */
22338
22339     /**
22340      * @cfg {String/Number} activeItem
22341      * A string component id or the numeric index of the component that should be initially activated within the
22342      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
22343      * item in the container's collection).  activeItem only applies to layout styles that can display
22344      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
22345      */
22346     /**
22347      * @cfg {Object/Object[]} items
22348      * <p>A single item, or an array of child Components to be added to this container</p>
22349      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
22350      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
22351      * <p>Example:</p>
22352      * <pre><code>
22353 // specifying a single item
22354 items: {...},
22355 layout: 'fit',    // The single items is sized to fit
22356
22357 // specifying multiple items
22358 items: [{...}, {...}],
22359 layout: 'hbox', // The items are arranged horizontally
22360        </code></pre>
22361      * <p>Each item may be:</p>
22362      * <ul>
22363      * <li>A {@link Ext.Component Component}</li>
22364      * <li>A Component configuration object</li>
22365      * </ul>
22366      * <p>If a configuration object is specified, the actual type of Component to be
22367      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
22368      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
22369      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
22370      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
22371      * <p><b>Notes</b>:</p>
22372      * <p>Ext uses lazy rendering. Child Components will only be rendered
22373      * should it become necessary. Items are automatically laid out when they are first
22374      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
22375      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
22376      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
22377      */
22378     /**
22379      * @cfg {Object/Function} defaults
22380      * This option is a means of applying default settings to all added items whether added through the {@link #items}
22381      * config or via the {@link #add} or {@link #insert} methods.
22382      *
22383      * Defaults are applied to both config objects and instantiated components conditionally so as not to override
22384      * existing properties in the item (see {@link Ext#applyIf}).
22385      *
22386      * If the defaults option is specified as a function, then the function will be called using this Container as the
22387      * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
22388      * from that call is then applied to the item as default properties.
22389      *
22390      * For example, to automatically apply padding to the body of each of a set of
22391      * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
22392      *
22393      * Usage:
22394      *
22395      *     defaults: { // defaults are applied to items, not the container
22396      *         autoScroll: true
22397      *     },
22398      *     items: [
22399      *         // default will not be applied here, panel1 will be autoScroll: false
22400      *         {
22401      *             xtype: 'panel',
22402      *             id: 'panel1',
22403      *             autoScroll: false
22404      *         },
22405      *         // this component will have autoScroll: true
22406      *         new Ext.panel.Panel({
22407      *             id: 'panel2'
22408      *         })
22409      *     ]
22410      */
22411
22412     /** @cfg {Boolean} suspendLayout
22413      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
22414      * as multiple arguments or an array.
22415      */
22416     suspendLayout : false,
22417
22418     /** @cfg {Boolean} autoDestroy
22419      * If true the container will automatically destroy any contained component that is removed from it, else
22420      * destruction must be handled manually.
22421      * Defaults to true.
22422      */
22423     autoDestroy : true,
22424
22425      /** @cfg {String} defaultType
22426       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
22427       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
22428       * <p>Defaults to <code>'panel'</code>.</p>
22429       */
22430     defaultType: 'panel',
22431
22432     isContainer : true,
22433
22434     /**
22435      * The number of container layout calls made on this object.
22436      * @property layoutCounter
22437      * @type {Number}
22438      * @private
22439      */
22440     layoutCounter : 0,
22441
22442     baseCls: Ext.baseCSSPrefix + 'container',
22443
22444     /**
22445      * @cfg {String[]} bubbleEvents
22446      * <p>An array of events that, when fired, should be bubbled to any parent container.
22447      * See {@link Ext.util.Observable#enableBubble}.
22448      * Defaults to <code>['add', 'remove']</code>.
22449      */
22450     bubbleEvents: ['add', 'remove'],
22451
22452     // @private
22453     initComponent : function(){
22454         var me = this;
22455         me.addEvents(
22456             /**
22457              * @event afterlayout
22458              * Fires when the components in this container are arranged by the associated layout manager.
22459              * @param {Ext.container.Container} this
22460              * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
22461              */
22462             'afterlayout',
22463             /**
22464              * @event beforeadd
22465              * Fires before any {@link Ext.Component} is added or inserted into the container.
22466              * A handler can return false to cancel the add.
22467              * @param {Ext.container.Container} this
22468              * @param {Ext.Component} component The component being added
22469              * @param {Number} index The index at which the component will be added to the container's items collection
22470              */
22471             'beforeadd',
22472             /**
22473              * @event beforeremove
22474              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
22475              * false to cancel the remove.
22476              * @param {Ext.container.Container} this
22477              * @param {Ext.Component} component The component being removed
22478              */
22479             'beforeremove',
22480             /**
22481              * @event add
22482              * @bubbles
22483              * Fires after any {@link Ext.Component} is added or inserted into the container.
22484              * @param {Ext.container.Container} this
22485              * @param {Ext.Component} component The component that was added
22486              * @param {Number} index The index at which the component was added to the container's items collection
22487              */
22488             'add',
22489             /**
22490              * @event remove
22491              * @bubbles
22492              * Fires after any {@link Ext.Component} is removed from the container.
22493              * @param {Ext.container.Container} this
22494              * @param {Ext.Component} component The component that was removed
22495              */
22496             'remove'
22497         );
22498
22499         // layoutOnShow stack
22500         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
22501         me.callParent();
22502         me.initItems();
22503     },
22504
22505     // @private
22506     initItems : function() {
22507         var me = this,
22508             items = me.items;
22509
22510         /**
22511          * The MixedCollection containing all the child items of this container.
22512          * @property items
22513          * @type Ext.util.MixedCollection
22514          */
22515         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
22516
22517         if (items) {
22518             if (!Ext.isArray(items)) {
22519                 items = [items];
22520             }
22521
22522             me.add(items);
22523         }
22524     },
22525
22526     // @private
22527     afterRender : function() {
22528         this.getLayout();
22529         this.callParent();
22530     },
22531
22532     renderChildren: function () {
22533         var me = this,
22534             layout = me.getLayout();
22535
22536         me.callParent();
22537         // this component's elements exist, so now create the child components' elements
22538
22539         if (layout) {
22540             me.suspendLayout = true;
22541             layout.renderChildren();
22542             delete me.suspendLayout;
22543         }
22544     },
22545
22546     // @private
22547     setLayout : function(layout) {
22548         var currentLayout = this.layout;
22549
22550         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
22551             currentLayout.setOwner(null);
22552         }
22553
22554         this.layout = layout;
22555         layout.setOwner(this);
22556     },
22557
22558     /**
22559      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
22560      * If a layout has not been instantiated yet, that is done first
22561      * @return {Ext.layout.container.AbstractContainer} The layout
22562      */
22563     getLayout : function() {
22564         var me = this;
22565         if (!me.layout || !me.layout.isLayout) {
22566             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
22567         }
22568
22569         return me.layout;
22570     },
22571
22572     /**
22573      * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
22574      * form most cases.
22575      * @return {Ext.container.Container} this
22576      */
22577     doLayout : function() {
22578         var me = this,
22579             layout = me.getLayout();
22580
22581         if (me.rendered && layout && !me.suspendLayout) {
22582             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
22583             if (!me.isFixedWidth() || !me.isFixedHeight()) {
22584                 // Only run the ComponentLayout if it is not already in progress
22585                 if (me.componentLayout.layoutBusy !== true) {
22586                     me.doComponentLayout();
22587                     if (me.componentLayout.layoutCancelled === true) {
22588                         layout.layout();
22589                     }
22590                 }
22591             }
22592             // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
22593             else {
22594                 // Only run the ContainerLayout if it is not already in progress
22595                 if (layout.layoutBusy !== true) {
22596                     layout.layout();
22597                 }
22598             }
22599         }
22600
22601         return me;
22602     },
22603
22604     // @private
22605     afterLayout : function(layout) {
22606         ++this.layoutCounter;
22607         this.fireEvent('afterlayout', this, layout);
22608     },
22609
22610     // @private
22611     prepareItems : function(items, applyDefaults) {
22612         if (!Ext.isArray(items)) {
22613             items = [items];
22614         }
22615
22616         // Make sure defaults are applied and item is initialized
22617         var i = 0,
22618             len = items.length,
22619             item;
22620
22621         for (; i < len; i++) {
22622             item = items[i];
22623             if (applyDefaults) {
22624                 item = this.applyDefaults(item);
22625             }
22626             items[i] = this.lookupComponent(item);
22627         }
22628         return items;
22629     },
22630
22631     // @private
22632     applyDefaults : function(config) {
22633         var defaults = this.defaults;
22634
22635         if (defaults) {
22636             if (Ext.isFunction(defaults)) {
22637                 defaults = defaults.call(this, config);
22638             }
22639
22640             if (Ext.isString(config)) {
22641                 config = Ext.ComponentManager.get(config);
22642             }
22643             Ext.applyIf(config, defaults);
22644         }
22645
22646         return config;
22647     },
22648
22649     // @private
22650     lookupComponent : function(comp) {
22651         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
22652     },
22653
22654     // @private
22655     createComponent : function(config, defaultType) {
22656         // // add in ownerCt at creation time but then immediately
22657         // // remove so that onBeforeAdd can handle it
22658         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
22659         //
22660         // delete component.initialConfig.ownerCt;
22661         // delete component.ownerCt;
22662
22663         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
22664     },
22665
22666     // @private - used as the key lookup function for the items collection
22667     getComponentId : function(comp) {
22668         return comp.getItemId();
22669     },
22670
22671     /**
22672
22673 Adds {@link Ext.Component Component}(s) to this Container.
22674
22675 ##Description:##
22676
22677 - Fires the {@link #beforeadd} event before adding.
22678 - The Container's {@link #defaults default config values} will be applied
22679   accordingly (see `{@link #defaults}` for details).
22680 - Fires the `{@link #add}` event after the component has been added.
22681
22682 ##Notes:##
22683
22684 If the Container is __already rendered__ when `add`
22685 is called, it will render the newly added Component into its content area.
22686
22687 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
22688 will recalculate its internal layout at this time too.
22689
22690 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
22691
22692 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
22693
22694     tb = new {@link Ext.toolbar.Toolbar}({
22695         renderTo: document.body
22696     });  // toolbar is rendered
22697     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
22698
22699 ##Warning:##
22700
22701 Components directly managed by the BorderLayout layout manager
22702 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
22703 for more details.
22704
22705      * @param {Ext.Component[]/Ext.Component...} component
22706      * Either one or more Components to add or an Array of Components to add.
22707      * See `{@link #items}` for additional information.
22708      *
22709      * @return {Ext.Component[]/Ext.Component} The Components that were added.
22710      * @markdown
22711      */
22712     add : function() {
22713         var me = this,
22714             args = Array.prototype.slice.call(arguments),
22715             hasMultipleArgs,
22716             items,
22717             results = [],
22718             i,
22719             ln,
22720             item,
22721             index = -1,
22722             cmp;
22723
22724         if (typeof args[0] == 'number') {
22725             index = args.shift();
22726         }
22727
22728         hasMultipleArgs = args.length > 1;
22729         if (hasMultipleArgs || Ext.isArray(args[0])) {
22730
22731             items = hasMultipleArgs ? args : args[0];
22732             // Suspend Layouts while we add multiple items to the container
22733             me.suspendLayout = true;
22734             for (i = 0, ln = items.length; i < ln; i++) {
22735                 item = items[i];
22736
22737                 //<debug>
22738                 if (!item) {
22739                     Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
22740                 }
22741                 //</debug>
22742
22743                 if (index != -1) {
22744                     item = me.add(index + i, item);
22745                 } else {
22746                     item = me.add(item);
22747                 }
22748                 results.push(item);
22749             }
22750             // Resume Layouts now that all items have been added and do a single layout for all the items just added
22751             me.suspendLayout = false;
22752             me.doLayout();
22753             return results;
22754         }
22755
22756         cmp = me.prepareItems(args[0], true)[0];
22757
22758         // Floating Components are not added into the items collection
22759         // But they do get an upward ownerCt link so that they can traverse
22760         // up to their z-index parent.
22761         if (cmp.floating) {
22762             cmp.onAdded(me, index);
22763         } else {
22764             index = (index !== -1) ? index : me.items.length;
22765             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
22766                 me.items.insert(index, cmp);
22767                 cmp.onAdded(me, index);
22768                 me.onAdd(cmp, index);
22769                 me.fireEvent('add', me, cmp, index);
22770             }
22771             me.doLayout();
22772         }
22773         return cmp;
22774     },
22775
22776     onAdd : Ext.emptyFn,
22777     onRemove : Ext.emptyFn,
22778
22779     /**
22780      * Inserts a Component into this Container at a specified index. Fires the
22781      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
22782      * Component has been inserted.
22783      * @param {Number} index The index at which the Component will be inserted
22784      * into the Container's items collection
22785      * @param {Ext.Component} component The child Component to insert.<br><br>
22786      * Ext uses lazy rendering, and will only render the inserted Component should
22787      * it become necessary.<br><br>
22788      * A Component config object may be passed in order to avoid the overhead of
22789      * constructing a real Component object if lazy rendering might mean that the
22790      * inserted Component will not be rendered immediately. To take advantage of
22791      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
22792      * property to the registered type of the Component wanted.<br><br>
22793      * For a list of all available xtypes, see {@link Ext.Component}.
22794      * @return {Ext.Component} component The Component (or config object) that was
22795      * inserted with the Container's default config values applied.
22796      */
22797     insert : function(index, comp) {
22798         return this.add(index, comp);
22799     },
22800
22801     /**
22802      * Moves a Component within the Container
22803      * @param {Number} fromIdx The index the Component you wish to move is currently at.
22804      * @param {Number} toIdx The new index for the Component.
22805      * @return {Ext.Component} component The Component (or config object) that was moved.
22806      */
22807     move : function(fromIdx, toIdx) {
22808         var items = this.items,
22809             item;
22810         item = items.removeAt(fromIdx);
22811         if (item === false) {
22812             return false;
22813         }
22814         items.insert(toIdx, item);
22815         this.doLayout();
22816         return item;
22817     },
22818
22819     // @private
22820     onBeforeAdd : function(item) {
22821         var me = this;
22822
22823         if (item.ownerCt) {
22824             item.ownerCt.remove(item, false);
22825         }
22826
22827         if (me.border === false || me.border === 0) {
22828             item.border = (item.border === true);
22829         }
22830     },
22831
22832     /**
22833      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
22834      * the {@link #remove} event after the component has been removed.
22835      * @param {Ext.Component/String} component The component reference or id to remove.
22836      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
22837      * Defaults to the value of this Container's {@link #autoDestroy} config.
22838      * @return {Ext.Component} component The Component that was removed.
22839      */
22840     remove : function(comp, autoDestroy) {
22841         var me = this,
22842             c = me.getComponent(comp);
22843         //<debug>
22844             if (Ext.isDefined(Ext.global.console) && !c) {
22845                 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.");
22846             }
22847         //</debug>
22848
22849         if (c && me.fireEvent('beforeremove', me, c) !== false) {
22850             me.doRemove(c, autoDestroy);
22851             me.fireEvent('remove', me, c);
22852         }
22853
22854         return c;
22855     },
22856
22857     // @private
22858     doRemove : function(component, autoDestroy) {
22859         var me = this,
22860             layout = me.layout,
22861             hasLayout = layout && me.rendered;
22862
22863         me.items.remove(component);
22864         component.onRemoved();
22865
22866         if (hasLayout) {
22867             layout.onRemove(component);
22868         }
22869
22870         me.onRemove(component, autoDestroy);
22871
22872         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
22873             component.destroy();
22874         }
22875
22876         if (hasLayout && !autoDestroy) {
22877             layout.afterRemove(component);
22878         }
22879
22880         if (!me.destroying) {
22881             me.doLayout();
22882         }
22883     },
22884
22885     /**
22886      * Removes all components from this container.
22887      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
22888      * Defaults to the value of this Container's {@link #autoDestroy} config.
22889      * @return {Ext.Component[]} Array of the destroyed components
22890      */
22891     removeAll : function(autoDestroy) {
22892         var me = this,
22893             removeItems = me.items.items.slice(),
22894             items = [],
22895             i = 0,
22896             len = removeItems.length,
22897             item;
22898
22899         // Suspend Layouts while we remove multiple items from the container
22900         me.suspendLayout = true;
22901         for (; i < len; i++) {
22902             item = removeItems[i];
22903             me.remove(item, autoDestroy);
22904
22905             if (item.ownerCt !== me) {
22906                 items.push(item);
22907             }
22908         }
22909
22910         // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
22911         me.suspendLayout = false;
22912         if (len) {
22913             me.doLayout();
22914         }
22915         return items;
22916     },
22917
22918     // Used by ComponentQuery to retrieve all of the items
22919     // which can potentially be considered a child of this Container.
22920     // This should be overriden by components which have child items
22921     // that are not contained in items. For example dockedItems, menu, etc
22922     // IMPORTANT note for maintainers:
22923     //  Items are returned in tree traversal order. Each item is appended to the result array
22924     //  followed by the results of that child's getRefItems call.
22925     //  Floating child items are appended after internal child items.
22926     getRefItems : function(deep) {
22927         var me = this,
22928             items = me.items.items,
22929             len = items.length,
22930             i = 0,
22931             item,
22932             result = [];
22933
22934         for (; i < len; i++) {
22935             item = items[i];
22936             result.push(item);
22937             if (deep && item.getRefItems) {
22938                 result.push.apply(result, item.getRefItems(true));
22939             }
22940         }
22941
22942         // Append floating items to the list.
22943         // These will only be present after they are rendered.
22944         if (me.floatingItems && me.floatingItems.accessList) {
22945             result.push.apply(result, me.floatingItems.accessList);
22946         }
22947
22948         return result;
22949     },
22950
22951     /**
22952      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
22953      * each component. The scope (<code>this</code> reference) of the
22954      * function call will be the scope provided or the current component. The arguments to the function
22955      * will be the args provided or the current component. If the function returns false at any point,
22956      * the cascade is stopped on that branch.
22957      * @param {Function} fn The function to call
22958      * @param {Object} [scope] The scope of the function (defaults to current component)
22959      * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
22960      * @return {Ext.Container} this
22961      */
22962     cascade : function(fn, scope, origArgs){
22963         var me = this,
22964             cs = me.items ? me.items.items : [],
22965             len = cs.length,
22966             i = 0,
22967             c,
22968             args = origArgs ? origArgs.concat(me) : [me],
22969             componentIndex = args.length - 1;
22970
22971         if (fn.apply(scope || me, args) !== false) {
22972             for(; i < len; i++){
22973                 c = cs[i];
22974                 if (c.cascade) {
22975                     c.cascade(fn, scope, origArgs);
22976                 } else {
22977                     args[componentIndex] = c;
22978                     fn.apply(scope || cs, args);
22979                 }
22980             }
22981         }
22982         return this;
22983     },
22984
22985     /**
22986      * Examines this container's <code>{@link #items}</code> <b>property</b>
22987      * and gets a direct child component of this container.
22988      * @param {String/Number} comp This parameter may be any of the following:
22989      * <div><ul class="mdetail-params">
22990      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
22991      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
22992      * <li>a <b><code>Number</code></b> : representing the position of the child component
22993      * within the <code>{@link #items}</code> <b>property</b></li>
22994      * </ul></div>
22995      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
22996      * @return Ext.Component The component (if found).
22997      */
22998     getComponent : function(comp) {
22999         if (Ext.isObject(comp)) {
23000             comp = comp.getItemId();
23001         }
23002
23003         return this.items.get(comp);
23004     },
23005
23006     /**
23007      * Retrieves all descendant components which match the passed selector.
23008      * Executes an Ext.ComponentQuery.query using this container as its root.
23009      * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
23010      * If no selector is specified all items will be returned.
23011      * @return {Ext.Component[]} Components which matched the selector
23012      */
23013     query : function(selector) {
23014         selector = selector || '*';
23015         return Ext.ComponentQuery.query(selector, this);
23016     },
23017
23018     /**
23019      * Retrieves the first direct child of this container which matches the passed selector.
23020      * The passed in selector must comply with an Ext.ComponentQuery selector.
23021      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
23022      * specified, the first child will be returned.
23023      * @return Ext.Component
23024      */
23025     child : function(selector) {
23026         selector = selector || '';
23027         return this.query('> ' + selector)[0] || null;
23028     },
23029
23030     /**
23031      * Retrieves the first descendant of this container which matches the passed selector.
23032      * The passed in selector must comply with an Ext.ComponentQuery selector.
23033      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
23034      * specified, the first child will be returned.
23035      * @return Ext.Component
23036      */
23037     down : function(selector) {
23038         return this.query(selector)[0] || null;
23039     },
23040
23041     // inherit docs
23042     show : function() {
23043         this.callParent(arguments);
23044         this.performDeferredLayouts();
23045         return this;
23046     },
23047
23048     // Lay out any descendant containers who queued a layout operation during the time this was hidden
23049     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
23050     performDeferredLayouts: function() {
23051         var layoutCollection = this.layoutOnShow,
23052             ln = layoutCollection.getCount(),
23053             i = 0,
23054             needsLayout,
23055             item;
23056
23057         for (; i < ln; i++) {
23058             item = layoutCollection.get(i);
23059             needsLayout = item.needsLayout;
23060
23061             if (Ext.isObject(needsLayout)) {
23062                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
23063             }
23064         }
23065         layoutCollection.clear();
23066     },
23067
23068     //@private
23069     // Enable all immediate children that was previously disabled
23070     onEnable: function() {
23071         Ext.Array.each(this.query('[isFormField]'), function(item) {
23072             if (item.resetDisable) {
23073                 item.enable();
23074                 delete item.resetDisable;
23075             }
23076         });
23077         this.callParent();
23078     },
23079
23080     // @private
23081     // Disable all immediate children that was previously disabled
23082     onDisable: function() {
23083         Ext.Array.each(this.query('[isFormField]'), function(item) {
23084             if (item.resetDisable !== false && !item.disabled) {
23085                 item.disable();
23086                 item.resetDisable = true;
23087             }
23088         });
23089         this.callParent();
23090     },
23091
23092     /**
23093      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
23094      * from being executed.
23095      */
23096     beforeLayout: function() {
23097         return true;
23098     },
23099
23100     // @private
23101     beforeDestroy : function() {
23102         var me = this,
23103             items = me.items,
23104             c;
23105
23106         if (items) {
23107             while ((c = items.first())) {
23108                 me.doRemove(c, true);
23109             }
23110         }
23111
23112         Ext.destroy(
23113             me.layout
23114         );
23115         me.callParent();
23116     }
23117 });
23118
23119 /**
23120  * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
23121  * containing items, namely adding, inserting and removing items.
23122  *
23123  * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
23124  * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
23125  * lightweight Container to be encapsulated by an HTML element to your specifications by using the
23126  * {@link Ext.Component#autoEl autoEl} config option.
23127  *
23128  * The code below illustrates how to explicitly create a Container:
23129  *
23130  *     @example
23131  *     // Explicitly create a Container
23132  *     Ext.create('Ext.container.Container', {
23133  *         layout: {
23134  *             type: 'hbox'
23135  *         },
23136  *         width: 400,
23137  *         renderTo: Ext.getBody(),
23138  *         border: 1,
23139  *         style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
23140  *         defaults: {
23141  *             labelWidth: 80,
23142  *             // implicitly create Container by specifying xtype
23143  *             xtype: 'datefield',
23144  *             flex: 1,
23145  *             style: {
23146  *                 padding: '10px'
23147  *             }
23148  *         },
23149  *         items: [{
23150  *             xtype: 'datefield',
23151  *             name: 'startDate',
23152  *             fieldLabel: 'Start date'
23153  *         },{
23154  *             xtype: 'datefield',
23155  *             name: 'endDate',
23156  *             fieldLabel: 'End date'
23157  *         }]
23158  *     });
23159  *
23160  * ## Layout
23161  *
23162  * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
23163  * the Container using the `{@link #layout}` configuration property.
23164  *
23165  * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
23166  * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
23167  * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
23168  * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
23169  * inside the Container, and **does not apply any sizing** at all.
23170  *
23171  * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
23172  * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
23173  * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
23174  * any way when the Container is resized.
23175  *
23176  * Certain layout managers allow dynamic addition of child components. Those that do include
23177  * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
23178  * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
23179  *
23180  *     //  Create the GridPanel.
23181  *     var myNewGrid = new Ext.grid.Panel({
23182  *         store: myStore,
23183  *         headers: myHeaders,
23184  *         title: 'Results', // the title becomes the title of the tab
23185  *     });
23186  *
23187  *     myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
23188  *     myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
23189  *
23190  * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
23191  * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
23192  * Ext.layout.container.Fit fit} exactly into its client area.
23193  *
23194  * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
23195  * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
23196  * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
23197  * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
23198  * GridPanel will not be sized as expected.
23199  *
23200  * ## Adding via remote configuration
23201  *
23202  * A server side script can be used to add Components which are generated dynamically on the server. An example of
23203  * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
23204  *
23205  *     // execute an Ajax request to invoke server side script:
23206  *     Ext.Ajax.request({
23207  *         url: 'gen-invoice-grid.php',
23208  *         // send additional parameters to instruct server script
23209  *         params: {
23210  *             startDate: Ext.getCmp('start-date').getValue(),
23211  *             endDate: Ext.getCmp('end-date').getValue()
23212  *         },
23213  *         // process the response object to add it to the TabPanel:
23214  *         success: function(xhr) {
23215  *             var newComponent = eval(xhr.responseText); // see discussion below
23216  *             myTabPanel.add(newComponent); // add the component to the TabPanel
23217  *             myTabPanel.setActiveTab(newComponent);
23218  *         },
23219  *         failure: function() {
23220  *             Ext.Msg.alert("Grid create failed", "Server communication failure");
23221  *         }
23222  *     });
23223  *
23224  * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
23225  * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
23226  *
23227  *     {
23228  *         "xtype": 'grid',
23229  *         "title": 'Invoice Report',
23230  *         "store": {
23231  *             "model": 'Invoice',
23232  *             "proxy": {
23233  *                 "type": 'ajax',
23234  *                 "url": 'get-invoice-data.php',
23235  *                 "reader": {
23236  *                     "type": 'json'
23237  *                     "record": 'transaction',
23238  *                     "idProperty": 'id',
23239  *                     "totalRecords": 'total'
23240  *                 })
23241  *             },
23242  *             "autoLoad": {
23243  *                 "params": {
23244  *                     "startDate": '01/01/2008',
23245  *                     "endDate": '01/31/2008'
23246  *                 }
23247  *             }
23248  *         },
23249  *         "headers": [
23250  *             {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
23251  *             {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
23252  *             {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
23253  *             {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
23254  *         ]
23255  *     }
23256  *
23257  * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
23258  * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
23259  * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
23260  *
23261  * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
23262  * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
23263  * into the code since these are all known on the server.
23264  */
23265 Ext.define('Ext.container.Container', {
23266     extend: 'Ext.container.AbstractContainer',
23267     alias: 'widget.container',
23268     alternateClassName: 'Ext.Container',
23269
23270     /**
23271      * Return the immediate child Component in which the passed element is located.
23272      * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
23273      * @return {Ext.Component} The child item which contains the passed element.
23274      */
23275     getChildByElement: function(el) {
23276         var item,
23277             itemEl,
23278             i = 0,
23279             it = this.items.items,
23280             ln = it.length;
23281
23282         el = Ext.getDom(el);
23283         for (; i < ln; i++) {
23284             item = it[i];
23285             itemEl = item.getEl();
23286             if ((itemEl.dom === el) || itemEl.contains(el)) {
23287                 return item;
23288             }
23289         }
23290         return null;
23291     }
23292 });
23293
23294 /**
23295  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
23296  * the right-justified button container.
23297  *
23298  *     @example
23299  *     Ext.create('Ext.panel.Panel', {
23300  *          title: 'Toolbar Fill Example',
23301  *          width: 300,
23302  *          height: 200,
23303  *          tbar : [
23304  *              'Item 1',
23305  *              { xtype: 'tbfill' },
23306  *              'Item 2'
23307  *          ],
23308  *          renderTo: Ext.getBody()
23309  *      });
23310  */
23311 Ext.define('Ext.toolbar.Fill', {
23312     extend: 'Ext.Component',
23313     alias: 'widget.tbfill',
23314     alternateClassName: 'Ext.Toolbar.Fill',
23315     isFill : true,
23316     flex: 1
23317 });
23318 /**
23319  * @class Ext.toolbar.Item
23320  * @extends Ext.Component
23321  * The base class that other non-interacting Toolbar Item classes should extend in order to
23322  * get some basic common toolbar item functionality.
23323  */
23324 Ext.define('Ext.toolbar.Item', {
23325     extend: 'Ext.Component',
23326     alias: 'widget.tbitem',
23327     alternateClassName: 'Ext.Toolbar.Item',
23328     enable:Ext.emptyFn,
23329     disable:Ext.emptyFn,
23330     focus:Ext.emptyFn
23331     /**
23332      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
23333      */
23334 });
23335 /**
23336  * @class Ext.toolbar.Separator
23337  * @extends Ext.toolbar.Item
23338  * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
23339  *
23340  *     @example
23341  *     Ext.create('Ext.panel.Panel', {
23342  *         title: 'Toolbar Seperator Example',
23343  *         width: 300,
23344  *         height: 200,
23345  *         tbar : [
23346  *             'Item 1',
23347  *             { xtype: 'tbseparator' },
23348  *             'Item 2'
23349  *         ],
23350  *         renderTo: Ext.getBody()
23351  *     });
23352  */
23353 Ext.define('Ext.toolbar.Separator', {
23354     extend: 'Ext.toolbar.Item',
23355     alias: 'widget.tbseparator',
23356     alternateClassName: 'Ext.Toolbar.Separator',
23357     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
23358     focusable: false
23359 });
23360 /**
23361  * @class Ext.menu.Manager
23362  * Provides a common registry of all menus on a page.
23363  * @singleton
23364  */
23365 Ext.define('Ext.menu.Manager', {
23366     singleton: true,
23367     requires: [
23368         'Ext.util.MixedCollection',
23369         'Ext.util.KeyMap'
23370     ],
23371     alternateClassName: 'Ext.menu.MenuMgr',
23372
23373     uses: ['Ext.menu.Menu'],
23374
23375     menus: {},
23376     groups: {},
23377     attached: false,
23378     lastShow: new Date(),
23379
23380     init: function() {
23381         var me = this;
23382         
23383         me.active = Ext.create('Ext.util.MixedCollection');
23384         Ext.getDoc().addKeyListener(27, function() {
23385             if (me.active.length > 0) {
23386                 me.hideAll();
23387             }
23388         }, me);
23389     },
23390
23391     /**
23392      * Hides all menus that are currently visible
23393      * @return {Boolean} success True if any active menus were hidden.
23394      */
23395     hideAll: function() {
23396         var active = this.active,
23397             c;
23398         if (active && active.length > 0) {
23399             c = active.clone();
23400             c.each(function(m) {
23401                 m.hide();
23402             });
23403             return true;
23404         }
23405         return false;
23406     },
23407
23408     onHide: function(m) {
23409         var me = this,
23410             active = me.active;
23411         active.remove(m);
23412         if (active.length < 1) {
23413             Ext.getDoc().un('mousedown', me.onMouseDown, me);
23414             me.attached = false;
23415         }
23416     },
23417
23418     onShow: function(m) {
23419         var me = this,
23420             active   = me.active,
23421             last     = active.last(),
23422             attached = me.attached,
23423             menuEl   = m.getEl(),
23424             zIndex;
23425
23426         me.lastShow = new Date();
23427         active.add(m);
23428         if (!attached) {
23429             Ext.getDoc().on('mousedown', me.onMouseDown, me);
23430             me.attached = true;
23431         }
23432         m.toFront();
23433     },
23434
23435     onBeforeHide: function(m) {
23436         if (m.activeChild) {
23437             m.activeChild.hide();
23438         }
23439         if (m.autoHideTimer) {
23440             clearTimeout(m.autoHideTimer);
23441             delete m.autoHideTimer;
23442         }
23443     },
23444
23445     onBeforeShow: function(m) {
23446         var active = this.active,
23447             parentMenu = m.parentMenu;
23448             
23449         active.remove(m);
23450         if (!parentMenu && !m.allowOtherMenus) {
23451             this.hideAll();
23452         }
23453         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
23454             parentMenu.activeChild.hide();
23455         }
23456     },
23457
23458     // private
23459     onMouseDown: function(e) {
23460         var me = this,
23461             active = me.active,
23462             lastShow = me.lastShow,
23463             target = e.target;
23464
23465         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
23466             me.hideAll();
23467             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
23468             // fired on the element, so we'll focus it here
23469             if (Ext.isIE && Ext.fly(target).focusable()) {
23470                 target.focus();
23471             }
23472         }
23473     },
23474
23475     // private
23476     register: function(menu) {
23477         var me = this;
23478
23479         if (!me.active) {
23480             me.init();
23481         }
23482
23483         if (menu.floating) {
23484             me.menus[menu.id] = menu;
23485             menu.on({
23486                 beforehide: me.onBeforeHide,
23487                 hide: me.onHide,
23488                 beforeshow: me.onBeforeShow,
23489                 show: me.onShow,
23490                 scope: me
23491             });
23492         }
23493     },
23494
23495     /**
23496      * Returns a {@link Ext.menu.Menu} object
23497      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
23498      * be used to generate and return a new Menu this.
23499      * @return {Ext.menu.Menu} The specified menu, or null if none are found
23500      */
23501     get: function(menu) {
23502         var menus = this.menus;
23503         
23504         if (typeof menu == 'string') { // menu id
23505             if (!menus) {  // not initialized, no menus to return
23506                 return null;
23507             }
23508             return menus[menu];
23509         } else if (menu.isMenu) {  // menu instance
23510             return menu;
23511         } else if (Ext.isArray(menu)) { // array of menu items
23512             return Ext.create('Ext.menu.Menu', {items:menu});
23513         } else { // otherwise, must be a config
23514             return Ext.ComponentManager.create(menu, 'menu');
23515         }
23516     },
23517
23518     // private
23519     unregister: function(menu) {
23520         var me = this,
23521             menus = me.menus,
23522             active = me.active;
23523
23524         delete menus[menu.id];
23525         active.remove(menu);
23526         menu.un({
23527             beforehide: me.onBeforeHide,
23528             hide: me.onHide,
23529             beforeshow: me.onBeforeShow,
23530             show: me.onShow,
23531             scope: me
23532         });
23533     },
23534
23535     // private
23536     registerCheckable: function(menuItem) {
23537         var groups  = this.groups,
23538             groupId = menuItem.group;
23539
23540         if (groupId) {
23541             if (!groups[groupId]) {
23542                 groups[groupId] = [];
23543             }
23544
23545             groups[groupId].push(menuItem);
23546         }
23547     },
23548
23549     // private
23550     unregisterCheckable: function(menuItem) {
23551         var groups  = this.groups,
23552             groupId = menuItem.group;
23553
23554         if (groupId) {
23555             Ext.Array.remove(groups[groupId], menuItem);
23556         }
23557     },
23558
23559     onCheckChange: function(menuItem, state) {
23560         var groups  = this.groups,
23561             groupId = menuItem.group,
23562             i       = 0,
23563             group, ln, curr;
23564
23565         if (groupId && state) {
23566             group = groups[groupId];
23567             ln = group.length;
23568             for (; i < ln; i++) {
23569                 curr = group[i];
23570                 if (curr != menuItem) {
23571                     curr.setChecked(false);
23572                 }
23573             }
23574         }
23575     }
23576 });
23577 /**
23578  * Component layout for buttons
23579  * @class Ext.layout.component.Button
23580  * @extends Ext.layout.component.Component
23581  * @private
23582  */
23583 Ext.define('Ext.layout.component.Button', {
23584
23585     /* Begin Definitions */
23586
23587     alias: ['layout.button'],
23588
23589     extend: 'Ext.layout.component.Component',
23590
23591     /* End Definitions */
23592
23593     type: 'button',
23594
23595     cellClsRE: /-btn-(tl|br)\b/,
23596     htmlRE: /<.*>/,
23597
23598     beforeLayout: function() {
23599         return this.callParent(arguments) || this.lastText !== this.owner.text;
23600     },
23601
23602     /**
23603      * Set the dimensions of the inner &lt;button&gt; element to match the
23604      * component dimensions.
23605      */
23606     onLayout: function(width, height) {
23607         var me = this,
23608             isNum = Ext.isNumber,
23609             owner = me.owner,
23610             ownerEl = owner.el,
23611             btnEl = owner.btnEl,
23612             btnInnerEl = owner.btnInnerEl,
23613             btnIconEl = owner.btnIconEl,
23614             sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
23615             minWidth = owner.minWidth,
23616             maxWidth = owner.maxWidth,
23617             ownerWidth, btnFrameWidth, metrics;
23618
23619         me.getTargetInfo();
23620         me.callParent(arguments);
23621
23622         btnInnerEl.unclip();
23623         me.setTargetSize(width, height);
23624
23625         if (!isNum(width)) {
23626             // In IE7 strict mode button elements with width:auto get strange extra side margins within
23627             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
23628             // the size of the text and set the width to match.
23629             if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
23630                 btnFrameWidth = me.btnFrameWidth;
23631                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
23632                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
23633                 btnEl.setWidth(metrics.width + btnFrameWidth);
23634                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
23635
23636                 if (sizeIconEl) {
23637                     btnIconEl.setWidth(metrics.width + btnFrameWidth);
23638                 }
23639             } else {
23640                 // Remove any previous fixed widths
23641                 ownerEl.setWidth(null);
23642                 btnEl.setWidth(null);
23643                 btnInnerEl.setWidth(null);
23644                 btnIconEl.setWidth(null);
23645             }
23646
23647             // Handle maxWidth/minWidth config
23648             if (minWidth || maxWidth) {
23649                 ownerWidth = ownerEl.getWidth();
23650                 if (minWidth && (ownerWidth < minWidth)) {
23651                     me.setTargetSize(minWidth, height);
23652                 }
23653                 else if (maxWidth && (ownerWidth > maxWidth)) {
23654                     btnInnerEl.clip();
23655                     me.setTargetSize(maxWidth, height);
23656                 }
23657             }
23658         }
23659
23660         this.lastText = owner.text;
23661     },
23662
23663     setTargetSize: function(width, height) {
23664         var me = this,
23665             owner = me.owner,
23666             isNum = Ext.isNumber,
23667             btnInnerEl = owner.btnInnerEl,
23668             btnWidth = (isNum(width) ? width - me.adjWidth : width),
23669             btnHeight = (isNum(height) ? height - me.adjHeight : height),
23670             btnFrameHeight = me.btnFrameHeight,
23671             text = owner.getText(),
23672             textHeight;
23673
23674         me.callParent(arguments);
23675         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
23676         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
23677         if (btnHeight >= 0) {
23678             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
23679         }
23680
23681         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
23682         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
23683         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
23684         // vertically within the button's height. This is more expensive than the basic line-height approach so
23685         // we only do it if the text contains markup.
23686         if (text && this.htmlRE.test(text)) {
23687             btnInnerEl.setStyle('line-height', 'normal');
23688             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
23689             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
23690             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
23691         }
23692     },
23693
23694     getTargetInfo: function() {
23695         var me = this,
23696             owner = me.owner,
23697             ownerEl = owner.el,
23698             frameSize = me.frameSize,
23699             frameBody = owner.frameBody,
23700             btnWrap = owner.btnWrap,
23701             innerEl = owner.btnInnerEl;
23702
23703         if (!('adjWidth' in me)) {
23704             Ext.apply(me, {
23705                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
23706                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
23707                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
23708                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
23709                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
23710                 btnFrameWidth: innerEl.getFrameWidth('lr'),
23711                 btnFrameHeight: innerEl.getFrameWidth('tb'),
23712                 btnFrameTop: innerEl.getFrameWidth('t')
23713             });
23714         }
23715
23716         return me.callParent();
23717     }
23718 });
23719 /**
23720  * @docauthor Robert Dougan <rob@sencha.com>
23721  *
23722  * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
23723  * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
23724  * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
23725  * a user clicks the button, or use {@link #listeners listeners} for other events such as
23726  * {@link #mouseover mouseover}. Example usage:
23727  *
23728  *     @example
23729  *     Ext.create('Ext.Button', {
23730  *         text: 'Click me',
23731  *         renderTo: Ext.getBody(),
23732  *         handler: function() {
23733  *             alert('You clicked the button!')
23734  *         }
23735  *     });
23736  *
23737  * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
23738  * method.  Example usage:
23739  *
23740  *     @example
23741  *     Ext.create('Ext.Button', {
23742  *         text    : 'Dynamic Handler Button',
23743  *         renderTo: Ext.getBody(),
23744  *         handler : function() {
23745  *             // this button will spit out a different number every time you click it.
23746  *             // so firstly we must check if that number is already set:
23747  *             if (this.clickCount) {
23748  *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
23749  *                 this.clickCount++;
23750  *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
23751  *             } else {
23752  *                 // if the clickCount property is not set, we will set it and alert the user
23753  *                 this.clickCount = 1;
23754  *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
23755  *             }
23756  *         }
23757  *     });
23758  *
23759  * A button within a container:
23760  *
23761  *     @example
23762  *     Ext.create('Ext.Container', {
23763  *         renderTo: Ext.getBody(),
23764  *         items   : [
23765  *             {
23766  *                 xtype: 'button',
23767  *                 text : 'My Button'
23768  *             }
23769  *         ]
23770  *     });
23771  *
23772  * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
23773  *
23774  * - `'small'`
23775  * - `'medium'`
23776  * - `'large'`
23777  *
23778  * Example usage:
23779  *
23780  *     @example
23781  *     Ext.create('Ext.Button', {
23782  *         renderTo: document.body,
23783  *         text    : 'Click me',
23784  *         scale   : 'large'
23785  *     });
23786  *
23787  * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
23788  * Example usage:
23789  *
23790  *     @example
23791  *     Ext.create('Ext.Button', {
23792  *         renderTo: Ext.getBody(),
23793  *         text: 'Click Me',
23794  *         enableToggle: true
23795  *     });
23796  *
23797  * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
23798  * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
23799  * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
23800  * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
23801  * on button.  Example usage:
23802  *
23803  *     @example
23804  *     Ext.create('Ext.Button', {
23805  *         text      : 'Menu button',
23806  *         renderTo  : Ext.getBody(),
23807  *         arrowAlign: 'bottom',
23808  *         menu      : [
23809  *             {text: 'Item 1'},
23810  *             {text: 'Item 2'},
23811  *             {text: 'Item 3'},
23812  *             {text: 'Item 4'}
23813  *         ]
23814  *     });
23815  *
23816  * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
23817  * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
23818  *
23819  * - `click`
23820  * - `toggle`
23821  * - `mouseover`
23822  * - `mouseout`
23823  * - `mouseshow`
23824  * - `menuhide`
23825  * - `menutriggerover`
23826  * - `menutriggerout`
23827  *
23828  * Example usage:
23829  *
23830  *     @example
23831  *     Ext.create('Ext.Button', {
23832  *         text     : 'Button',
23833  *         renderTo : Ext.getBody(),
23834  *         listeners: {
23835  *             click: function() {
23836  *                 // this == the button, as we are in the local scope
23837  *                 this.setText('I was clicked!');
23838  *             },
23839  *             mouseover: function() {
23840  *                 // set a new config which says we moused over, if not already set
23841  *                 if (!this.mousedOver) {
23842  *                     this.mousedOver = true;
23843  *                     alert('You moused over a button!\n\nI wont do this again.');
23844  *                 }
23845  *             }
23846  *         }
23847  *     });
23848  */
23849 Ext.define('Ext.button.Button', {
23850
23851     /* Begin Definitions */
23852     alias: 'widget.button',
23853     extend: 'Ext.Component',
23854
23855     requires: [
23856         'Ext.menu.Manager',
23857         'Ext.util.ClickRepeater',
23858         'Ext.layout.component.Button',
23859         'Ext.util.TextMetrics',
23860         'Ext.util.KeyMap'
23861     ],
23862
23863     alternateClassName: 'Ext.Button',
23864     /* End Definitions */
23865
23866     isButton: true,
23867     componentLayout: 'button',
23868
23869     /**
23870      * @property {Boolean} hidden
23871      * True if this button is hidden. Read-only.
23872      */
23873     hidden: false,
23874
23875     /**
23876      * @property {Boolean} disabled
23877      * True if this button is disabled. Read-only.
23878      */
23879     disabled: false,
23880
23881     /**
23882      * @property {Boolean} pressed
23883      * True if this button is pressed (only if enableToggle = true). Read-only.
23884      */
23885     pressed: false,
23886
23887     /**
23888      * @cfg {String} text
23889      * The button text to be used as innerHTML (html tags are accepted).
23890      */
23891
23892     /**
23893      * @cfg {String} icon
23894      * The path to an image to display in the button (the image will be set as the background-image CSS property of the
23895      * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
23896      */
23897
23898     /**
23899      * @cfg {Function} handler
23900      * A function called when the button is clicked (can be used instead of click event).
23901      * @cfg {Ext.button.Button} handler.button This button.
23902      * @cfg {Ext.EventObject} handler.e The click event.
23903      */
23904
23905     /**
23906      * @cfg {Number} minWidth
23907      * The minimum width for this button (used to give a set of buttons a common width).
23908      * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
23909      */
23910
23911     /**
23912      * @cfg {String/Object} tooltip
23913      * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
23914      * QuickTips config object.
23915      */
23916
23917     /**
23918      * @cfg {Boolean} [hidden=false]
23919      * True to start hidden.
23920      */
23921
23922     /**
23923      * @cfg {Boolean} [disabled=true]
23924      * True to start disabled.
23925      */
23926
23927     /**
23928      * @cfg {Boolean} [pressed=false]
23929      * True to start pressed (only if enableToggle = true)
23930      */
23931
23932     /**
23933      * @cfg {String} toggleGroup
23934      * The group this toggle button is a member of (only 1 per group can be pressed)
23935      */
23936
23937     /**
23938      * @cfg {Boolean/Object} [repeat=false]
23939      * True to repeat fire the click event while the mouse is down. This can also be a
23940      * {@link Ext.util.ClickRepeater ClickRepeater} config object.
23941      */
23942
23943     /**
23944      * @cfg {Number} tabIndex
23945      * Set a DOM tabIndex for this button.
23946      */
23947
23948     /**
23949      * @cfg {Boolean} [allowDepress=true]
23950      * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
23951      */
23952
23953     /**
23954      * @cfg {Boolean} [enableToggle=false]
23955      * True to enable pressed/not pressed toggling.
23956      */
23957     enableToggle: false,
23958
23959     /**
23960      * @cfg {Function} toggleHandler
23961      * Function called when a Button with {@link #enableToggle} set to true is clicked.
23962      * @cfg {Ext.button.Button} toggleHandler.button This button.
23963      * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
23964      */
23965
23966     /**
23967      * @cfg {Ext.menu.Menu/String/Object} menu
23968      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
23969      */
23970
23971     /**
23972      * @cfg {String} menuAlign
23973      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
23974      */
23975     menuAlign: 'tl-bl?',
23976
23977     /**
23978      * @cfg {String} textAlign
23979      * The text alignment for this button (center, left, right).
23980      */
23981     textAlign: 'center',
23982
23983     /**
23984      * @cfg {String} overflowText
23985      * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
23986      * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
23987      */
23988
23989     /**
23990      * @cfg {String} iconCls
23991      * A css class which sets a background image to be used as the icon for this button.
23992      */
23993
23994     /**
23995      * @cfg {String} type
23996      * The type of `<input>` to create: submit, reset or button.
23997      */
23998     type: 'button',
23999
24000     /**
24001      * @cfg {String} clickEvent
24002      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
24003      */
24004     clickEvent: 'click',
24005
24006     /**
24007      * @cfg {Boolean} preventDefault
24008      * True to prevent the default action when the {@link #clickEvent} is processed.
24009      */
24010     preventDefault: true,
24011
24012     /**
24013      * @cfg {Boolean} handleMouseEvents
24014      * False to disable visual cues on mouseover, mouseout and mousedown.
24015      */
24016     handleMouseEvents: true,
24017
24018     /**
24019      * @cfg {String} tooltipType
24020      * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
24021      */
24022     tooltipType: 'qtip',
24023
24024     /**
24025      * @cfg {String} [baseCls='x-btn']
24026      * The base CSS class to add to all buttons.
24027      */
24028     baseCls: Ext.baseCSSPrefix + 'btn',
24029
24030     /**
24031      * @cfg {String} pressedCls
24032      * The CSS class to add to a button when it is in the pressed state.
24033      */
24034     pressedCls: 'pressed',
24035
24036     /**
24037      * @cfg {String} overCls
24038      * The CSS class to add to a button when it is in the over (hovered) state.
24039      */
24040     overCls: 'over',
24041
24042     /**
24043      * @cfg {String} focusCls
24044      * The CSS class to add to a button when it is in the focussed state.
24045      */
24046     focusCls: 'focus',
24047
24048     /**
24049      * @cfg {String} menuActiveCls
24050      * The CSS class to add to a button when it's menu is active.
24051      */
24052     menuActiveCls: 'menu-active',
24053
24054     /**
24055      * @cfg {String} href
24056      * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
24057      *
24058      *     handler: function() { window.location = "http://www.sencha.com" }
24059      */
24060
24061     /**
24062      * @cfg {Object} baseParams
24063      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
24064      */
24065
24066     /**
24067      * @cfg {Object} params
24068      * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
24069      * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
24070      */
24071
24072     ariaRole: 'button',
24073
24074     // inherited
24075     renderTpl:
24076         '<em id="{id}-btnWrap" class="{splitCls}">' +
24077             '<tpl if="href">' +
24078                 '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
24079                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
24080                         '{text}' +
24081                     '</span>' +
24082                         '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
24083                 '</a>' +
24084             '</tpl>' +
24085             '<tpl if="!href">' +
24086                 '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
24087                     // the autocomplete="off" is required to prevent Firefox from remembering
24088                     // the button's disabled state between page reloads.
24089                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
24090                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
24091                         '{text}' +
24092                     '</span>' +
24093                     '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
24094                 '</button>' +
24095             '</tpl>' +
24096         '</em>' ,
24097
24098     /**
24099      * @cfg {String} scale
24100      * The size of the Button. Three values are allowed:
24101      *
24102      * - 'small' - Results in the button element being 16px high.
24103      * - 'medium' - Results in the button element being 24px high.
24104      * - 'large' - Results in the button element being 32px high.
24105      */
24106     scale: 'small',
24107
24108     /**
24109      * @private
24110      * An array of allowed scales.
24111      */
24112     allowedScales: ['small', 'medium', 'large'],
24113
24114     /**
24115      * @cfg {Object} scope
24116      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
24117      * Defaults to this Button.
24118      */
24119
24120     /**
24121      * @cfg {String} iconAlign
24122      * The side of the Button box to render the icon. Four values are allowed:
24123      *
24124      * - 'top'
24125      * - 'right'
24126      * - 'bottom'
24127      * - 'left'
24128      */
24129     iconAlign: 'left',
24130
24131     /**
24132      * @cfg {String} arrowAlign
24133      * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
24134      * values are allowed:
24135      *
24136      * - 'right'
24137      * - 'bottom'
24138      */
24139     arrowAlign: 'right',
24140
24141     /**
24142      * @cfg {String} arrowCls
24143      * The className used for the inner arrow element if the button has a menu.
24144      */
24145     arrowCls: 'arrow',
24146
24147     /**
24148      * @property {Ext.Template} template
24149      * A {@link Ext.Template Template} used to create the Button's DOM structure.
24150      *
24151      * Instances, or subclasses which need a different DOM structure may provide a different template layout in
24152      * conjunction with an implementation of {@link #getTemplateArgs}.
24153      */
24154
24155     /**
24156      * @cfg {String} cls
24157      * A CSS class string to apply to the button's main element.
24158      */
24159
24160     /**
24161      * @property {Ext.menu.Menu} menu
24162      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
24163      * option.
24164      */
24165
24166     /**
24167      * @cfg {Boolean} autoWidth
24168      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
24169      * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
24170      * from doing this automatic sizing.
24171      */
24172
24173     maskOnDisable: false,
24174
24175     // inherit docs
24176     initComponent: function() {
24177         var me = this;
24178         me.callParent(arguments);
24179
24180         me.addEvents(
24181             /**
24182              * @event click
24183              * Fires when this button is clicked
24184              * @param {Ext.button.Button} this
24185              * @param {Event} e The click event
24186              */
24187             'click',
24188
24189             /**
24190              * @event toggle
24191              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
24192              * @param {Ext.button.Button} this
24193              * @param {Boolean} pressed
24194              */
24195             'toggle',
24196
24197             /**
24198              * @event mouseover
24199              * Fires when the mouse hovers over the button
24200              * @param {Ext.button.Button} this
24201              * @param {Event} e The event object
24202              */
24203             'mouseover',
24204
24205             /**
24206              * @event mouseout
24207              * Fires when the mouse exits the button
24208              * @param {Ext.button.Button} this
24209              * @param {Event} e The event object
24210              */
24211             'mouseout',
24212
24213             /**
24214              * @event menushow
24215              * If this button has a menu, this event fires when it is shown
24216              * @param {Ext.button.Button} this
24217              * @param {Ext.menu.Menu} menu
24218              */
24219             'menushow',
24220
24221             /**
24222              * @event menuhide
24223              * If this button has a menu, this event fires when it is hidden
24224              * @param {Ext.button.Button} this
24225              * @param {Ext.menu.Menu} menu
24226              */
24227             'menuhide',
24228
24229             /**
24230              * @event menutriggerover
24231              * If this button has a menu, this event fires when the mouse enters the menu triggering element
24232              * @param {Ext.button.Button} this
24233              * @param {Ext.menu.Menu} menu
24234              * @param {Event} e
24235              */
24236             'menutriggerover',
24237
24238             /**
24239              * @event menutriggerout
24240              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
24241              * @param {Ext.button.Button} this
24242              * @param {Ext.menu.Menu} menu
24243              * @param {Event} e
24244              */
24245             'menutriggerout'
24246         );
24247
24248         if (me.menu) {
24249             // Flag that we'll have a splitCls
24250             me.split = true;
24251
24252             // retrieve menu by id or instantiate instance if needed
24253             me.menu = Ext.menu.Manager.get(me.menu);
24254             me.menu.ownerCt = me;
24255         }
24256
24257         // Accept url as a synonym for href
24258         if (me.url) {
24259             me.href = me.url;
24260         }
24261
24262         // preventDefault defaults to false for links
24263         if (me.href && !me.hasOwnProperty('preventDefault')) {
24264             me.preventDefault = false;
24265         }
24266
24267         if (Ext.isString(me.toggleGroup)) {
24268             me.enableToggle = true;
24269         }
24270
24271     },
24272
24273     // private
24274     initAria: function() {
24275         this.callParent();
24276         var actionEl = this.getActionEl();
24277         if (this.menu) {
24278             actionEl.dom.setAttribute('aria-haspopup', true);
24279         }
24280     },
24281
24282     // inherit docs
24283     getActionEl: function() {
24284         return this.btnEl;
24285     },
24286
24287     // inherit docs
24288     getFocusEl: function() {
24289         return this.btnEl;
24290     },
24291
24292     // private
24293     setButtonCls: function() {
24294         var me = this,
24295             cls = [],
24296             btnIconEl = me.btnIconEl,
24297             hide = 'x-hide-display';
24298
24299         if (me.useSetClass) {
24300             if (!Ext.isEmpty(me.oldCls)) {
24301                 me.removeClsWithUI(me.oldCls);
24302                 me.removeClsWithUI(me.pressedCls);
24303             }
24304
24305             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
24306             if (me.iconCls || me.icon) {
24307                 if (me.text) {
24308                     cls.push('icon-text-' + me.iconAlign);
24309                 } else {
24310                     cls.push('icon');
24311                 }
24312                 if (btnIconEl) {
24313                     btnIconEl.removeCls(hide);
24314                 }
24315             } else {
24316                 if (me.text) {
24317                     cls.push('noicon');
24318                 }
24319                 if (btnIconEl) {
24320                     btnIconEl.addCls(hide);
24321                 }
24322             }
24323
24324             me.oldCls = cls;
24325             me.addClsWithUI(cls);
24326             me.addClsWithUI(me.pressed ? me.pressedCls : null);
24327         }
24328     },
24329
24330     // private
24331     onRender: function(ct, position) {
24332         // classNames for the button
24333         var me = this,
24334             repeater, btn;
24335
24336         // Apply the renderData to the template args
24337         Ext.applyIf(me.renderData, me.getTemplateArgs());
24338
24339         me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
24340
24341         if (me.scale) {
24342             me.ui = me.ui + '-' + me.scale;
24343         }
24344
24345         // Render internal structure
24346         me.callParent(arguments);
24347
24348         // If it is a split button + has a toolip for the arrow
24349         if (me.split && me.arrowTooltip) {
24350             me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
24351         }
24352
24353         // Add listeners to the focus and blur events on the element
24354         me.mon(me.btnEl, {
24355             scope: me,
24356             focus: me.onFocus,
24357             blur : me.onBlur
24358         });
24359
24360         // Set btn as a local variable for easy access
24361         btn = me.el;
24362
24363         if (me.icon) {
24364             me.setIcon(me.icon);
24365         }
24366
24367         if (me.iconCls) {
24368             me.setIconCls(me.iconCls);
24369         }
24370
24371         if (me.tooltip) {
24372             me.setTooltip(me.tooltip, true);
24373         }
24374
24375         if (me.textAlign) {
24376             me.setTextAlign(me.textAlign);
24377         }
24378
24379         // Add the mouse events to the button
24380         if (me.handleMouseEvents) {
24381             me.mon(btn, {
24382                 scope: me,
24383                 mouseover: me.onMouseOver,
24384                 mouseout: me.onMouseOut,
24385                 mousedown: me.onMouseDown
24386             });
24387
24388             if (me.split) {
24389                 me.mon(btn, {
24390                     mousemove: me.onMouseMove,
24391                     scope: me
24392                 });
24393             }
24394         }
24395
24396         // Check if the button has a menu
24397         if (me.menu) {
24398             me.mon(me.menu, {
24399                 scope: me,
24400                 show: me.onMenuShow,
24401                 hide: me.onMenuHide
24402             });
24403
24404             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
24405                 key: Ext.EventObject.DOWN,
24406                 handler: me.onDownKey,
24407                 scope: me
24408             });
24409         }
24410
24411         // Check if it is a repeat button
24412         if (me.repeat) {
24413             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
24414             me.mon(repeater, 'click', me.onRepeatClick, me);
24415         } else {
24416             me.mon(btn, me.clickEvent, me.onClick, me);
24417         }
24418
24419         // Register the button in the toggle manager
24420         Ext.ButtonToggleManager.register(me);
24421     },
24422
24423     /**
24424      * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
24425      * create this Button's DOM structure.
24426      *
24427      * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
24428      * their own implementation of this method.
24429      *
24430      * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
24431      * {@link #template} returns an Object containing the following properties:
24432      * @return {String} return.type The `<button>`'s {@link #type}
24433      * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
24434      * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
24435      * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
24436      * button's scale and icon alignment.
24437      * @return {String} return.text The {@link #text} to display ion the Button.
24438      * @return {Number} return.tabIndex The tab index within the input flow.
24439      */
24440     getTemplateArgs: function() {
24441         var me = this,
24442             persistentPadding = me.getPersistentBtnPadding(),
24443             innerSpanStyle = '';
24444
24445         // Create negative margin offsets to counteract persistent button padding if needed
24446         if (Math.max.apply(Math, persistentPadding) > 0) {
24447             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
24448                 return -pad + 'px';
24449             }).join(' ');
24450         }
24451
24452         return {
24453             href     : me.getHref(),
24454             target   : me.target || '_blank',
24455             type     : me.type,
24456             splitCls : me.getSplitCls(),
24457             cls      : me.cls,
24458             iconCls  : me.iconCls || '',
24459             text     : me.text || '&#160;',
24460             tabIndex : me.tabIndex,
24461             innerSpanStyle: innerSpanStyle
24462         };
24463     },
24464
24465     /**
24466      * @private
24467      * If there is a configured href for this Button, returns the href with parameters appended.
24468      * @returns The href string with parameters appended.
24469      */
24470     getHref: function() {
24471         var me = this,
24472             params = Ext.apply({}, me.baseParams);
24473
24474         // write baseParams first, then write any params
24475         params = Ext.apply(params, me.params);
24476         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
24477     },
24478
24479     /**
24480      * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
24481      *
24482      * **Only valid if the Button was originally configured with a {@link #href}**
24483      *
24484      * @param {Object} params Parameters to use in the href URL.
24485      */
24486     setParams: function(params) {
24487         this.params = params;
24488         this.btnEl.dom.href = this.getHref();
24489     },
24490
24491     getSplitCls: function() {
24492         var me = this;
24493         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
24494     },
24495
24496     // private
24497     afterRender: function() {
24498         var me = this;
24499         me.useSetClass = true;
24500         me.setButtonCls();
24501         me.doc = Ext.getDoc();
24502         this.callParent(arguments);
24503     },
24504
24505     /**
24506      * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
24507      * value of the {@link #iconCls} config internally.
24508      * @param {String} cls The CSS class providing the icon image
24509      * @return {Ext.button.Button} this
24510      */
24511     setIconCls: function(cls) {
24512         var me = this,
24513             btnIconEl = me.btnIconEl,
24514             oldCls = me.iconCls;
24515             
24516         me.iconCls = cls;
24517         if (btnIconEl) {
24518             // Remove the previous iconCls from the button
24519             btnIconEl.removeCls(oldCls);
24520             btnIconEl.addCls(cls || '');
24521             me.setButtonCls();
24522         }
24523         return me;
24524     },
24525
24526     /**
24527      * Sets the tooltip for this Button.
24528      *
24529      * @param {String/Object} tooltip This may be:
24530      *
24531      *   - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
24532      *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
24533      *
24534      * @return {Ext.button.Button} this
24535      */
24536     setTooltip: function(tooltip, initial) {
24537         var me = this;
24538
24539         if (me.rendered) {
24540             if (!initial) {
24541                 me.clearTip();
24542             }
24543             if (Ext.isObject(tooltip)) {
24544                 Ext.tip.QuickTipManager.register(Ext.apply({
24545                     target: me.btnEl.id
24546                 },
24547                 tooltip));
24548                 me.tooltip = tooltip;
24549             } else {
24550                 me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
24551             }
24552         } else {
24553             me.tooltip = tooltip;
24554         }
24555         return me;
24556     },
24557
24558     /**
24559      * Sets the text alignment for this button.
24560      * @param {String} align The new alignment of the button text. See {@link #textAlign}.
24561      */
24562     setTextAlign: function(align) {
24563         var me = this,
24564             btnEl = me.btnEl;
24565
24566         if (btnEl) {
24567             btnEl.removeCls(me.baseCls + '-' + me.textAlign);
24568             btnEl.addCls(me.baseCls + '-' + align);
24569         }
24570         me.textAlign = align;
24571         return me;
24572     },
24573
24574     getTipAttr: function(){
24575         return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
24576     },
24577
24578     // private
24579     getRefItems: function(deep){
24580         var menu = this.menu,
24581             items;
24582         
24583         if (menu) {
24584             items = menu.getRefItems(deep);
24585             items.unshift(menu);
24586         }
24587         return items || [];
24588     },
24589
24590     // private
24591     clearTip: function() {
24592         if (Ext.isObject(this.tooltip)) {
24593             Ext.tip.QuickTipManager.unregister(this.btnEl);
24594         }
24595     },
24596
24597     // private
24598     beforeDestroy: function() {
24599         var me = this;
24600         if (me.rendered) {
24601             me.clearTip();
24602         }
24603         if (me.menu && me.destroyMenu !== false) {
24604             Ext.destroy(me.menu);
24605         }
24606         Ext.destroy(me.btnInnerEl, me.repeater);
24607         me.callParent();
24608     },
24609
24610     // private
24611     onDestroy: function() {
24612         var me = this;
24613         if (me.rendered) {
24614             me.doc.un('mouseover', me.monitorMouseOver, me);
24615             me.doc.un('mouseup', me.onMouseUp, me);
24616             delete me.doc;
24617             Ext.ButtonToggleManager.unregister(me);
24618
24619             Ext.destroy(me.keyMap);
24620             delete me.keyMap;
24621         }
24622         me.callParent();
24623     },
24624
24625     /**
24626      * Assigns this Button's click handler
24627      * @param {Function} handler The function to call when the button is clicked
24628      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
24629      * Defaults to this Button.
24630      * @return {Ext.button.Button} this
24631      */
24632     setHandler: function(handler, scope) {
24633         this.handler = handler;
24634         this.scope = scope;
24635         return this;
24636     },
24637
24638     /**
24639      * Sets this Button's text
24640      * @param {String} text The button text
24641      * @return {Ext.button.Button} this
24642      */
24643     setText: function(text) {
24644         var me = this;
24645         me.text = text;
24646         if (me.el) {
24647             me.btnInnerEl.update(text || '&#160;');
24648             me.setButtonCls();
24649         }
24650         me.doComponentLayout();
24651         return me;
24652     },
24653
24654     /**
24655      * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
24656      * config internally.
24657      * @param {String} icon The path to an image to display in the button
24658      * @return {Ext.button.Button} this
24659      */
24660     setIcon: function(icon) {
24661         var me = this,
24662             iconEl = me.btnIconEl;
24663             
24664         me.icon = icon;
24665         if (iconEl) {
24666             iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
24667             me.setButtonCls();
24668         }
24669         return me;
24670     },
24671
24672     /**
24673      * Gets the text for this Button
24674      * @return {String} The button text
24675      */
24676     getText: function() {
24677         return this.text;
24678     },
24679
24680     /**
24681      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
24682      * @param {Boolean} [state] Force a particular state
24683      * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
24684      * @return {Ext.button.Button} this
24685      */
24686     toggle: function(state, suppressEvent) {
24687         var me = this;
24688         state = state === undefined ? !me.pressed : !!state;
24689         if (state !== me.pressed) {
24690             if (me.rendered) {
24691                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
24692             }
24693             me.btnEl.dom.setAttribute('aria-pressed', state);
24694             me.pressed = state;
24695             if (!suppressEvent) {
24696                 me.fireEvent('toggle', me, state);
24697                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
24698             }
24699         }
24700         return me;
24701     },
24702     
24703     maybeShowMenu: function(){
24704         var me = this;
24705         if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
24706             me.showMenu();
24707         }
24708     },
24709
24710     /**
24711      * Shows this button's menu (if it has one)
24712      */
24713     showMenu: function() {
24714         var me = this;
24715         if (me.rendered && me.menu) {
24716             if (me.tooltip && me.getTipAttr() != 'title') {
24717                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
24718             }
24719             if (me.menu.isVisible()) {
24720                 me.menu.hide();
24721             }
24722
24723             me.menu.showBy(me.el, me.menuAlign);
24724         }
24725         return me;
24726     },
24727
24728     /**
24729      * Hides this button's menu (if it has one)
24730      */
24731     hideMenu: function() {
24732         if (this.hasVisibleMenu()) {
24733             this.menu.hide();
24734         }
24735         return this;
24736     },
24737
24738     /**
24739      * Returns true if the button has a menu and it is visible
24740      * @return {Boolean}
24741      */
24742     hasVisibleMenu: function() {
24743         var menu = this.menu;
24744         return menu && menu.rendered && menu.isVisible();
24745     },
24746
24747     // private
24748     onRepeatClick: function(repeat, e) {
24749         this.onClick(e);
24750     },
24751
24752     // private
24753     onClick: function(e) {
24754         var me = this;
24755         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
24756             e.preventDefault();
24757         }
24758         if (e.button !== 0) {
24759             return;
24760         }
24761         if (!me.disabled) {
24762             me.doToggle();
24763             me.maybeShowMenu();
24764             me.fireHandler(e);
24765         }
24766     },
24767     
24768     fireHandler: function(e){
24769         var me = this,
24770             handler = me.handler;
24771             
24772         me.fireEvent('click', me, e);
24773         if (handler) {
24774             handler.call(me.scope || me, me, e);
24775         }
24776         me.onBlur();
24777     },
24778     
24779     doToggle: function(){
24780         var me = this;
24781         if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
24782             me.toggle();
24783         }
24784     },
24785
24786     /**
24787      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
24788      * The targets are interrogated to see what is being entered from where.
24789      * @param e
24790      */
24791     onMouseOver: function(e) {
24792         var me = this;
24793         if (!me.disabled && !e.within(me.el, true, true)) {
24794             me.onMouseEnter(e);
24795         }
24796     },
24797
24798     /**
24799      * @private
24800      * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
24801      * or the mouse leaves the encapsulating element.
24802      * The targets are interrogated to see what is being exited to where.
24803      * @param e
24804      */
24805     onMouseOut: function(e) {
24806         var me = this;
24807         if (!e.within(me.el, true, true)) {
24808             if (me.overMenuTrigger) {
24809                 me.onMenuTriggerOut(e);
24810             }
24811             me.onMouseLeave(e);
24812         }
24813     },
24814
24815     /**
24816      * @private
24817      * mousemove handler called when the mouse moves anywhere within the encapsulating element.
24818      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
24819      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
24820      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
24821      * events when needed. In the future we should consider making the trigger a separate element that
24822      * is absolutely positioned and sized over the trigger area.
24823      */
24824     onMouseMove: function(e) {
24825         var me = this,
24826             el = me.el,
24827             over = me.overMenuTrigger,
24828             overlap, btnSize;
24829
24830         if (me.split) {
24831             if (me.arrowAlign === 'right') {
24832                 overlap = e.getX() - el.getX();
24833                 btnSize = el.getWidth();
24834             } else {
24835                 overlap = e.getY() - el.getY();
24836                 btnSize = el.getHeight();
24837             }
24838
24839             if (overlap > (btnSize - me.getTriggerSize())) {
24840                 if (!over) {
24841                     me.onMenuTriggerOver(e);
24842                 }
24843             } else {
24844                 if (over) {
24845                     me.onMenuTriggerOut(e);
24846                 }
24847             }
24848         }
24849     },
24850
24851     /**
24852      * @private
24853      * Measures the size of the trigger area for menu and split buttons. Will be a width for
24854      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
24855      */
24856     getTriggerSize: function() {
24857         var me = this,
24858             size = me.triggerSize,
24859             side, sideFirstLetter, undef;
24860
24861         if (size === undef) {
24862             side = me.arrowAlign;
24863             sideFirstLetter = side.charAt(0);
24864             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
24865         }
24866         return size;
24867     },
24868
24869     /**
24870      * @private
24871      * virtual mouseenter handler called when it is detected that the mouseout event
24872      * signified the mouse entering the encapsulating element.
24873      * @param e
24874      */
24875     onMouseEnter: function(e) {
24876         var me = this;
24877         me.addClsWithUI(me.overCls);
24878         me.fireEvent('mouseover', me, e);
24879     },
24880
24881     /**
24882      * @private
24883      * virtual mouseleave handler called when it is detected that the mouseover event
24884      * signified the mouse entering the encapsulating element.
24885      * @param e
24886      */
24887     onMouseLeave: function(e) {
24888         var me = this;
24889         me.removeClsWithUI(me.overCls);
24890         me.fireEvent('mouseout', me, e);
24891     },
24892
24893     /**
24894      * @private
24895      * virtual mouseenter handler called when it is detected that the mouseover event
24896      * signified the mouse entering the arrow area of the button - the <em>.
24897      * @param e
24898      */
24899     onMenuTriggerOver: function(e) {
24900         var me = this;
24901         me.overMenuTrigger = true;
24902         me.fireEvent('menutriggerover', me, me.menu, e);
24903     },
24904
24905     /**
24906      * @private
24907      * virtual mouseleave handler called when it is detected that the mouseout event
24908      * signified the mouse leaving the arrow area of the button - the <em>.
24909      * @param e
24910      */
24911     onMenuTriggerOut: function(e) {
24912         var me = this;
24913         delete me.overMenuTrigger;
24914         me.fireEvent('menutriggerout', me, me.menu, e);
24915     },
24916
24917     // inherit docs
24918     enable : function(silent) {
24919         var me = this;
24920
24921         me.callParent(arguments);
24922
24923         me.removeClsWithUI('disabled');
24924
24925         return me;
24926     },
24927
24928     // inherit docs
24929     disable : function(silent) {
24930         var me = this;
24931
24932         me.callParent(arguments);
24933
24934         me.addClsWithUI('disabled');
24935         me.removeClsWithUI(me.overCls);
24936
24937         return me;
24938     },
24939
24940     /**
24941      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
24942      * @param {String} scale The scale to change to.
24943      */
24944     setScale: function(scale) {
24945         var me = this,
24946             ui = me.ui.replace('-' + me.scale, '');
24947
24948         //check if it is an allowed scale
24949         if (!Ext.Array.contains(me.allowedScales, scale)) {
24950             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
24951         }
24952
24953         me.scale = scale;
24954         me.setUI(ui);
24955     },
24956
24957     // inherit docs
24958     setUI: function(ui) {
24959         var me = this;
24960
24961         //we need to append the scale to the UI, if not already done
24962         if (me.scale && !ui.match(me.scale)) {
24963             ui = ui + '-' + me.scale;
24964         }
24965
24966         me.callParent([ui]);
24967
24968         // Set all the state classNames, as they need to include the UI
24969         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
24970     },
24971
24972     // private
24973     onFocus: function(e) {
24974         var me = this;
24975         if (!me.disabled) {
24976             me.addClsWithUI(me.focusCls);
24977         }
24978     },
24979
24980     // private
24981     onBlur: function(e) {
24982         var me = this;
24983         me.removeClsWithUI(me.focusCls);
24984     },
24985
24986     // private
24987     onMouseDown: function(e) {
24988         var me = this;
24989         if (!me.disabled && e.button === 0) {
24990             me.addClsWithUI(me.pressedCls);
24991             me.doc.on('mouseup', me.onMouseUp, me);
24992         }
24993     },
24994     // private
24995     onMouseUp: function(e) {
24996         var me = this;
24997         if (e.button === 0) {
24998             if (!me.pressed) {
24999                 me.removeClsWithUI(me.pressedCls);
25000             }
25001             me.doc.un('mouseup', me.onMouseUp, me);
25002         }
25003     },
25004     // private
25005     onMenuShow: function(e) {
25006         var me = this;
25007         me.ignoreNextClick = 0;
25008         me.addClsWithUI(me.menuActiveCls);
25009         me.fireEvent('menushow', me, me.menu);
25010     },
25011
25012     // private
25013     onMenuHide: function(e) {
25014         var me = this;
25015         me.removeClsWithUI(me.menuActiveCls);
25016         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
25017         me.fireEvent('menuhide', me, me.menu);
25018     },
25019
25020     // private
25021     restoreClick: function() {
25022         this.ignoreNextClick = 0;
25023     },
25024
25025     // private
25026     onDownKey: function() {
25027         var me = this;
25028
25029         if (!me.disabled) {
25030             if (me.menu) {
25031                 me.showMenu();
25032             }
25033         }
25034     },
25035
25036     /**
25037      * @private
25038      * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
25039      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
25040      * @return {Number[]} [top, right, bottom, left]
25041      */
25042     getPersistentBtnPadding: function() {
25043         var cls = Ext.button.Button,
25044             padding = cls.persistentPadding,
25045             btn, leftTop, btnEl, btnInnerEl;
25046
25047         if (!padding) {
25048             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
25049
25050             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
25051                 // Create auto-size button offscreen and measure its insides
25052                 btn = Ext.create('Ext.button.Button', {
25053                     renderTo: Ext.getBody(),
25054                     text: 'test',
25055                     style: 'position:absolute;top:-999px;'
25056                 });
25057                 btnEl = btn.btnEl;
25058                 btnInnerEl = btn.btnInnerEl;
25059                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
25060
25061                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
25062                 padding[0] = leftTop[1];
25063                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
25064                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
25065                 padding[3] = leftTop[0];
25066
25067                 btn.destroy();
25068             }
25069         }
25070
25071         return padding;
25072     }
25073
25074 }, function() {
25075     var groups = {};
25076
25077     function toggleGroup(btn, state) {
25078         var g, i, l;
25079         if (state) {
25080             g = groups[btn.toggleGroup];
25081             for (i = 0, l = g.length; i < l; i++) {
25082                 if (g[i] !== btn) {
25083                     g[i].toggle(false);
25084                 }
25085             }
25086         }
25087     }
25088
25089     /**
25090      * Private utility class used by Button
25091      * @hide
25092      */
25093     Ext.ButtonToggleManager = {
25094         register: function(btn) {
25095             if (!btn.toggleGroup) {
25096                 return;
25097             }
25098             var group = groups[btn.toggleGroup];
25099             if (!group) {
25100                 group = groups[btn.toggleGroup] = [];
25101             }
25102             group.push(btn);
25103             btn.on('toggle', toggleGroup);
25104         },
25105
25106         unregister: function(btn) {
25107             if (!btn.toggleGroup) {
25108                 return;
25109             }
25110             var group = groups[btn.toggleGroup];
25111             if (group) {
25112                 Ext.Array.remove(group, btn);
25113                 btn.un('toggle', toggleGroup);
25114             }
25115         },
25116
25117         /**
25118          * Gets the pressed button in the passed group or null
25119          * @param {String} group
25120          * @return {Ext.button.Button}
25121          */
25122         getPressed: function(group) {
25123             var g = groups[group],
25124                 i = 0,
25125                 len;
25126             if (g) {
25127                 for (len = g.length; i < len; i++) {
25128                     if (g[i].pressed === true) {
25129                         return g[i];
25130                     }
25131                 }
25132             }
25133             return null;
25134         }
25135     };
25136 });
25137
25138 /**
25139  * @class Ext.layout.container.boxOverflow.Menu
25140  * @extends Ext.layout.container.boxOverflow.None
25141  * @private
25142  */
25143 Ext.define('Ext.layout.container.boxOverflow.Menu', {
25144
25145     /* Begin Definitions */
25146
25147     extend: 'Ext.layout.container.boxOverflow.None',
25148     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
25149     alternateClassName: 'Ext.layout.boxOverflow.Menu',
25150     
25151     /* End Definitions */
25152
25153     /**
25154      * @cfg {String} afterCtCls
25155      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
25156      * which must always be present at the rightmost edge of the Container
25157      */
25158
25159     /**
25160      * @property noItemsMenuText
25161      * @type String
25162      * HTML fragment to render into the toolbar overflow menu if there are no items to display
25163      */
25164     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
25165
25166     constructor: function(layout) {
25167         var me = this;
25168
25169         me.callParent(arguments);
25170
25171         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
25172         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
25173
25174         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
25175         /**
25176          * @property menuItems
25177          * @type Array
25178          * Array of all items that are currently hidden and should go into the dropdown menu
25179          */
25180         me.menuItems = [];
25181     },
25182     
25183     onRemove: function(comp){
25184         Ext.Array.remove(this.menuItems, comp);
25185     },
25186
25187     handleOverflow: function(calculations, targetSize) {
25188         var me = this,
25189             layout = me.layout,
25190             methodName = 'get' + layout.parallelPrefixCap,
25191             newSize = {},
25192             posArgs = [null, null];
25193
25194         me.callParent(arguments);
25195         this.createMenu(calculations, targetSize);
25196         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
25197         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
25198
25199         // Center the menuTrigger button.
25200         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
25201         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
25202         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
25203
25204         return { targetSize: newSize };
25205     },
25206
25207     /**
25208      * @private
25209      * Called by the layout, when it determines that there is no overflow.
25210      * Also called as an interceptor to the layout's onLayout method to reshow
25211      * previously hidden overflowing items.
25212      */
25213     clearOverflow: function(calculations, targetSize) {
25214         var me = this,
25215             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
25216             items = me.menuItems,
25217             i = 0,
25218             length = items.length,
25219             item;
25220
25221         me.hideTrigger();
25222         for (; i < length; i++) {
25223             items[i].show();
25224         }
25225         items.length = 0;
25226
25227         return targetSize ? {
25228             targetSize: {
25229                 height: targetSize.height,
25230                 width : newWidth
25231             }
25232         } : null;
25233     },
25234
25235     /**
25236      * @private
25237      */
25238     showTrigger: function() {
25239         this.menuTrigger.show();
25240     },
25241
25242     /**
25243      * @private
25244      */
25245     hideTrigger: function() {
25246         if (this.menuTrigger !== undefined) {
25247             this.menuTrigger.hide();
25248         }
25249     },
25250
25251     /**
25252      * @private
25253      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
25254      */
25255     beforeMenuShow: function(menu) {
25256         var me = this,
25257             items = me.menuItems,
25258             i = 0,
25259             len   = items.length,
25260             item,
25261             prev;
25262
25263         var needsSep = function(group, prev){
25264             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
25265         };
25266
25267         me.clearMenu();
25268         menu.removeAll();
25269
25270         for (; i < len; i++) {
25271             item = items[i];
25272
25273             // Do not show a separator as a first item
25274             if (!i && (item instanceof Ext.toolbar.Separator)) {
25275                 continue;
25276             }
25277             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
25278                 menu.add('-');
25279             }
25280
25281             me.addComponentToMenu(menu, item);
25282             prev = item;
25283         }
25284
25285         // put something so the menu isn't empty if no compatible items found
25286         if (menu.items.length < 1) {
25287             menu.add(me.noItemsMenuText);
25288         }
25289     },
25290     
25291     /**
25292      * @private
25293      * Returns a menu config for a given component. This config is used to create a menu item
25294      * to be added to the expander menu
25295      * @param {Ext.Component} component The component to create the config for
25296      * @param {Boolean} hideOnClick Passed through to the menu item
25297      */
25298     createMenuConfig : function(component, hideOnClick) {
25299         var config = Ext.apply({}, component.initialConfig),
25300             group  = component.toggleGroup;
25301
25302         Ext.copyTo(config, component, [
25303             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
25304         ]);
25305
25306         Ext.apply(config, {
25307             text       : component.overflowText || component.text,
25308             hideOnClick: hideOnClick,
25309             destroyMenu: false
25310         });
25311
25312         if (group || component.enableToggle) {
25313             Ext.apply(config, {
25314                 group  : group,
25315                 checked: component.pressed,
25316                 listeners: {
25317                     checkchange: function(item, checked){
25318                         component.toggle(checked);
25319                     }
25320                 }
25321             });
25322         }
25323
25324         delete config.ownerCt;
25325         delete config.xtype;
25326         delete config.id;
25327         return config;
25328     },
25329
25330     /**
25331      * @private
25332      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
25333      * @param {Ext.menu.Menu} menu The menu to add to
25334      * @param {Ext.Component} component The component to add
25335      */
25336     addComponentToMenu : function(menu, component) {
25337         var me = this;
25338         if (component instanceof Ext.toolbar.Separator) {
25339             menu.add('-');
25340         } else if (component.isComponent) {
25341             if (component.isXType('splitbutton')) {
25342                 menu.add(me.createMenuConfig(component, true));
25343
25344             } else if (component.isXType('button')) {
25345                 menu.add(me.createMenuConfig(component, !component.menu));
25346
25347             } else if (component.isXType('buttongroup')) {
25348                 component.items.each(function(item){
25349                      me.addComponentToMenu(menu, item);
25350                 });
25351             } else {
25352                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
25353             }
25354         }
25355     },
25356
25357     /**
25358      * @private
25359      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
25360      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
25361      */
25362     clearMenu : function() {
25363         var menu = this.moreMenu;
25364         if (menu && menu.items) {
25365             menu.items.each(function(item) {
25366                 if (item.menu) {
25367                     delete item.menu;
25368                 }
25369             });
25370         }
25371     },
25372
25373     /**
25374      * @private
25375      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
25376      * in the layout are too wide to fit in the space available
25377      */
25378     createMenu: function(calculations, targetSize) {
25379         var me = this,
25380             layout = me.layout,
25381             startProp = layout.parallelBefore,
25382             sizeProp = layout.parallelPrefix,
25383             available = targetSize[sizeProp],
25384             boxes = calculations.boxes,
25385             i = 0,
25386             len = boxes.length,
25387             box;
25388
25389         if (!me.menuTrigger) {
25390             me.createInnerElements();
25391
25392             /**
25393              * @private
25394              * @property menu
25395              * @type Ext.menu.Menu
25396              * The expand menu - holds items for every item that cannot be shown
25397              * because the container is currently not large enough.
25398              */
25399             me.menu = Ext.create('Ext.menu.Menu', {
25400                 listeners: {
25401                     scope: me,
25402                     beforeshow: me.beforeMenuShow
25403                 }
25404             });
25405
25406             /**
25407              * @private
25408              * @property menuTrigger
25409              * @type Ext.button.Button
25410              * The expand button which triggers the overflow menu to be shown
25411              */
25412             me.menuTrigger = Ext.create('Ext.button.Button', {
25413                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
25414                 iconCls : me.layout.owner.menuTriggerCls,
25415                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
25416                 menu    : me.menu,
25417                 getSplitCls: function() { return '';},
25418                 renderTo: me.afterCt
25419             });
25420         }
25421         me.showTrigger();
25422         available -= me.afterCt.getWidth();
25423
25424         // Hide all items which are off the end, and store them to allow them to be restored
25425         // before each layout operation.
25426         me.menuItems.length = 0;
25427         for (; i < len; i++) {
25428             box = boxes[i];
25429             if (box[startProp] + box[sizeProp] > available) {
25430                 me.menuItems.push(box.component);
25431                 box.component.hide();
25432             }
25433         }
25434     },
25435
25436     /**
25437      * @private
25438      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
25439      * @param {Ext.container.Container} container The Container attached to this Layout instance
25440      * @param {Ext.Element} target The target Element
25441      */
25442     createInnerElements: function() {
25443         var me = this,
25444             target = me.layout.getRenderTarget();
25445
25446         if (!this.afterCt) {
25447             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
25448             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
25449         }
25450     },
25451
25452     /**
25453      * @private
25454      */
25455     destroy: function() {
25456         Ext.destroy(this.menu, this.menuTrigger);
25457     }
25458 });
25459 /**
25460  * This class represents a rectangular region in X,Y space, and performs geometric
25461  * transformations or tests upon the region.
25462  *
25463  * This class may be used to compare the document regions occupied by elements.
25464  */
25465 Ext.define('Ext.util.Region', {
25466
25467     /* Begin Definitions */
25468
25469     requires: ['Ext.util.Offset'],
25470
25471     statics: {
25472         /**
25473          * @static
25474          * Retrieves an Ext.util.Region for a particular element.
25475          * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
25476          * @returns {Ext.util.Region} region
25477          */
25478         getRegion: function(el) {
25479             return Ext.fly(el).getPageBox(true);
25480         },
25481
25482         /**
25483          * @static
25484          * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
25485          * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
25486          * @return {Ext.util.Region} region The Region constructed based on the passed object
25487          */
25488         from: function(o) {
25489             return new this(o.top, o.right, o.bottom, o.left);
25490         }
25491     },
25492
25493     /* End Definitions */
25494
25495     /**
25496      * Creates a region from the bounding sides.
25497      * @param {Number} top Top The topmost pixel of the Region.
25498      * @param {Number} right Right The rightmost pixel of the Region.
25499      * @param {Number} bottom Bottom The bottom pixel of the Region.
25500      * @param {Number} left Left The leftmost pixel of the Region.
25501      */
25502     constructor : function(t, r, b, l) {
25503         var me = this;
25504         me.y = me.top = me[1] = t;
25505         me.right = r;
25506         me.bottom = b;
25507         me.x = me.left = me[0] = l;
25508     },
25509
25510     /**
25511      * Checks if this region completely contains the region that is passed in.
25512      * @param {Ext.util.Region} region
25513      * @return {Boolean}
25514      */
25515     contains : function(region) {
25516         var me = this;
25517         return (region.x >= me.x &&
25518                 region.right <= me.right &&
25519                 region.y >= me.y &&
25520                 region.bottom <= me.bottom);
25521
25522     },
25523
25524     /**
25525      * Checks if this region intersects the region passed in.
25526      * @param {Ext.util.Region} region
25527      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
25528      */
25529     intersect : function(region) {
25530         var me = this,
25531             t = Math.max(me.y, region.y),
25532             r = Math.min(me.right, region.right),
25533             b = Math.min(me.bottom, region.bottom),
25534             l = Math.max(me.x, region.x);
25535
25536         if (b > t && r > l) {
25537             return new this.self(t, r, b, l);
25538         }
25539         else {
25540             return false;
25541         }
25542     },
25543
25544     /**
25545      * Returns the smallest region that contains the current AND targetRegion.
25546      * @param {Ext.util.Region} region
25547      * @return {Ext.util.Region} a new region
25548      */
25549     union : function(region) {
25550         var me = this,
25551             t = Math.min(me.y, region.y),
25552             r = Math.max(me.right, region.right),
25553             b = Math.max(me.bottom, region.bottom),
25554             l = Math.min(me.x, region.x);
25555
25556         return new this.self(t, r, b, l);
25557     },
25558
25559     /**
25560      * Modifies the current region to be constrained to the targetRegion.
25561      * @param {Ext.util.Region} targetRegion
25562      * @return {Ext.util.Region} this
25563      */
25564     constrainTo : function(r) {
25565         var me = this,
25566             constrain = Ext.Number.constrain;
25567         me.top = me.y = constrain(me.top, r.y, r.bottom);
25568         me.bottom = constrain(me.bottom, r.y, r.bottom);
25569         me.left = me.x = constrain(me.left, r.x, r.right);
25570         me.right = constrain(me.right, r.x, r.right);
25571         return me;
25572     },
25573
25574     /**
25575      * Modifies the current region to be adjusted by offsets.
25576      * @param {Number} top top offset
25577      * @param {Number} right right offset
25578      * @param {Number} bottom bottom offset
25579      * @param {Number} left left offset
25580      * @return {Ext.util.Region} this
25581      */
25582     adjust : function(t, r, b, l) {
25583         var me = this;
25584         me.top = me.y += t;
25585         me.left = me.x += l;
25586         me.right += r;
25587         me.bottom += b;
25588         return me;
25589     },
25590
25591     /**
25592      * Get the offset amount of a point outside the region
25593      * @param {String} [axis]
25594      * @param {Ext.util.Point} [p] the point
25595      * @return {Ext.util.Offset}
25596      */
25597     getOutOfBoundOffset: function(axis, p) {
25598         if (!Ext.isObject(axis)) {
25599             if (axis == 'x') {
25600                 return this.getOutOfBoundOffsetX(p);
25601             } else {
25602                 return this.getOutOfBoundOffsetY(p);
25603             }
25604         } else {
25605             p = axis;
25606             var d = Ext.create('Ext.util.Offset');
25607             d.x = this.getOutOfBoundOffsetX(p.x);
25608             d.y = this.getOutOfBoundOffsetY(p.y);
25609             return d;
25610         }
25611
25612     },
25613
25614     /**
25615      * Get the offset amount on the x-axis
25616      * @param {Number} p the offset
25617      * @return {Number}
25618      */
25619     getOutOfBoundOffsetX: function(p) {
25620         if (p <= this.x) {
25621             return this.x - p;
25622         } else if (p >= this.right) {
25623             return this.right - p;
25624         }
25625
25626         return 0;
25627     },
25628
25629     /**
25630      * Get the offset amount on the y-axis
25631      * @param {Number} p the offset
25632      * @return {Number}
25633      */
25634     getOutOfBoundOffsetY: function(p) {
25635         if (p <= this.y) {
25636             return this.y - p;
25637         } else if (p >= this.bottom) {
25638             return this.bottom - p;
25639         }
25640
25641         return 0;
25642     },
25643
25644     /**
25645      * Check whether the point / offset is out of bound
25646      * @param {String} [axis]
25647      * @param {Ext.util.Point/Number} [p] the point / offset
25648      * @return {Boolean}
25649      */
25650     isOutOfBound: function(axis, p) {
25651         if (!Ext.isObject(axis)) {
25652             if (axis == 'x') {
25653                 return this.isOutOfBoundX(p);
25654             } else {
25655                 return this.isOutOfBoundY(p);
25656             }
25657         } else {
25658             p = axis;
25659             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
25660         }
25661     },
25662
25663     /**
25664      * Check whether the offset is out of bound in the x-axis
25665      * @param {Number} p the offset
25666      * @return {Boolean}
25667      */
25668     isOutOfBoundX: function(p) {
25669         return (p < this.x || p > this.right);
25670     },
25671
25672     /**
25673      * Check whether the offset is out of bound in the y-axis
25674      * @param {Number} p the offset
25675      * @return {Boolean}
25676      */
25677     isOutOfBoundY: function(p) {
25678         return (p < this.y || p > this.bottom);
25679     },
25680
25681     /**
25682      * Restrict a point within the region by a certain factor.
25683      * @param {String} [axis]
25684      * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
25685      * @param {Number} [factor]
25686      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
25687      * @private
25688      */
25689     restrict: function(axis, p, factor) {
25690         if (Ext.isObject(axis)) {
25691             var newP;
25692
25693             factor = p;
25694             p = axis;
25695
25696             if (p.copy) {
25697                 newP = p.copy();
25698             }
25699             else {
25700                 newP = {
25701                     x: p.x,
25702                     y: p.y
25703                 };
25704             }
25705
25706             newP.x = this.restrictX(p.x, factor);
25707             newP.y = this.restrictY(p.y, factor);
25708             return newP;
25709         } else {
25710             if (axis == 'x') {
25711                 return this.restrictX(p, factor);
25712             } else {
25713                 return this.restrictY(p, factor);
25714             }
25715         }
25716     },
25717
25718     /**
25719      * Restrict an offset within the region by a certain factor, on the x-axis
25720      * @param {Number} p
25721      * @param {Number} [factor=1] The factor.
25722      * @return {Number}
25723      * @private
25724      */
25725     restrictX : function(p, factor) {
25726         if (!factor) {
25727             factor = 1;
25728         }
25729
25730         if (p <= this.x) {
25731             p -= (p - this.x) * factor;
25732         }
25733         else if (p >= this.right) {
25734             p -= (p - this.right) * factor;
25735         }
25736         return p;
25737     },
25738
25739     /**
25740      * Restrict an offset within the region by a certain factor, on the y-axis
25741      * @param {Number} p
25742      * @param {Number} [factor] The factor, defaults to 1
25743      * @return {Number}
25744      * @private
25745      */
25746     restrictY : function(p, factor) {
25747         if (!factor) {
25748             factor = 1;
25749         }
25750
25751         if (p <= this.y) {
25752             p -= (p - this.y) * factor;
25753         }
25754         else if (p >= this.bottom) {
25755             p -= (p - this.bottom) * factor;
25756         }
25757         return p;
25758     },
25759
25760     /**
25761      * Get the width / height of this region
25762      * @return {Object} an object with width and height properties
25763      * @private
25764      */
25765     getSize: function() {
25766         return {
25767             width: this.right - this.x,
25768             height: this.bottom - this.y
25769         };
25770     },
25771
25772     /**
25773      * Create a copy of this Region.
25774      * @return {Ext.util.Region}
25775      */
25776     copy: function() {
25777         return new this.self(this.y, this.right, this.bottom, this.x);
25778     },
25779
25780     /**
25781      * Copy the values of another Region to this Region
25782      * @param {Ext.util.Region} p The region to copy from.
25783      * @return {Ext.util.Region} This Region
25784      */
25785     copyFrom: function(p) {
25786         var me = this;
25787         me.top = me.y = me[1] = p.y;
25788         me.right = p.right;
25789         me.bottom = p.bottom;
25790         me.left = me.x = me[0] = p.x;
25791
25792         return this;
25793     },
25794
25795     /*
25796      * Dump this to an eye-friendly string, great for debugging
25797      * @return {String}
25798      */
25799     toString: function() {
25800         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
25801     },
25802
25803     /**
25804      * Translate this region by the given offset amount
25805      * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
25806      * Or the x value is using the two argument form.
25807      * @param {Number} y The y value unless using an Offset object.
25808      * @return {Ext.util.Region} this This Region
25809      */
25810     translateBy: function(x, y) {
25811         if (arguments.length == 1) {
25812             y = x.y;
25813             x = x.x;
25814         }
25815         var me = this;
25816         me.top = me.y += y;
25817         me.right += x;
25818         me.bottom += y;
25819         me.left = me.x += x;
25820
25821         return me;
25822     },
25823
25824     /**
25825      * Round all the properties of this region
25826      * @return {Ext.util.Region} this This Region
25827      */
25828     round: function() {
25829         var me = this;
25830         me.top = me.y = Math.round(me.y);
25831         me.right = Math.round(me.right);
25832         me.bottom = Math.round(me.bottom);
25833         me.left = me.x = Math.round(me.x);
25834
25835         return me;
25836     },
25837
25838     /**
25839      * Check whether this region is equivalent to the given region
25840      * @param {Ext.util.Region} region The region to compare with
25841      * @return {Boolean}
25842      */
25843     equals: function(region) {
25844         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
25845     }
25846 });
25847
25848 /*
25849  * This is a derivative of the similarly named class in the YUI Library.
25850  * The original license:
25851  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
25852  * Code licensed under the BSD License:
25853  * http://developer.yahoo.net/yui/license.txt
25854  */
25855
25856
25857 /**
25858  * @class Ext.dd.DragDropManager
25859  * DragDropManager is a singleton that tracks the element interaction for
25860  * all DragDrop items in the window.  Generally, you will not call
25861  * this class directly, but it does have helper methods that could
25862  * be useful in your DragDrop implementations.
25863  * @singleton
25864  */
25865 Ext.define('Ext.dd.DragDropManager', {
25866     singleton: true,
25867
25868     requires: ['Ext.util.Region'],
25869
25870     uses: ['Ext.tip.QuickTipManager'],
25871
25872     // shorter ClassName, to save bytes and use internally
25873     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
25874
25875     /**
25876      * Two dimensional Array of registered DragDrop objects.  The first
25877      * dimension is the DragDrop item group, the second the DragDrop
25878      * object.
25879      * @property ids
25880      * @type String[]
25881      * @private
25882      */
25883     ids: {},
25884
25885     /**
25886      * Array of element ids defined as drag handles.  Used to determine
25887      * if the element that generated the mousedown event is actually the
25888      * handle and not the html element itself.
25889      * @property handleIds
25890      * @type String[]
25891      * @private
25892      */
25893     handleIds: {},
25894
25895     /**
25896      * the DragDrop object that is currently being dragged
25897      * @property {Ext.dd.DragDrop} dragCurrent
25898      * @private
25899      **/
25900     dragCurrent: null,
25901
25902     /**
25903      * the DragDrop object(s) that are being hovered over
25904      * @property {Ext.dd.DragDrop[]} dragOvers
25905      * @private
25906      */
25907     dragOvers: {},
25908
25909     /**
25910      * the X distance between the cursor and the object being dragged
25911      * @property deltaX
25912      * @type Number
25913      * @private
25914      */
25915     deltaX: 0,
25916
25917     /**
25918      * the Y distance between the cursor and the object being dragged
25919      * @property deltaY
25920      * @type Number
25921      * @private
25922      */
25923     deltaY: 0,
25924
25925     /**
25926      * Flag to determine if we should prevent the default behavior of the
25927      * events we define. By default this is true, but this can be set to
25928      * false if you need the default behavior (not recommended)
25929      * @property preventDefault
25930      * @type Boolean
25931      */
25932     preventDefault: true,
25933
25934     /**
25935      * Flag to determine if we should stop the propagation of the events
25936      * we generate. This is true by default but you may want to set it to
25937      * false if the html element contains other features that require the
25938      * mouse click.
25939      * @property stopPropagation
25940      * @type Boolean
25941      */
25942     stopPropagation: true,
25943
25944     /**
25945      * Internal flag that is set to true when drag and drop has been
25946      * intialized
25947      * @property initialized
25948      * @private
25949      */
25950     initialized: false,
25951
25952     /**
25953      * All drag and drop can be disabled.
25954      * @property locked
25955      * @private
25956      */
25957     locked: false,
25958
25959     /**
25960      * Called the first time an element is registered.
25961      * @method init
25962      * @private
25963      */
25964     init: function() {
25965         this.initialized = true;
25966     },
25967
25968     /**
25969      * In point mode, drag and drop interaction is defined by the
25970      * location of the cursor during the drag/drop
25971      * @property POINT
25972      * @type Number
25973      */
25974     POINT: 0,
25975
25976     /**
25977      * In intersect mode, drag and drop interaction is defined by the
25978      * overlap of two or more drag and drop objects.
25979      * @property INTERSECT
25980      * @type Number
25981      */
25982     INTERSECT: 1,
25983
25984     /**
25985      * The current drag and drop mode.  Default: POINT
25986      * @property mode
25987      * @type Number
25988      */
25989     mode: 0,
25990
25991     /**
25992      * Runs method on all drag and drop objects
25993      * @method _execOnAll
25994      * @private
25995      */
25996     _execOnAll: function(sMethod, args) {
25997         for (var i in this.ids) {
25998             for (var j in this.ids[i]) {
25999                 var oDD = this.ids[i][j];
26000                 if (! this.isTypeOfDD(oDD)) {
26001                     continue;
26002                 }
26003                 oDD[sMethod].apply(oDD, args);
26004             }
26005         }
26006     },
26007
26008     /**
26009      * Drag and drop initialization.  Sets up the global event handlers
26010      * @method _onLoad
26011      * @private
26012      */
26013     _onLoad: function() {
26014
26015         this.init();
26016
26017         var Event = Ext.EventManager;
26018         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
26019         Event.on(document, "mousemove", this.handleMouseMove, this, true);
26020         Event.on(window,   "unload",    this._onUnload, this, true);
26021         Event.on(window,   "resize",    this._onResize, this, true);
26022         // Event.on(window,   "mouseout",    this._test);
26023
26024     },
26025
26026     /**
26027      * Reset constraints on all drag and drop objs
26028      * @method _onResize
26029      * @private
26030      */
26031     _onResize: function(e) {
26032         this._execOnAll("resetConstraints", []);
26033     },
26034
26035     /**
26036      * Lock all drag and drop functionality
26037      * @method lock
26038      */
26039     lock: function() { this.locked = true; },
26040
26041     /**
26042      * Unlock all drag and drop functionality
26043      * @method unlock
26044      */
26045     unlock: function() { this.locked = false; },
26046
26047     /**
26048      * Is drag and drop locked?
26049      * @method isLocked
26050      * @return {Boolean} True if drag and drop is locked, false otherwise.
26051      */
26052     isLocked: function() { return this.locked; },
26053
26054     /**
26055      * Location cache that is set for all drag drop objects when a drag is
26056      * initiated, cleared when the drag is finished.
26057      * @property locationCache
26058      * @private
26059      */
26060     locationCache: {},
26061
26062     /**
26063      * Set useCache to false if you want to force object the lookup of each
26064      * drag and drop linked element constantly during a drag.
26065      * @property useCache
26066      * @type Boolean
26067      */
26068     useCache: true,
26069
26070     /**
26071      * The number of pixels that the mouse needs to move after the
26072      * mousedown before the drag is initiated.  Default=3;
26073      * @property clickPixelThresh
26074      * @type Number
26075      */
26076     clickPixelThresh: 3,
26077
26078     /**
26079      * The number of milliseconds after the mousedown event to initiate the
26080      * drag if we don't get a mouseup event. Default=350
26081      * @property clickTimeThresh
26082      * @type Number
26083      */
26084     clickTimeThresh: 350,
26085
26086     /**
26087      * Flag that indicates that either the drag pixel threshold or the
26088      * mousdown time threshold has been met
26089      * @property dragThreshMet
26090      * @type Boolean
26091      * @private
26092      */
26093     dragThreshMet: false,
26094
26095     /**
26096      * Timeout used for the click time threshold
26097      * @property clickTimeout
26098      * @type Object
26099      * @private
26100      */
26101     clickTimeout: null,
26102
26103     /**
26104      * The X position of the mousedown event stored for later use when a
26105      * drag threshold is met.
26106      * @property startX
26107      * @type Number
26108      * @private
26109      */
26110     startX: 0,
26111
26112     /**
26113      * The Y position of the mousedown event stored for later use when a
26114      * drag threshold is met.
26115      * @property startY
26116      * @type Number
26117      * @private
26118      */
26119     startY: 0,
26120
26121     /**
26122      * Each DragDrop instance must be registered with the DragDropManager.
26123      * This is executed in DragDrop.init()
26124      * @method regDragDrop
26125      * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
26126      * @param {String} sGroup the name of the group this element belongs to
26127      */
26128     regDragDrop: function(oDD, sGroup) {
26129         if (!this.initialized) { this.init(); }
26130
26131         if (!this.ids[sGroup]) {
26132             this.ids[sGroup] = {};
26133         }
26134         this.ids[sGroup][oDD.id] = oDD;
26135     },
26136
26137     /**
26138      * Removes the supplied dd instance from the supplied group. Executed
26139      * by DragDrop.removeFromGroup, so don't call this function directly.
26140      * @method removeDDFromGroup
26141      * @private
26142      */
26143     removeDDFromGroup: function(oDD, sGroup) {
26144         if (!this.ids[sGroup]) {
26145             this.ids[sGroup] = {};
26146         }
26147
26148         var obj = this.ids[sGroup];
26149         if (obj && obj[oDD.id]) {
26150             delete obj[oDD.id];
26151         }
26152     },
26153
26154     /**
26155      * Unregisters a drag and drop item.  This is executed in
26156      * DragDrop.unreg, use that method instead of calling this directly.
26157      * @method _remove
26158      * @private
26159      */
26160     _remove: function(oDD) {
26161         for (var g in oDD.groups) {
26162             if (g && this.ids[g] && this.ids[g][oDD.id]) {
26163                 delete this.ids[g][oDD.id];
26164             }
26165         }
26166         delete this.handleIds[oDD.id];
26167     },
26168
26169     /**
26170      * Each DragDrop handle element must be registered.  This is done
26171      * automatically when executing DragDrop.setHandleElId()
26172      * @method regHandle
26173      * @param {String} sDDId the DragDrop id this element is a handle for
26174      * @param {String} sHandleId the id of the element that is the drag
26175      * handle
26176      */
26177     regHandle: function(sDDId, sHandleId) {
26178         if (!this.handleIds[sDDId]) {
26179             this.handleIds[sDDId] = {};
26180         }
26181         this.handleIds[sDDId][sHandleId] = sHandleId;
26182     },
26183
26184     /**
26185      * Utility function to determine if a given element has been
26186      * registered as a drag drop item.
26187      * @method isDragDrop
26188      * @param {String} id the element id to check
26189      * @return {Boolean} true if this element is a DragDrop item,
26190      * false otherwise
26191      */
26192     isDragDrop: function(id) {
26193         return ( this.getDDById(id) ) ? true : false;
26194     },
26195
26196     /**
26197      * Returns the drag and drop instances that are in all groups the
26198      * passed in instance belongs to.
26199      * @method getRelated
26200      * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
26201      * @param {Boolean} bTargetsOnly if true, only return targetable objs
26202      * @return {Ext.dd.DragDrop[]} the related instances
26203      */
26204     getRelated: function(p_oDD, bTargetsOnly) {
26205         var oDDs = [];
26206         for (var i in p_oDD.groups) {
26207             for (var j in this.ids[i]) {
26208                 var dd = this.ids[i][j];
26209                 if (! this.isTypeOfDD(dd)) {
26210                     continue;
26211                 }
26212                 if (!bTargetsOnly || dd.isTarget) {
26213                     oDDs[oDDs.length] = dd;
26214                 }
26215             }
26216         }
26217
26218         return oDDs;
26219     },
26220
26221     /**
26222      * Returns true if the specified dd target is a legal target for
26223      * the specifice drag obj
26224      * @method isLegalTarget
26225      * @param {Ext.dd.DragDrop} oDD the drag obj
26226      * @param {Ext.dd.DragDrop} oTargetDD the target
26227      * @return {Boolean} true if the target is a legal target for the
26228      * dd obj
26229      */
26230     isLegalTarget: function (oDD, oTargetDD) {
26231         var targets = this.getRelated(oDD, true);
26232         for (var i=0, len=targets.length;i<len;++i) {
26233             if (targets[i].id == oTargetDD.id) {
26234                 return true;
26235             }
26236         }
26237
26238         return false;
26239     },
26240
26241     /**
26242      * My goal is to be able to transparently determine if an object is
26243      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
26244      * returns "object", oDD.constructor.toString() always returns
26245      * "DragDrop" and not the name of the subclass.  So for now it just
26246      * evaluates a well-known variable in DragDrop.
26247      * @method isTypeOfDD
26248      * @param {Object} the object to evaluate
26249      * @return {Boolean} true if typeof oDD = DragDrop
26250      */
26251     isTypeOfDD: function (oDD) {
26252         return (oDD && oDD.__ygDragDrop);
26253     },
26254
26255     /**
26256      * Utility function to determine if a given element has been
26257      * registered as a drag drop handle for the given Drag Drop object.
26258      * @method isHandle
26259      * @param {String} id the element id to check
26260      * @return {Boolean} true if this element is a DragDrop handle, false
26261      * otherwise
26262      */
26263     isHandle: function(sDDId, sHandleId) {
26264         return ( this.handleIds[sDDId] &&
26265                         this.handleIds[sDDId][sHandleId] );
26266     },
26267
26268     /**
26269      * Returns the DragDrop instance for a given id
26270      * @method getDDById
26271      * @param {String} id the id of the DragDrop object
26272      * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
26273      */
26274     getDDById: function(id) {
26275         for (var i in this.ids) {
26276             if (this.ids[i][id]) {
26277                 return this.ids[i][id];
26278             }
26279         }
26280         return null;
26281     },
26282
26283     /**
26284      * Fired after a registered DragDrop object gets the mousedown event.
26285      * Sets up the events required to track the object being dragged
26286      * @method handleMouseDown
26287      * @param {Event} e the event
26288      * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
26289      * @private
26290      */
26291     handleMouseDown: function(e, oDD) {
26292         if(Ext.tip.QuickTipManager){
26293             Ext.tip.QuickTipManager.ddDisable();
26294         }
26295         if(this.dragCurrent){
26296             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
26297             // so clean up first to avoid breaking the next drag
26298             this.handleMouseUp(e);
26299         }
26300
26301         this.currentTarget = e.getTarget();
26302         this.dragCurrent = oDD;
26303
26304         var el = oDD.getEl();
26305
26306         // track start position
26307         this.startX = e.getPageX();
26308         this.startY = e.getPageY();
26309
26310         this.deltaX = this.startX - el.offsetLeft;
26311         this.deltaY = this.startY - el.offsetTop;
26312
26313         this.dragThreshMet = false;
26314
26315         this.clickTimeout = setTimeout(
26316                 function() {
26317                     var DDM = Ext.dd.DragDropManager;
26318                     DDM.startDrag(DDM.startX, DDM.startY);
26319                 },
26320                 this.clickTimeThresh );
26321     },
26322
26323     /**
26324      * Fired when either the drag pixel threshol or the mousedown hold
26325      * time threshold has been met.
26326      * @method startDrag
26327      * @param {Number} x the X position of the original mousedown
26328      * @param {Number} y the Y position of the original mousedown
26329      */
26330     startDrag: function(x, y) {
26331         clearTimeout(this.clickTimeout);
26332         if (this.dragCurrent) {
26333             this.dragCurrent.b4StartDrag(x, y);
26334             this.dragCurrent.startDrag(x, y);
26335         }
26336         this.dragThreshMet = true;
26337     },
26338
26339     /**
26340      * Internal function to handle the mouseup event.  Will be invoked
26341      * from the context of the document.
26342      * @method handleMouseUp
26343      * @param {Event} e the event
26344      * @private
26345      */
26346     handleMouseUp: function(e) {
26347
26348         if(Ext.tip && Ext.tip.QuickTipManager){
26349             Ext.tip.QuickTipManager.ddEnable();
26350         }
26351         if (! this.dragCurrent) {
26352             return;
26353         }
26354
26355         clearTimeout(this.clickTimeout);
26356
26357         if (this.dragThreshMet) {
26358             this.fireEvents(e, true);
26359         } else {
26360         }
26361
26362         this.stopDrag(e);
26363
26364         this.stopEvent(e);
26365     },
26366
26367     /**
26368      * Utility to stop event propagation and event default, if these
26369      * features are turned on.
26370      * @method stopEvent
26371      * @param {Event} e the event as returned by this.getEvent()
26372      */
26373     stopEvent: function(e){
26374         if(this.stopPropagation) {
26375             e.stopPropagation();
26376         }
26377
26378         if (this.preventDefault) {
26379             e.preventDefault();
26380         }
26381     },
26382
26383     /**
26384      * Internal function to clean up event handlers after the drag
26385      * operation is complete
26386      * @method stopDrag
26387      * @param {Event} e the event
26388      * @private
26389      */
26390     stopDrag: function(e) {
26391         // Fire the drag end event for the item that was dragged
26392         if (this.dragCurrent) {
26393             if (this.dragThreshMet) {
26394                 this.dragCurrent.b4EndDrag(e);
26395                 this.dragCurrent.endDrag(e);
26396             }
26397
26398             this.dragCurrent.onMouseUp(e);
26399         }
26400
26401         this.dragCurrent = null;
26402         this.dragOvers = {};
26403     },
26404
26405     /**
26406      * Internal function to handle the mousemove event.  Will be invoked
26407      * from the context of the html element.
26408      *
26409      * @TODO figure out what we can do about mouse events lost when the
26410      * user drags objects beyond the window boundary.  Currently we can
26411      * detect this in internet explorer by verifying that the mouse is
26412      * down during the mousemove event.  Firefox doesn't give us the
26413      * button state on the mousemove event.
26414      * @method handleMouseMove
26415      * @param {Event} e the event
26416      * @private
26417      */
26418     handleMouseMove: function(e) {
26419         if (! this.dragCurrent) {
26420             return true;
26421         }
26422         // var button = e.which || e.button;
26423
26424         // check for IE mouseup outside of page boundary
26425         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
26426             this.stopEvent(e);
26427             return this.handleMouseUp(e);
26428         }
26429
26430         if (!this.dragThreshMet) {
26431             var diffX = Math.abs(this.startX - e.getPageX());
26432             var diffY = Math.abs(this.startY - e.getPageY());
26433             if (diffX > this.clickPixelThresh ||
26434                         diffY > this.clickPixelThresh) {
26435                 this.startDrag(this.startX, this.startY);
26436             }
26437         }
26438
26439         if (this.dragThreshMet) {
26440             this.dragCurrent.b4Drag(e);
26441             this.dragCurrent.onDrag(e);
26442             if(!this.dragCurrent.moveOnly){
26443                 this.fireEvents(e, false);
26444             }
26445         }
26446
26447         this.stopEvent(e);
26448
26449         return true;
26450     },
26451
26452     /**
26453      * Iterates over all of the DragDrop elements to find ones we are
26454      * hovering over or dropping on
26455      * @method fireEvents
26456      * @param {Event} e the event
26457      * @param {Boolean} isDrop is this a drop op or a mouseover op?
26458      * @private
26459      */
26460     fireEvents: function(e, isDrop) {
26461         var dc = this.dragCurrent;
26462
26463         // If the user did the mouse up outside of the window, we could
26464         // get here even though we have ended the drag.
26465         if (!dc || dc.isLocked()) {
26466             return;
26467         }
26468
26469         var pt = e.getPoint();
26470
26471         // cache the previous dragOver array
26472         var oldOvers = [];
26473
26474         var outEvts   = [];
26475         var overEvts  = [];
26476         var dropEvts  = [];
26477         var enterEvts = [];
26478
26479         // Check to see if the object(s) we were hovering over is no longer
26480         // being hovered over so we can fire the onDragOut event
26481         for (var i in this.dragOvers) {
26482
26483             var ddo = this.dragOvers[i];
26484
26485             if (! this.isTypeOfDD(ddo)) {
26486                 continue;
26487             }
26488
26489             if (! this.isOverTarget(pt, ddo, this.mode)) {
26490                 outEvts.push( ddo );
26491             }
26492
26493             oldOvers[i] = true;
26494             delete this.dragOvers[i];
26495         }
26496
26497         for (var sGroup in dc.groups) {
26498
26499             if ("string" != typeof sGroup) {
26500                 continue;
26501             }
26502
26503             for (i in this.ids[sGroup]) {
26504                 var oDD = this.ids[sGroup][i];
26505                 if (! this.isTypeOfDD(oDD)) {
26506                     continue;
26507                 }
26508
26509                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
26510                     if (this.isOverTarget(pt, oDD, this.mode)) {
26511                         // look for drop interactions
26512                         if (isDrop) {
26513                             dropEvts.push( oDD );
26514                         // look for drag enter and drag over interactions
26515                         } else {
26516
26517                             // initial drag over: dragEnter fires
26518                             if (!oldOvers[oDD.id]) {
26519                                 enterEvts.push( oDD );
26520                             // subsequent drag overs: dragOver fires
26521                             } else {
26522                                 overEvts.push( oDD );
26523                             }
26524
26525                             this.dragOvers[oDD.id] = oDD;
26526                         }
26527                     }
26528                 }
26529             }
26530         }
26531
26532         if (this.mode) {
26533             if (outEvts.length) {
26534                 dc.b4DragOut(e, outEvts);
26535                 dc.onDragOut(e, outEvts);
26536             }
26537
26538             if (enterEvts.length) {
26539                 dc.onDragEnter(e, enterEvts);
26540             }
26541
26542             if (overEvts.length) {
26543                 dc.b4DragOver(e, overEvts);
26544                 dc.onDragOver(e, overEvts);
26545             }
26546
26547             if (dropEvts.length) {
26548                 dc.b4DragDrop(e, dropEvts);
26549                 dc.onDragDrop(e, dropEvts);
26550             }
26551
26552         } else {
26553             // fire dragout events
26554             var len = 0;
26555             for (i=0, len=outEvts.length; i<len; ++i) {
26556                 dc.b4DragOut(e, outEvts[i].id);
26557                 dc.onDragOut(e, outEvts[i].id);
26558             }
26559
26560             // fire enter events
26561             for (i=0,len=enterEvts.length; i<len; ++i) {
26562                 // dc.b4DragEnter(e, oDD.id);
26563                 dc.onDragEnter(e, enterEvts[i].id);
26564             }
26565
26566             // fire over events
26567             for (i=0,len=overEvts.length; i<len; ++i) {
26568                 dc.b4DragOver(e, overEvts[i].id);
26569                 dc.onDragOver(e, overEvts[i].id);
26570             }
26571
26572             // fire drop events
26573             for (i=0, len=dropEvts.length; i<len; ++i) {
26574                 dc.b4DragDrop(e, dropEvts[i].id);
26575                 dc.onDragDrop(e, dropEvts[i].id);
26576             }
26577
26578         }
26579
26580         // notify about a drop that did not find a target
26581         if (isDrop && !dropEvts.length) {
26582             dc.onInvalidDrop(e);
26583         }
26584
26585     },
26586
26587     /**
26588      * Helper function for getting the best match from the list of drag
26589      * and drop objects returned by the drag and drop events when we are
26590      * in INTERSECT mode.  It returns either the first object that the
26591      * cursor is over, or the object that has the greatest overlap with
26592      * the dragged element.
26593      * @method getBestMatch
26594      * @param  {Ext.dd.DragDrop[]} dds The array of drag and drop objects
26595      * targeted
26596      * @return {Ext.dd.DragDrop}       The best single match
26597      */
26598     getBestMatch: function(dds) {
26599         var winner = null;
26600         // Return null if the input is not what we expect
26601         //if (!dds || !dds.length || dds.length == 0) {
26602            // winner = null;
26603         // If there is only one item, it wins
26604         //} else if (dds.length == 1) {
26605
26606         var len = dds.length;
26607
26608         if (len == 1) {
26609             winner = dds[0];
26610         } else {
26611             // Loop through the targeted items
26612             for (var i=0; i<len; ++i) {
26613                 var dd = dds[i];
26614                 // If the cursor is over the object, it wins.  If the
26615                 // cursor is over multiple matches, the first one we come
26616                 // to wins.
26617                 if (dd.cursorIsOver) {
26618                     winner = dd;
26619                     break;
26620                 // Otherwise the object with the most overlap wins
26621                 } else {
26622                     if (!winner ||
26623                         winner.overlap.getArea() < dd.overlap.getArea()) {
26624                         winner = dd;
26625                     }
26626                 }
26627             }
26628         }
26629
26630         return winner;
26631     },
26632
26633     /**
26634      * Refreshes the cache of the top-left and bottom-right points of the
26635      * drag and drop objects in the specified group(s).  This is in the
26636      * format that is stored in the drag and drop instance, so typical
26637      * usage is:
26638      * <code>
26639      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
26640      * </code>
26641      * Alternatively:
26642      * <code>
26643      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
26644      * </code>
26645      * @TODO this really should be an indexed array.  Alternatively this
26646      * method could accept both.
26647      * @method refreshCache
26648      * @param {Object} groups an associative array of groups to refresh
26649      */
26650     refreshCache: function(groups) {
26651         for (var sGroup in groups) {
26652             if ("string" != typeof sGroup) {
26653                 continue;
26654             }
26655             for (var i in this.ids[sGroup]) {
26656                 var oDD = this.ids[sGroup][i];
26657
26658                 if (this.isTypeOfDD(oDD)) {
26659                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
26660                     var loc = this.getLocation(oDD);
26661                     if (loc) {
26662                         this.locationCache[oDD.id] = loc;
26663                     } else {
26664                         delete this.locationCache[oDD.id];
26665                         // this will unregister the drag and drop object if
26666                         // the element is not in a usable state
26667                         // oDD.unreg();
26668                     }
26669                 }
26670             }
26671         }
26672     },
26673
26674     /**
26675      * This checks to make sure an element exists and is in the DOM.  The
26676      * main purpose is to handle cases where innerHTML is used to remove
26677      * drag and drop objects from the DOM.  IE provides an 'unspecified
26678      * error' when trying to access the offsetParent of such an element
26679      * @method verifyEl
26680      * @param {HTMLElement} el the element to check
26681      * @return {Boolean} true if the element looks usable
26682      */
26683     verifyEl: function(el) {
26684         if (el) {
26685             var parent;
26686             if(Ext.isIE){
26687                 try{
26688                     parent = el.offsetParent;
26689                 }catch(e){}
26690             }else{
26691                 parent = el.offsetParent;
26692             }
26693             if (parent) {
26694                 return true;
26695             }
26696         }
26697
26698         return false;
26699     },
26700
26701     /**
26702      * Returns a Region object containing the drag and drop element's position
26703      * and size, including the padding configured for it
26704      * @method getLocation
26705      * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
26706      * @return {Ext.util.Region} a Region object representing the total area
26707      * the element occupies, including any padding
26708      * the instance is configured for.
26709      */
26710     getLocation: function(oDD) {
26711         if (! this.isTypeOfDD(oDD)) {
26712             return null;
26713         }
26714
26715         //delegate getLocation method to the
26716         //drag and drop target.
26717         if (oDD.getRegion) {
26718             return oDD.getRegion();
26719         }
26720
26721         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
26722
26723         try {
26724             pos= Ext.Element.getXY(el);
26725         } catch (e) { }
26726
26727         if (!pos) {
26728             return null;
26729         }
26730
26731         x1 = pos[0];
26732         x2 = x1 + el.offsetWidth;
26733         y1 = pos[1];
26734         y2 = y1 + el.offsetHeight;
26735
26736         t = y1 - oDD.padding[0];
26737         r = x2 + oDD.padding[1];
26738         b = y2 + oDD.padding[2];
26739         l = x1 - oDD.padding[3];
26740
26741         return Ext.create('Ext.util.Region', t, r, b, l);
26742     },
26743
26744     /**
26745      * Checks the cursor location to see if it over the target
26746      * @method isOverTarget
26747      * @param {Ext.util.Point} pt The point to evaluate
26748      * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
26749      * @return {Boolean} true if the mouse is over the target
26750      * @private
26751      */
26752     isOverTarget: function(pt, oTarget, intersect) {
26753         // use cache if available
26754         var loc = this.locationCache[oTarget.id];
26755         if (!loc || !this.useCache) {
26756             loc = this.getLocation(oTarget);
26757             this.locationCache[oTarget.id] = loc;
26758
26759         }
26760
26761         if (!loc) {
26762             return false;
26763         }
26764
26765         oTarget.cursorIsOver = loc.contains( pt );
26766
26767         // DragDrop is using this as a sanity check for the initial mousedown
26768         // in this case we are done.  In POINT mode, if the drag obj has no
26769         // contraints, we are also done. Otherwise we need to evaluate the
26770         // location of the target as related to the actual location of the
26771         // dragged element.
26772         var dc = this.dragCurrent;
26773         if (!dc || !dc.getTargetCoord ||
26774                 (!intersect && !dc.constrainX && !dc.constrainY)) {
26775             return oTarget.cursorIsOver;
26776         }
26777
26778         oTarget.overlap = null;
26779
26780         // Get the current location of the drag element, this is the
26781         // location of the mouse event less the delta that represents
26782         // where the original mousedown happened on the element.  We
26783         // need to consider constraints and ticks as well.
26784         var pos = dc.getTargetCoord(pt.x, pt.y);
26785
26786         var el = dc.getDragEl();
26787         var curRegion = Ext.create('Ext.util.Region', pos.y,
26788                                                pos.x + el.offsetWidth,
26789                                                pos.y + el.offsetHeight,
26790                                                pos.x );
26791
26792         var overlap = curRegion.intersect(loc);
26793
26794         if (overlap) {
26795             oTarget.overlap = overlap;
26796             return (intersect) ? true : oTarget.cursorIsOver;
26797         } else {
26798             return false;
26799         }
26800     },
26801
26802     /**
26803      * unload event handler
26804      * @method _onUnload
26805      * @private
26806      */
26807     _onUnload: function(e, me) {
26808         Ext.dd.DragDropManager.unregAll();
26809     },
26810
26811     /**
26812      * Cleans up the drag and drop events and objects.
26813      * @method unregAll
26814      * @private
26815      */
26816     unregAll: function() {
26817
26818         if (this.dragCurrent) {
26819             this.stopDrag();
26820             this.dragCurrent = null;
26821         }
26822
26823         this._execOnAll("unreg", []);
26824
26825         for (var i in this.elementCache) {
26826             delete this.elementCache[i];
26827         }
26828
26829         this.elementCache = {};
26830         this.ids = {};
26831     },
26832
26833     /**
26834      * A cache of DOM elements
26835      * @property elementCache
26836      * @private
26837      */
26838     elementCache: {},
26839
26840     /**
26841      * Get the wrapper for the DOM element specified
26842      * @method getElWrapper
26843      * @param {String} id the id of the element to get
26844      * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
26845      * @private
26846      * @deprecated This wrapper isn't that useful
26847      */
26848     getElWrapper: function(id) {
26849         var oWrapper = this.elementCache[id];
26850         if (!oWrapper || !oWrapper.el) {
26851             oWrapper = this.elementCache[id] =
26852                 new this.ElementWrapper(Ext.getDom(id));
26853         }
26854         return oWrapper;
26855     },
26856
26857     /**
26858      * Returns the actual DOM element
26859      * @method getElement
26860      * @param {String} id the id of the elment to get
26861      * @return {Object} The element
26862      * @deprecated use Ext.lib.Ext.getDom instead
26863      */
26864     getElement: function(id) {
26865         return Ext.getDom(id);
26866     },
26867
26868     /**
26869      * Returns the style property for the DOM element (i.e.,
26870      * document.getElById(id).style)
26871      * @method getCss
26872      * @param {String} id the id of the elment to get
26873      * @return {Object} The style property of the element
26874      */
26875     getCss: function(id) {
26876         var el = Ext.getDom(id);
26877         return (el) ? el.style : null;
26878     },
26879
26880     /**
26881      * @class Ext.dd.DragDropManager.ElementWrapper
26882      * Inner class for cached elements
26883      * @private
26884      * @deprecated
26885      */
26886     ElementWrapper: function(el) {
26887         /**
26888          * The element
26889          * @property el
26890          */
26891         this.el = el || null;
26892         /**
26893          * The element id
26894          * @property id
26895          */
26896         this.id = this.el && el.id;
26897         /**
26898          * A reference to the style property
26899          * @property css
26900          */
26901         this.css = this.el && el.style;
26902     },
26903
26904     // The DragDropManager class continues
26905     /** @class Ext.dd.DragDropManager */
26906
26907     /**
26908      * Returns the X position of an html element
26909      * @param {HTMLElement} el the element for which to get the position
26910      * @return {Number} the X coordinate
26911      */
26912     getPosX: function(el) {
26913         return Ext.Element.getX(el);
26914     },
26915
26916     /**
26917      * Returns the Y position of an html element
26918      * @param {HTMLElement} el the element for which to get the position
26919      * @return {Number} the Y coordinate
26920      */
26921     getPosY: function(el) {
26922         return Ext.Element.getY(el);
26923     },
26924
26925     /**
26926      * Swap two nodes.  In IE, we use the native method, for others we
26927      * emulate the IE behavior
26928      * @param {HTMLElement} n1 the first node to swap
26929      * @param {HTMLElement} n2 the other node to swap
26930      */
26931     swapNode: function(n1, n2) {
26932         if (n1.swapNode) {
26933             n1.swapNode(n2);
26934         } else {
26935             var p = n2.parentNode;
26936             var s = n2.nextSibling;
26937
26938             if (s == n1) {
26939                 p.insertBefore(n1, n2);
26940             } else if (n2 == n1.nextSibling) {
26941                 p.insertBefore(n2, n1);
26942             } else {
26943                 n1.parentNode.replaceChild(n2, n1);
26944                 p.insertBefore(n1, s);
26945             }
26946         }
26947     },
26948
26949     /**
26950      * Returns the current scroll position
26951      * @private
26952      */
26953     getScroll: function () {
26954         var doc   = window.document,
26955             docEl = doc.documentElement,
26956             body  = doc.body,
26957             top   = 0,
26958             left  = 0;
26959
26960         if (Ext.isGecko4) {
26961             top  = window.scrollYOffset;
26962             left = window.scrollXOffset;
26963         } else {
26964             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
26965                 top  = docEl.scrollTop;
26966                 left = docEl.scrollLeft;
26967             } else if (body) {
26968                 top  = body.scrollTop;
26969                 left = body.scrollLeft;
26970             }
26971         }
26972         return {
26973             top: top,
26974             left: left
26975         };
26976     },
26977
26978     /**
26979      * Returns the specified element style property
26980      * @param {HTMLElement} el          the element
26981      * @param {String}      styleProp   the style property
26982      * @return {String} The value of the style property
26983      */
26984     getStyle: function(el, styleProp) {
26985         return Ext.fly(el).getStyle(styleProp);
26986     },
26987
26988     /**
26989      * Gets the scrollTop
26990      * @return {Number} the document's scrollTop
26991      */
26992     getScrollTop: function () {
26993         return this.getScroll().top;
26994     },
26995
26996     /**
26997      * Gets the scrollLeft
26998      * @return {Number} the document's scrollTop
26999      */
27000     getScrollLeft: function () {
27001         return this.getScroll().left;
27002     },
27003
27004     /**
27005      * Sets the x/y position of an element to the location of the
27006      * target element.
27007      * @param {HTMLElement} moveEl      The element to move
27008      * @param {HTMLElement} targetEl    The position reference element
27009      */
27010     moveToEl: function (moveEl, targetEl) {
27011         var aCoord = Ext.Element.getXY(targetEl);
27012         Ext.Element.setXY(moveEl, aCoord);
27013     },
27014
27015     /**
27016      * Numeric array sort function
27017      * @param {Number} a
27018      * @param {Number} b
27019      * @returns {Number} positive, negative or 0
27020      */
27021     numericSort: function(a, b) {
27022         return (a - b);
27023     },
27024
27025     /**
27026      * Internal counter
27027      * @property {Number} _timeoutCount
27028      * @private
27029      */
27030     _timeoutCount: 0,
27031
27032     /**
27033      * Trying to make the load order less important.  Without this we get
27034      * an error if this file is loaded before the Event Utility.
27035      * @private
27036      */
27037     _addListeners: function() {
27038         if ( document ) {
27039             this._onLoad();
27040         } else {
27041             if (this._timeoutCount > 2000) {
27042             } else {
27043                 setTimeout(this._addListeners, 10);
27044                 if (document && document.body) {
27045                     this._timeoutCount += 1;
27046                 }
27047             }
27048         }
27049     },
27050
27051     /**
27052      * Recursively searches the immediate parent and all child nodes for
27053      * the handle element in order to determine wheter or not it was
27054      * clicked.
27055      * @param {HTMLElement} node the html element to inspect
27056      */
27057     handleWasClicked: function(node, id) {
27058         if (this.isHandle(id, node.id)) {
27059             return true;
27060         } else {
27061             // check to see if this is a text node child of the one we want
27062             var p = node.parentNode;
27063
27064             while (p) {
27065                 if (this.isHandle(id, p.id)) {
27066                     return true;
27067                 } else {
27068                     p = p.parentNode;
27069                 }
27070             }
27071         }
27072
27073         return false;
27074     }
27075 }, function() {
27076     this._addListeners();
27077 });
27078
27079 /**
27080  * @class Ext.layout.container.Box
27081  * @extends Ext.layout.container.Container
27082  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
27083  */
27084
27085 Ext.define('Ext.layout.container.Box', {
27086
27087     /* Begin Definitions */
27088
27089     alias: ['layout.box'],
27090     extend: 'Ext.layout.container.Container',
27091     alternateClassName: 'Ext.layout.BoxLayout',
27092
27093     requires: [
27094         'Ext.layout.container.boxOverflow.None',
27095         'Ext.layout.container.boxOverflow.Menu',
27096         'Ext.layout.container.boxOverflow.Scroller',
27097         'Ext.util.Format',
27098         'Ext.dd.DragDropManager'
27099     ],
27100
27101     /* End Definitions */
27102
27103     /**
27104      * @cfg {Boolean/Number/Object} animate
27105      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
27106      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
27107      * <p>May be set as a property at any time.</p>
27108      */
27109
27110     /**
27111      * @cfg {Object} defaultMargins
27112      * <p>If the individual contained items do not have a <tt>margins</tt>
27113      * property specified or margin specified via CSS, the default margins from this property will be
27114      * applied to each item.</p>
27115      * <br><p>This property may be specified as an object containing margins
27116      * to apply in the format:</p><pre><code>
27117 {
27118     top: (top margin),
27119     right: (right margin),
27120     bottom: (bottom margin),
27121     left: (left margin)
27122 }</code></pre>
27123      * <p>This property may also be specified as a string containing
27124      * space-separated, numeric margin values. The order of the sides associated
27125      * with each value matches the way CSS processes margin values:</p>
27126      * <div class="mdetail-params"><ul>
27127      * <li>If there is only one value, it applies to all sides.</li>
27128      * <li>If there are two values, the top and bottom borders are set to the
27129      * first value and the right and left are set to the second.</li>
27130      * <li>If there are three values, the top is set to the first value, the left
27131      * and right are set to the second, and the bottom is set to the third.</li>
27132      * <li>If there are four values, they apply to the top, right, bottom, and
27133      * left, respectively.</li>
27134      * </ul></div>
27135      */
27136     defaultMargins: {
27137         top: 0,
27138         right: 0,
27139         bottom: 0,
27140         left: 0
27141     },
27142
27143     /**
27144      * @cfg {String} padding
27145      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
27146      * <p>This property must be specified as a string containing
27147      * space-separated, numeric padding values. The order of the sides associated
27148      * with each value matches the way CSS processes padding values:</p>
27149      * <div class="mdetail-params"><ul>
27150      * <li>If there is only one value, it applies to all sides.</li>
27151      * <li>If there are two values, the top and bottom borders are set to the
27152      * first value and the right and left are set to the second.</li>
27153      * <li>If there are three values, the top is set to the first value, the left
27154      * and right are set to the second, and the bottom is set to the third.</li>
27155      * <li>If there are four values, they apply to the top, right, bottom, and
27156      * left, respectively.</li>
27157      * </ul></div>
27158      */
27159     padding: '0',
27160     // documented in subclasses
27161     pack: 'start',
27162
27163     /**
27164      * @cfg {String} pack
27165      * Controls how the child items of the container are packed together. Acceptable configuration values
27166      * for this property are:
27167      * <div class="mdetail-params"><ul>
27168      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
27169      * <b>left</b> side of container</div></li>
27170      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
27171      * <b>mid-width</b> of container</div></li>
27172      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
27173      * side of container</div></li>
27174      * </ul></div>
27175      */
27176     /**
27177      * @cfg {Number} flex
27178      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
27179      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
27180      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
27181      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
27182      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
27183      */
27184
27185     type: 'box',
27186     scrollOffset: 0,
27187     itemCls: Ext.baseCSSPrefix + 'box-item',
27188     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
27189     innerCls: Ext.baseCSSPrefix + 'box-inner',
27190
27191     bindToOwnerCtContainer: true,
27192
27193     // availableSpaceOffset is used to adjust the availableWidth, typically used
27194     // to reserve space for a scrollbar
27195     availableSpaceOffset: 0,
27196
27197     // whether or not to reserve the availableSpaceOffset in layout calculations
27198     reserveOffset: true,
27199
27200     /**
27201      * @cfg {Boolean} shrinkToFit
27202      * True (the default) to allow fixed size components to shrink (limited to their
27203      * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
27204      * overflow.
27205      */
27206     shrinkToFit: true,
27207
27208     /**
27209      * @cfg {Boolean} clearInnerCtOnLayout
27210      */
27211     clearInnerCtOnLayout: false,
27212
27213     flexSortFn: function (a, b) {
27214         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
27215             infiniteValue = Infinity;
27216         a = a.component[maxParallelPrefix] || infiniteValue;
27217         b = b.component[maxParallelPrefix] || infiniteValue;
27218         // IE 6/7 Don't like Infinity - Infinity...
27219         if (!isFinite(a) && !isFinite(b)) {
27220             return false;
27221         }
27222         return a - b;
27223     },
27224
27225     // Sort into *descending* order.
27226     minSizeSortFn: function(a, b) {
27227         return b.available - a.available;
27228     },
27229
27230     constructor: function(config) {
27231         var me = this;
27232
27233         me.callParent(arguments);
27234
27235         // The sort function needs access to properties in this, so must be bound.
27236         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
27237
27238         me.initOverflowHandler();
27239     },
27240
27241     /**
27242      * @private
27243      * Returns the current size and positioning of the passed child item.
27244      * @param {Ext.Component} child The child Component to calculate the box for
27245      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
27246      */
27247     getChildBox: function(child) {
27248         child = child.el || this.owner.getComponent(child).el;
27249         var size = child.getBox(false, true);
27250         return {
27251             left: size.left,
27252             top: size.top,
27253             width: size.width,
27254             height: size.height
27255         };
27256     },
27257
27258     /**
27259      * @private
27260      * Calculates the size and positioning of the passed child item.
27261      * @param {Ext.Component} child The child Component to calculate the box for
27262      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
27263      */
27264     calculateChildBox: function(child) {
27265         var me = this,
27266             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
27267             ln = boxes.length,
27268             i = 0;
27269
27270         child = me.owner.getComponent(child);
27271         for (; i < ln; i++) {
27272             if (boxes[i].component === child) {
27273                 return boxes[i];
27274             }
27275         }
27276     },
27277
27278     /**
27279      * @private
27280      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
27281      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
27282      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
27283      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
27284      * @param {Object} targetSize Object containing target size and height
27285      * @return {Object} Object containing box measurements for each child, plus meta data
27286      */
27287     calculateChildBoxes: function(visibleItems, targetSize) {
27288         var me = this,
27289             math = Math,
27290             mmax = math.max,
27291             infiniteValue = Infinity,
27292             undefinedValue,
27293
27294             parallelPrefix = me.parallelPrefix,
27295             parallelPrefixCap = me.parallelPrefixCap,
27296             perpendicularPrefix = me.perpendicularPrefix,
27297             perpendicularPrefixCap = me.perpendicularPrefixCap,
27298             parallelMinString = 'min' + parallelPrefixCap,
27299             perpendicularMinString = 'min' + perpendicularPrefixCap,
27300             perpendicularMaxString = 'max' + perpendicularPrefixCap,
27301
27302             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
27303             perpendicularSize = targetSize[perpendicularPrefix],
27304             padding = me.padding,
27305             parallelOffset = padding[me.parallelBefore],
27306             paddingParallel = parallelOffset + padding[me.parallelAfter],
27307             perpendicularOffset = padding[me.perpendicularLeftTop],
27308             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
27309             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
27310
27311             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
27312
27313             isStart = me.pack == 'start',
27314             isCenter = me.pack == 'center',
27315             isEnd = me.pack == 'end',
27316
27317             constrain = Ext.Number.constrain,
27318             visibleCount = visibleItems.length,
27319             nonFlexSize = 0,
27320             totalFlex = 0,
27321             desiredSize = 0,
27322             minimumSize = 0,
27323             maxSize = 0,
27324             boxes = [],
27325             minSizes = [],
27326             calculatedWidth,
27327
27328             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
27329             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
27330             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
27331             perpendicularMargins, stretchSize;
27332
27333         //gather the total flex of all flexed items and the width taken up by fixed width items
27334         for (i = 0; i < visibleCount; i++) {
27335             child = visibleItems[i];
27336             childPerpendicular = child[perpendicularPrefix];
27337             if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
27338                 if (child.componentLayout.initialized !== true) {
27339                     me.layoutItem(child);
27340                 }
27341             }
27342
27343             childMargins = child.margins;
27344             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
27345
27346             // Create the box description object for this child item.
27347             tmpObj = {
27348                 component: child,
27349                 margins: childMargins
27350             };
27351
27352             // flex and not 'auto' width
27353             if (child.flex) {
27354                 totalFlex += child.flex;
27355                 childParallel = undefinedValue;
27356             }
27357             // Not flexed or 'auto' width or undefined width
27358             else {
27359                 if (!(child[parallelPrefix] && childPerpendicular)) {
27360                     childSize = child.getSize();
27361                 }
27362                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
27363                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
27364             }
27365
27366             nonFlexSize += parallelMargins + (childParallel || 0);
27367             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
27368             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
27369
27370             // Max height for align - force layout of non-laid out subcontainers without a numeric height
27371             if (typeof childPerpendicular != 'number') {
27372                 // Clear any static sizing and revert to flow so we can get a proper measurement
27373                 // child['set' + perpendicularPrefixCap](null);
27374                 childPerpendicular = child['get' + perpendicularPrefixCap]();
27375             }
27376
27377             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
27378             // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
27379             maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
27380
27381             tmpObj[parallelPrefix] = childParallel || undefinedValue;
27382             tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
27383             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
27384             boxes.push(tmpObj);
27385         }
27386
27387         // Only calculate parallel overflow indicators if we are not auto sizing
27388         if (!me.autoSize) {
27389             shortfall = desiredSize - parallelSize;
27390             tooNarrow = minimumSize > parallelSize;
27391         }
27392
27393         //the space available to the flexed items
27394         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
27395
27396         if (tooNarrow) {
27397             for (i = 0; i < visibleCount; i++) {
27398                 box = boxes[i];
27399                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
27400                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
27401                 box[parallelPrefix] = minSize;
27402             }
27403         }
27404         else {
27405             //all flexed items should be sized to their minimum size, other items should be shrunk down until
27406             //the shortfall has been accounted for
27407             if (shortfall > 0) {
27408                 /*
27409                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
27410                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
27411                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
27412                  */
27413                 for (i = 0; i < visibleCount; i++) {
27414                     item = visibleItems[i];
27415                     minSize = item[parallelMinString] || 0;
27416
27417                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
27418                     //shrunk to their minSize because they're flexible and should be the first to lose size
27419                     if (item.flex) {
27420                         box = boxes[i];
27421                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
27422                         box[parallelPrefix] = minSize;
27423                     } else if (me.shrinkToFit) {
27424                         minSizes.push({
27425                             minSize: minSize,
27426                             available: boxes[i][parallelPrefix] - minSize,
27427                             index: i
27428                         });
27429                     }
27430                 }
27431
27432                 //sort by descending amount of width remaining before minWidth is reached
27433                 Ext.Array.sort(minSizes, me.minSizeSortFn);
27434
27435                 /*
27436                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
27437                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
27438                  * smallest difference between their size and minSize first, so that if reducing the size by the average
27439                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
27440                  */
27441                 for (i = 0, length = minSizes.length; i < length; i++) {
27442                     itemIndex = minSizes[i].index;
27443
27444                     if (itemIndex == undefinedValue) {
27445                         continue;
27446                     }
27447                     item = visibleItems[itemIndex];
27448                     minSize = minSizes[i].minSize;
27449
27450                     box = boxes[itemIndex];
27451                     oldSize = box[parallelPrefix];
27452                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
27453                     reduction = oldSize - newSize;
27454
27455                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
27456                     box[parallelPrefix] = newSize;
27457                     shortfall -= reduction;
27458                 }
27459                 tooNarrow = (shortfall > 0);
27460             }
27461             else {
27462                 remainingSpace = availableSpace;
27463                 remainingFlex = totalFlex;
27464                 flexedBoxes = [];
27465
27466                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
27467                 for (i = 0; i < visibleCount; i++) {
27468                     child = visibleItems[i];
27469                     if (isStart && child.flex) {
27470                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
27471                     }
27472                 }
27473                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
27474                 // so that unallocated space caused by maxWidth being less than flexed width
27475                 // can be reallocated to subsequent flexed boxes.
27476                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
27477
27478                 // Calculate the size of each flexed item, and attempt to set it.
27479                 for (i = 0; i < flexedBoxes.length; i++) {
27480                     calcs = flexedBoxes[i];
27481                     child = calcs.component;
27482                     childMargins = calcs.margins;
27483
27484                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
27485
27486                     // Implement maxSize and minSize check
27487                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
27488
27489                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
27490                     remainingSpace -= flexedSize;
27491                     remainingFlex -= child.flex;
27492
27493                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
27494                     calcs[parallelPrefix] = flexedSize;
27495                 }
27496             }
27497         }
27498
27499         if (isCenter) {
27500             parallelOffset += availableSpace / 2;
27501         }
27502         else if (isEnd) {
27503             parallelOffset += availableSpace;
27504         }
27505
27506         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
27507         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
27508         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
27509         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
27510         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
27511
27512             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
27513             if (me.owner.frameSize) {
27514                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
27515             }
27516             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
27517             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
27518         }
27519
27520         //finally, calculate the left and top position of each item
27521         for (i = 0; i < visibleCount; i++) {
27522             child = visibleItems[i];
27523             calcs = boxes[i];
27524
27525             childMargins = calcs.margins;
27526
27527             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
27528
27529             // Advance past the "before" margin
27530             parallelOffset += childMargins[me.parallelBefore];
27531
27532             calcs[me.parallelBefore] = parallelOffset;
27533             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
27534
27535             if (me.align == 'stretch') {
27536                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
27537                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
27538                 calcs[perpendicularPrefix] = stretchSize;
27539             }
27540             else if (me.align == 'stretchmax') {
27541                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
27542                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
27543                 calcs[perpendicularPrefix] = stretchSize;
27544             }
27545             else if (me.align == me.alignCenteringString) {
27546                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
27547                 // the size to yield the space available to center within.
27548                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
27549                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
27550                 if (diff > 0) {
27551                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
27552                 }
27553             }
27554
27555             // Advance past the box size and the "after" margin
27556             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
27557         }
27558
27559         return {
27560             boxes: boxes,
27561             meta : {
27562                 calculatedWidth: calculatedWidth,
27563                 maxSize: maxSize,
27564                 nonFlexSize: nonFlexSize,
27565                 desiredSize: desiredSize,
27566                 minimumSize: minimumSize,
27567                 shortfall: shortfall,
27568                 tooNarrow: tooNarrow
27569             }
27570         };
27571     },
27572
27573     onRemove: function(comp){
27574         this.callParent(arguments);
27575         if (this.overflowHandler) {
27576             this.overflowHandler.onRemove(comp);
27577         }
27578     },
27579
27580     /**
27581      * @private
27582      */
27583     initOverflowHandler: function() {
27584         var handler = this.overflowHandler;
27585
27586         if (typeof handler == 'string') {
27587             handler = {
27588                 type: handler
27589             };
27590         }
27591
27592         var handlerType = 'None';
27593         if (handler && handler.type !== undefined) {
27594             handlerType = handler.type;
27595         }
27596
27597         var constructor = Ext.layout.container.boxOverflow[handlerType];
27598         if (constructor[this.type]) {
27599             constructor = constructor[this.type];
27600         }
27601
27602         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
27603     },
27604
27605     /**
27606      * @private
27607      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
27608      * when laying out
27609      */
27610     onLayout: function() {
27611         this.callParent();
27612         // Clear the innerCt size so it doesn't influence the child items.
27613         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
27614             this.innerCt.setSize(null, null);
27615         }
27616
27617         var me = this,
27618             targetSize = me.getLayoutTargetSize(),
27619             items = me.getVisibleItems(),
27620             calcs = me.calculateChildBoxes(items, targetSize),
27621             boxes = calcs.boxes,
27622             meta = calcs.meta,
27623             handler, method, results;
27624
27625         if (me.autoSize && calcs.meta.desiredSize) {
27626             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
27627         }
27628
27629         //invoke the overflow handler, if one is configured
27630         if (meta.shortfall > 0) {
27631             handler = me.overflowHandler;
27632             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
27633
27634             results = handler[method](calcs, targetSize);
27635
27636             if (results) {
27637                 if (results.targetSize) {
27638                     targetSize = results.targetSize;
27639                 }
27640
27641                 if (results.recalculate) {
27642                     items = me.getVisibleItems();
27643                     calcs = me.calculateChildBoxes(items, targetSize);
27644                     boxes = calcs.boxes;
27645                 }
27646             }
27647         } else {
27648             me.overflowHandler.clearOverflow();
27649         }
27650
27651         /**
27652          * @private
27653          * @property layoutTargetLastSize
27654          * @type Object
27655          * Private cache of the last measured size of the layout target. This should never be used except by
27656          * BoxLayout subclasses during their onLayout run.
27657          */
27658         me.layoutTargetLastSize = targetSize;
27659
27660         /**
27661          * @private
27662          * @property childBoxCache
27663          * @type Array
27664          * Array of the last calculated height, width, top and left positions of each visible rendered component
27665          * within the Box layout.
27666          */
27667         me.childBoxCache = calcs;
27668
27669         me.updateInnerCtSize(targetSize, calcs);
27670         me.updateChildBoxes(boxes);
27671         me.handleTargetOverflow(targetSize);
27672     },
27673     
27674     animCallback: Ext.emptyFn,
27675
27676     /**
27677      * Resizes and repositions each child component
27678      * @param {Object[]} boxes The box measurements
27679      */
27680     updateChildBoxes: function(boxes) {
27681         var me = this,
27682             i = 0,
27683             length = boxes.length,
27684             animQueue = [],
27685             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
27686             oldBox, newBox, changed, comp, boxAnim, animCallback;
27687
27688         for (; i < length; i++) {
27689             newBox = boxes[i];
27690             comp = newBox.component;
27691
27692             // If a Component is being drag/dropped, skip positioning it.
27693             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
27694             if (dd && (dd.getDragEl() === comp.el.dom)) {
27695                 continue;
27696             }
27697
27698             changed = false;
27699
27700             oldBox = me.getChildBox(comp);
27701
27702             // If we are animating, we build up an array of Anim config objects, one for each
27703             // child Component which has any changed box properties. Those with unchanged
27704             // properties are not animated.
27705             if (me.animate) {
27706                 // Animate may be a config object containing callback.
27707                 animCallback = me.animate.callback || me.animate;
27708                 boxAnim = {
27709                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
27710                     target: comp,
27711                     from: {},
27712                     to: {},
27713                     listeners: {}
27714                 };
27715                 // Only set from and to properties when there's a change.
27716                 // Perform as few Component setter methods as possible.
27717                 // Temporarily set the property values that we are not animating
27718                 // so that doComponentLayout does not auto-size them.
27719                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
27720                     changed = true;
27721                     // boxAnim.from.width = oldBox.width;
27722                     boxAnim.to.width = newBox.width;
27723                 }
27724                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
27725                     changed = true;
27726                     // boxAnim.from.height = oldBox.height;
27727                     boxAnim.to.height = newBox.height;
27728                 }
27729                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
27730                     changed = true;
27731                     // boxAnim.from.left = oldBox.left;
27732                     boxAnim.to.left = newBox.left;
27733                 }
27734                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
27735                     changed = true;
27736                     // boxAnim.from.top = oldBox.top;
27737                     boxAnim.to.top = newBox.top;
27738                 }
27739                 if (changed) {
27740                     animQueue.push(boxAnim);
27741                 }
27742             } else {
27743                 if (newBox.dirtySize) {
27744                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
27745                         me.setItemSize(comp, newBox.width, newBox.height);
27746                     }
27747                 }
27748                 // Don't set positions to NaN
27749                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
27750                     continue;
27751                 }
27752                 comp.setPosition(newBox.left, newBox.top);
27753             }
27754         }
27755
27756         // Kick off any queued animations
27757         length = animQueue.length;
27758         if (length) {
27759
27760             // A function which cleans up when a Component's animation is done.
27761             // The last one to finish calls the callback.
27762             var afterAnimate = function(anim) {
27763                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
27764                 length -= 1;
27765                 if (!length) {
27766                     me.animCallback(anim);
27767                     me.layoutBusy = false;
27768                     if (Ext.isFunction(animCallback)) {
27769                         animCallback();
27770                     }
27771                 }
27772             };
27773
27774             var beforeAnimate = function() {
27775                 me.layoutBusy = true;
27776             };
27777
27778             // Start each box animation off
27779             for (i = 0, length = animQueue.length; i < length; i++) {
27780                 boxAnim = animQueue[i];
27781
27782                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
27783                 boxAnim.listeners.afteranimate = afterAnimate;
27784
27785                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
27786                 if (!i) {
27787                     boxAnim.listeners.beforeanimate = beforeAnimate;
27788                 }
27789                 if (me.animate.duration) {
27790                     boxAnim.duration = me.animate.duration;
27791                 }
27792                 comp = boxAnim.target;
27793                 delete boxAnim.target;
27794                 // Stop any currently running animation
27795                 comp.stopAnimation();
27796                 comp.animate(boxAnim);
27797             }
27798         }
27799     },
27800
27801     /**
27802      * @private
27803      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
27804      * to make sure all child items fit within it. We call this before sizing the children because if our child
27805      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
27806      * again immediately afterwards, giving a performance hit.
27807      * Subclasses should provide an implementation.
27808      * @param {Object} currentSize The current height and width of the innerCt
27809      * @param {Object} calculations The new box calculations of all items to be laid out
27810      */
27811     updateInnerCtSize: function(tSize, calcs) {
27812         var me = this,
27813             mmax = Math.max,
27814             align = me.align,
27815             padding = me.padding,
27816             width = tSize.width,
27817             height = tSize.height,
27818             meta = calcs.meta,
27819             innerCtWidth,
27820             innerCtHeight;
27821
27822         if (me.direction == 'horizontal') {
27823             innerCtWidth = width;
27824             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
27825
27826             if (align == 'stretch') {
27827                 innerCtHeight = height;
27828             }
27829             else if (align == 'middle') {
27830                 innerCtHeight = mmax(height, innerCtHeight);
27831             }
27832         } else {
27833             innerCtHeight = height;
27834             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
27835
27836             if (align == 'stretch') {
27837                 innerCtWidth = width;
27838             }
27839             else if (align == 'center') {
27840                 innerCtWidth = mmax(width, innerCtWidth);
27841             }
27842         }
27843         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
27844
27845         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
27846         // then, if the Component has not assumed the size of its content, set it to do so.
27847         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
27848             me.owner.el.setWidth(meta.calculatedWidth);
27849         }
27850
27851         if (me.innerCt.dom.scrollTop) {
27852             me.innerCt.dom.scrollTop = 0;
27853         }
27854     },
27855
27856     /**
27857      * @private
27858      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
27859      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
27860      * target. Having a Box layout inside such a target is therefore not recommended.
27861      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
27862      * @param {Ext.container.Container} container The container
27863      * @param {Ext.Element} target The target element
27864      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
27865      */
27866     handleTargetOverflow: function(previousTargetSize) {
27867         var target = this.getTarget(),
27868             overflow = target.getStyle('overflow'),
27869             newTargetSize;
27870
27871         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
27872             newTargetSize = this.getLayoutTargetSize();
27873             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
27874                 this.adjustmentPass = true;
27875                 this.onLayout();
27876                 return true;
27877             }
27878         }
27879
27880         delete this.adjustmentPass;
27881     },
27882
27883     // private
27884     isValidParent : function(item, target, position) {
27885         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
27886         // We only care whether the item is a direct child of the innerCt element.
27887         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
27888         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
27889     },
27890
27891     // Overridden method from AbstractContainer.
27892     // Used in the base AbstractLayout.beforeLayout method to render all items into.
27893     getRenderTarget: function() {
27894         if (!this.innerCt) {
27895             // the innerCt prevents wrapping and shuffling while the container is resizing
27896             this.innerCt = this.getTarget().createChild({
27897                 cls: this.innerCls,
27898                 role: 'presentation'
27899             });
27900             this.padding = Ext.util.Format.parseBox(this.padding);
27901         }
27902         return this.innerCt;
27903     },
27904
27905     // private
27906     renderItem: function(item, target) {
27907         this.callParent(arguments);
27908         var me = this,
27909             itemEl = item.getEl(),
27910             style = itemEl.dom.style,
27911             margins = item.margins || item.margin;
27912
27913         // Parse the item's margin/margins specification
27914         if (margins) {
27915             if (Ext.isString(margins) || Ext.isNumber(margins)) {
27916                 margins = Ext.util.Format.parseBox(margins);
27917             } else {
27918                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
27919             }
27920         } else {
27921             margins = Ext.apply({}, me.defaultMargins);
27922         }
27923
27924         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
27925         margins.top    += itemEl.getMargin('t');
27926         margins.right  += itemEl.getMargin('r');
27927         margins.bottom += itemEl.getMargin('b');
27928         margins.left   += itemEl.getMargin('l');
27929         margins.height  = margins.top  + margins.bottom;
27930         margins.width   = margins.left + margins.right;
27931         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
27932
27933         // Item must reference calculated margins.
27934         item.margins = margins;
27935     },
27936
27937     /**
27938      * @private
27939      */
27940     destroy: function() {
27941         Ext.destroy(this.innerCt, this.overflowHandler);
27942         this.callParent(arguments);
27943     }
27944 });
27945 /**
27946  * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
27947  * space between child items containing a numeric `flex` configuration.
27948  *
27949  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
27950  *
27951  *     @example
27952  *     Ext.create('Ext.Panel', {
27953  *         width: 500,
27954  *         height: 300,
27955  *         title: "HBoxLayout Panel",
27956  *         layout: {
27957  *             type: 'hbox',
27958  *             align: 'stretch'
27959  *         },
27960  *         renderTo: document.body,
27961  *         items: [{
27962  *             xtype: 'panel',
27963  *             title: 'Inner Panel One',
27964  *             flex: 2
27965  *         },{
27966  *             xtype: 'panel',
27967  *             title: 'Inner Panel Two',
27968  *             flex: 1
27969  *         },{
27970  *             xtype: 'panel',
27971  *             title: 'Inner Panel Three',
27972  *             flex: 1
27973  *         }]
27974  *     });
27975  */
27976 Ext.define('Ext.layout.container.HBox', {
27977
27978     /* Begin Definitions */
27979
27980     alias: ['layout.hbox'],
27981     extend: 'Ext.layout.container.Box',
27982     alternateClassName: 'Ext.layout.HBoxLayout',
27983
27984     /* End Definitions */
27985
27986     /**
27987      * @cfg {String} align
27988      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
27989      *
27990      * - **top** : **Default** child items are aligned vertically at the **top** of the container
27991      * - **middle** : child items are aligned vertically in the **middle** of the container
27992      * - **stretch** : child items are stretched vertically to fill the height of the container
27993      * - **stretchmax** : child items are stretched vertically to the height of the largest item.
27994      */
27995     align: 'top', // top, middle, stretch, strechmax
27996
27997     //@private
27998     alignCenteringString: 'middle',
27999
28000     type : 'hbox',
28001
28002     direction: 'horizontal',
28003
28004     // When creating an argument list to setSize, use this order
28005     parallelSizeIndex: 0,
28006     perpendicularSizeIndex: 1,
28007
28008     parallelPrefix: 'width',
28009     parallelPrefixCap: 'Width',
28010     parallelLT: 'l',
28011     parallelRB: 'r',
28012     parallelBefore: 'left',
28013     parallelBeforeCap: 'Left',
28014     parallelAfter: 'right',
28015     parallelPosition: 'x',
28016
28017     perpendicularPrefix: 'height',
28018     perpendicularPrefixCap: 'Height',
28019     perpendicularLT: 't',
28020     perpendicularRB: 'b',
28021     perpendicularLeftTop: 'top',
28022     perpendicularRightBottom: 'bottom',
28023     perpendicularPosition: 'y',
28024     configureItem: function(item) {
28025         if (item.flex) {
28026             item.layoutManagedWidth = 1;
28027         } else {
28028             item.layoutManagedWidth = 2;
28029         }
28030
28031         if (this.align === 'stretch' || this.align === 'stretchmax') {
28032             item.layoutManagedHeight = 1;
28033         } else {
28034             item.layoutManagedHeight = 2;
28035         }
28036         this.callParent(arguments);
28037     }
28038 });
28039 /**
28040  * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
28041  * between child items containing a numeric `flex` configuration.
28042  *
28043  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
28044  *
28045  *     @example
28046  *     Ext.create('Ext.Panel', {
28047  *         width: 500,
28048  *         height: 400,
28049  *         title: "VBoxLayout Panel",
28050  *         layout: {
28051  *             type: 'vbox',
28052  *             align: 'center'
28053  *         },
28054  *         renderTo: document.body,
28055  *         items: [{
28056  *             xtype: 'panel',
28057  *             title: 'Inner Panel One',
28058  *             width: 250,
28059  *             flex: 2
28060  *         },
28061  *         {
28062  *             xtype: 'panel',
28063  *             title: 'Inner Panel Two',
28064  *             width: 250,
28065  *             flex: 4
28066  *         },
28067  *         {
28068  *             xtype: 'panel',
28069  *             title: 'Inner Panel Three',
28070  *             width: '50%',
28071  *             flex: 4
28072  *         }]
28073  *     });
28074  */
28075 Ext.define('Ext.layout.container.VBox', {
28076
28077     /* Begin Definitions */
28078
28079     alias: ['layout.vbox'],
28080     extend: 'Ext.layout.container.Box',
28081     alternateClassName: 'Ext.layout.VBoxLayout',
28082
28083     /* End Definitions */
28084
28085     /**
28086      * @cfg {String} align
28087      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
28088      *
28089      * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
28090      * - **center** : child items are aligned horizontally at the **mid-width** of the container
28091      * - **stretch** : child items are stretched horizontally to fill the width of the container
28092      * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
28093      */
28094     align : 'left', // left, center, stretch, strechmax
28095
28096     //@private
28097     alignCenteringString: 'center',
28098
28099     type: 'vbox',
28100
28101     direction: 'vertical',
28102
28103     // When creating an argument list to setSize, use this order
28104     parallelSizeIndex: 1,
28105     perpendicularSizeIndex: 0,
28106
28107     parallelPrefix: 'height',
28108     parallelPrefixCap: 'Height',
28109     parallelLT: 't',
28110     parallelRB: 'b',
28111     parallelBefore: 'top',
28112     parallelBeforeCap: 'Top',
28113     parallelAfter: 'bottom',
28114     parallelPosition: 'y',
28115
28116     perpendicularPrefix: 'width',
28117     perpendicularPrefixCap: 'Width',
28118     perpendicularLT: 'l',
28119     perpendicularRB: 'r',
28120     perpendicularLeftTop: 'left',
28121     perpendicularRightBottom: 'right',
28122     perpendicularPosition: 'x',
28123     configureItem: function(item) {
28124         if (item.flex) {
28125             item.layoutManagedHeight = 1;
28126         } else {
28127             item.layoutManagedHeight = 2;
28128         }
28129
28130         if (this.align === 'stretch' || this.align === 'stretchmax') {
28131             item.layoutManagedWidth = 1;
28132         } else {
28133             item.layoutManagedWidth = 2;
28134         }
28135         this.callParent(arguments);
28136     }
28137 });
28138 /**
28139  * @class Ext.FocusManager
28140
28141 The FocusManager is responsible for globally:
28142
28143 1. Managing component focus
28144 2. Providing basic keyboard navigation
28145 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
28146
28147 To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
28148 deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();.  The
28149 FocusManager is disabled by default.
28150
28151 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
28152
28153 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
28154 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
28155 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
28156 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
28157
28158  * @singleton
28159  * @author Jarred Nicholls <jarred@sencha.com>
28160  * @docauthor Jarred Nicholls <jarred@sencha.com>
28161  */
28162 Ext.define('Ext.FocusManager', {
28163     singleton: true,
28164     alternateClassName: 'Ext.FocusMgr',
28165
28166     mixins: {
28167         observable: 'Ext.util.Observable'
28168     },
28169
28170     requires: [
28171         'Ext.ComponentManager',
28172         'Ext.ComponentQuery',
28173         'Ext.util.HashMap',
28174         'Ext.util.KeyNav'
28175     ],
28176
28177     /**
28178      * @property {Boolean} enabled
28179      * Whether or not the FocusManager is currently enabled
28180      */
28181     enabled: false,
28182
28183     /**
28184      * @property {Ext.Component} focusedCmp
28185      * The currently focused component. Defaults to `undefined`.
28186      */
28187
28188     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
28189
28190     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
28191
28192     /**
28193      * @property {String[]} whitelist
28194      * A list of xtypes that should ignore certain navigation input keys and
28195      * allow for the default browser event/behavior. These input keys include:
28196      *
28197      * 1. Backspace
28198      * 2. Delete
28199      * 3. Left
28200      * 4. Right
28201      * 5. Up
28202      * 6. Down
28203      *
28204      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
28205      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
28206      * the user to move the input cursor left and right, and to delete characters, etc.
28207      */
28208     whitelist: [
28209         'textfield'
28210     ],
28211
28212     tabIndexWhitelist: [
28213         'a',
28214         'button',
28215         'embed',
28216         'frame',
28217         'iframe',
28218         'img',
28219         'input',
28220         'object',
28221         'select',
28222         'textarea'
28223     ],
28224
28225     constructor: function() {
28226         var me = this,
28227             CQ = Ext.ComponentQuery;
28228
28229         me.addEvents(
28230             /**
28231              * @event beforecomponentfocus
28232              * Fires before a component becomes focused. Return `false` to prevent
28233              * the component from gaining focus.
28234              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
28235              * @param {Ext.Component} cmp The component that is being focused
28236              * @param {Ext.Component} previousCmp The component that was previously focused,
28237              * or `undefined` if there was no previously focused component.
28238              */
28239             'beforecomponentfocus',
28240
28241             /**
28242              * @event componentfocus
28243              * Fires after a component becomes focused.
28244              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
28245              * @param {Ext.Component} cmp The component that has been focused
28246              * @param {Ext.Component} previousCmp The component that was previously focused,
28247              * or `undefined` if there was no previously focused component.
28248              */
28249             'componentfocus',
28250
28251             /**
28252              * @event disable
28253              * Fires when the FocusManager is disabled
28254              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
28255              */
28256             'disable',
28257
28258             /**
28259              * @event enable
28260              * Fires when the FocusManager is enabled
28261              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
28262              */
28263             'enable'
28264         );
28265
28266         // Setup KeyNav that's bound to document to catch all
28267         // unhandled/bubbled key events for navigation
28268         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
28269             disabled: true,
28270             scope: me,
28271
28272             backspace: me.focusLast,
28273             enter: me.navigateIn,
28274             esc: me.navigateOut,
28275             tab: me.navigateSiblings
28276
28277             //space: me.navigateIn,
28278             //del: me.focusLast,
28279             //left: me.navigateSiblings,
28280             //right: me.navigateSiblings,
28281             //down: me.navigateSiblings,
28282             //up: me.navigateSiblings
28283         });
28284
28285         me.focusData = {};
28286         me.subscribers = Ext.create('Ext.util.HashMap');
28287         me.focusChain = {};
28288
28289         // Setup some ComponentQuery pseudos
28290         Ext.apply(CQ.pseudos, {
28291             focusable: function(cmps) {
28292                 var len = cmps.length,
28293                     results = [],
28294                     i = 0,
28295                     c,
28296
28297                     isFocusable = function(x) {
28298                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
28299                     };
28300
28301                 for (; i < len; i++) {
28302                     c = cmps[i];
28303                     if (isFocusable(c)) {
28304                         results.push(c);
28305                     }
28306                 }
28307
28308                 return results;
28309             },
28310
28311             nextFocus: function(cmps, idx, step) {
28312                 step = step || 1;
28313                 idx = parseInt(idx, 10);
28314
28315                 var len = cmps.length,
28316                     i = idx + step,
28317                     c;
28318
28319                 for (; i != idx; i += step) {
28320                     if (i >= len) {
28321                         i = 0;
28322                     } else if (i < 0) {
28323                         i = len - 1;
28324                     }
28325
28326                     c = cmps[i];
28327                     if (CQ.is(c, ':focusable')) {
28328                         return [c];
28329                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
28330                         return [c.placeholder];
28331                     }
28332                 }
28333
28334                 return [];
28335             },
28336
28337             prevFocus: function(cmps, idx) {
28338                 return this.nextFocus(cmps, idx, -1);
28339             },
28340
28341             root: function(cmps) {
28342                 var len = cmps.length,
28343                     results = [],
28344                     i = 0,
28345                     c;
28346
28347                 for (; i < len; i++) {
28348                     c = cmps[i];
28349                     if (!c.ownerCt) {
28350                         results.push(c);
28351                     }
28352                 }
28353
28354                 return results;
28355             }
28356         });
28357     },
28358
28359     /**
28360      * Adds the specified xtype to the {@link #whitelist}.
28361      * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
28362      */
28363     addXTypeToWhitelist: function(xtype) {
28364         var me = this;
28365
28366         if (Ext.isArray(xtype)) {
28367             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
28368             return;
28369         }
28370
28371         if (!Ext.Array.contains(me.whitelist, xtype)) {
28372             me.whitelist.push(xtype);
28373         }
28374     },
28375
28376     clearComponent: function(cmp) {
28377         clearTimeout(this.cmpFocusDelay);
28378         if (!cmp.isDestroyed) {
28379             cmp.blur();
28380         }
28381     },
28382
28383     /**
28384      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
28385      */
28386     disable: function() {
28387         var me = this;
28388
28389         if (!me.enabled) {
28390             return;
28391         }
28392
28393         delete me.options;
28394         me.enabled = false;
28395
28396         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
28397
28398         me.removeDOM();
28399
28400         // Stop handling key navigation
28401         me.keyNav.disable();
28402
28403         // disable focus for all components
28404         me.setFocusAll(false);
28405
28406         me.fireEvent('disable', me);
28407     },
28408
28409     /**
28410      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
28411      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
28412         - focusFrame : Boolean
28413             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
28414      * @markdown
28415      */
28416     enable: function(options) {
28417         var me = this;
28418
28419         if (options === true) {
28420             options = { focusFrame: true };
28421         }
28422         me.options = options = options || {};
28423
28424         if (me.enabled) {
28425             return;
28426         }
28427
28428         // Handle components that are newly added after we are enabled
28429         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
28430
28431         me.initDOM(options);
28432
28433         // Start handling key navigation
28434         me.keyNav.enable();
28435
28436         // enable focus for all components
28437         me.setFocusAll(true, options);
28438
28439         // Finally, let's focus our global focus el so we start fresh
28440         me.focusEl.focus();
28441         delete me.focusedCmp;
28442
28443         me.enabled = true;
28444         me.fireEvent('enable', me);
28445     },
28446
28447     focusLast: function(e) {
28448         var me = this;
28449
28450         if (me.isWhitelisted(me.focusedCmp)) {
28451             return true;
28452         }
28453
28454         // Go back to last focused item
28455         if (me.previousFocusedCmp) {
28456             me.previousFocusedCmp.focus();
28457         }
28458     },
28459
28460     getRootComponents: function() {
28461         var me = this,
28462             CQ = Ext.ComponentQuery,
28463             inline = CQ.query(':focusable:root:not([floating])'),
28464             floating = CQ.query(':focusable:root[floating]');
28465
28466         // Floating items should go to the top of our root stack, and be ordered
28467         // by their z-index (highest first)
28468         floating.sort(function(a, b) {
28469             return a.el.getZIndex() > b.el.getZIndex();
28470         });
28471
28472         return floating.concat(inline);
28473     },
28474
28475     initDOM: function(options) {
28476         var me = this,
28477             sp = '&#160',
28478             cls = me.focusFrameCls;
28479
28480         if (!Ext.isReady) {
28481             Ext.onReady(me.initDOM, me);
28482             return;
28483         }
28484
28485         // Create global focus element
28486         if (!me.focusEl) {
28487             me.focusEl = Ext.getBody().createChild({
28488                 tabIndex: '-1',
28489                 cls: me.focusElementCls,
28490                 html: sp
28491             });
28492         }
28493
28494         // Create global focus frame
28495         if (!me.focusFrame && options.focusFrame) {
28496             me.focusFrame = Ext.getBody().createChild({
28497                 cls: cls,
28498                 children: [
28499                     { cls: cls + '-top' },
28500                     { cls: cls + '-bottom' },
28501                     { cls: cls + '-left' },
28502                     { cls: cls + '-right' }
28503                 ],
28504                 style: 'top: -100px; left: -100px;'
28505             });
28506             me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
28507             me.focusFrameWidth = 2;
28508             me.focusFrame.hide().setLeftTop(0, 0);
28509         }
28510     },
28511
28512     isWhitelisted: function(cmp) {
28513         return cmp && Ext.Array.some(this.whitelist, function(x) {
28514             return cmp.isXType(x);
28515         });
28516     },
28517
28518     navigateIn: function(e) {
28519         var me = this,
28520             focusedCmp = me.focusedCmp,
28521             rootCmps,
28522             firstChild;
28523
28524         if (!focusedCmp) {
28525             // No focus yet, so focus the first root cmp on the page
28526             rootCmps = me.getRootComponents();
28527             if (rootCmps.length) {
28528                 rootCmps[0].focus();
28529             }
28530         } else {
28531             // Drill into child ref items of the focused cmp, if applicable.
28532             // This works for any Component with a getRefItems implementation.
28533             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
28534             if (firstChild) {
28535                 firstChild.focus();
28536             } else {
28537                 // Let's try to fire a click event, as if it came from the mouse
28538                 if (Ext.isFunction(focusedCmp.onClick)) {
28539                     e.button = 0;
28540                     focusedCmp.onClick(e);
28541                     focusedCmp.focus();
28542                 }
28543             }
28544         }
28545     },
28546
28547     navigateOut: function(e) {
28548         var me = this,
28549             parent;
28550
28551         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
28552             me.focusEl.focus();
28553         } else {
28554             parent.focus();
28555         }
28556
28557         // In some browsers (Chrome) FocusManager can handle this before other
28558         // handlers. Ext Windows have their own Esc key handling, so we need to
28559         // return true here to allow the event to bubble.
28560         return true;
28561     },
28562
28563     navigateSiblings: function(e, source, parent) {
28564         var me = this,
28565             src = source || me,
28566             key = e.getKey(),
28567             EO = Ext.EventObject,
28568             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
28569             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
28570             nextSelector = goBack ? 'prev' : 'next',
28571             idx, next, focusedCmp;
28572
28573         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
28574         if (!focusedCmp && !parent) {
28575             return;
28576         }
28577
28578         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
28579             return true;
28580         }
28581
28582         parent = parent || focusedCmp.up();
28583         if (parent) {
28584             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
28585             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
28586             if (next && focusedCmp !== next) {
28587                 next.focus();
28588                 return next;
28589             }
28590         }
28591     },
28592
28593     onComponentBlur: function(cmp, e) {
28594         var me = this;
28595
28596         if (me.focusedCmp === cmp) {
28597             me.previousFocusedCmp = cmp;
28598             delete me.focusedCmp;
28599         }
28600
28601         if (me.focusFrame) {
28602             me.focusFrame.hide();
28603         }
28604     },
28605
28606     onComponentCreated: function(hash, id, cmp) {
28607         this.setFocus(cmp, true, this.options);
28608     },
28609
28610     onComponentDestroy: function(cmp) {
28611         this.setFocus(cmp, false);
28612     },
28613
28614     onComponentFocus: function(cmp, e) {
28615         var me = this,
28616             chain = me.focusChain;
28617
28618         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
28619             me.clearComponent(cmp);
28620
28621             // Check our focus chain, so we don't run into a never ending recursion
28622             // If we've attempted (unsuccessfully) to focus this component before,
28623             // then we're caught in a loop of child->parent->...->child and we
28624             // need to cut the loop off rather than feed into it.
28625             if (chain[cmp.id]) {
28626                 return;
28627             }
28628
28629             // Try to focus the parent instead
28630             var parent = cmp.up();
28631             if (parent) {
28632                 // Add component to our focus chain to detect infinite focus loop
28633                 // before we fire off an attempt to focus our parent.
28634                 // See the comments above.
28635                 chain[cmp.id] = true;
28636                 parent.focus();
28637             }
28638
28639             return;
28640         }
28641
28642         // Clear our focus chain when we have a focusable component
28643         me.focusChain = {};
28644
28645         // Defer focusing for 90ms so components can do a layout/positioning
28646         // and give us an ability to buffer focuses
28647         clearTimeout(me.cmpFocusDelay);
28648         if (arguments.length !== 2) {
28649             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
28650             return;
28651         }
28652
28653         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
28654             me.clearComponent(cmp);
28655             return;
28656         }
28657
28658         me.focusedCmp = cmp;
28659
28660         // If we have a focus frame, show it around the focused component
28661         if (me.shouldShowFocusFrame(cmp)) {
28662             var cls = '.' + me.focusFrameCls + '-',
28663                 ff = me.focusFrame,
28664                 fw = me.focusFrameWidth,
28665                 box = cmp.el.getPageBox(),
28666
28667             // Size the focus frame's t/b/l/r according to the box
28668             // This leaves a hole in the middle of the frame so user
28669             // interaction w/ the mouse can continue
28670                 bt = box.top,
28671                 bl = box.left,
28672                 bw = box.width,
28673                 bh = box.height,
28674                 ft = ff.child(cls + 'top'),
28675                 fb = ff.child(cls + 'bottom'),
28676                 fl = ff.child(cls + 'left'),
28677                 fr = ff.child(cls + 'right');
28678
28679             ft.setWidth(bw).setLeftTop(bl, bt);
28680             fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
28681             fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
28682             fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
28683
28684             ff.show();
28685         }
28686
28687         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
28688     },
28689
28690     onComponentHide: function(cmp) {
28691         var me = this,
28692             CQ = Ext.ComponentQuery,
28693             cmpHadFocus = false,
28694             focusedCmp,
28695             parent;
28696
28697         if (me.focusedCmp) {
28698             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
28699             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
28700
28701             if (focusedCmp) {
28702                 me.clearComponent(focusedCmp);
28703             }
28704         }
28705
28706         me.clearComponent(cmp);
28707
28708         if (cmpHadFocus) {
28709             parent = CQ.query('^:focusable', cmp)[0];
28710             if (parent) {
28711                 parent.focus();
28712             }
28713         }
28714     },
28715
28716     removeDOM: function() {
28717         var me = this;
28718
28719         // If we are still enabled globally, or there are still subscribers
28720         // then we will halt here, since our DOM stuff is still being used
28721         if (me.enabled || me.subscribers.length) {
28722             return;
28723         }
28724
28725         Ext.destroy(
28726             me.focusEl,
28727             me.focusFrame
28728         );
28729         delete me.focusEl;
28730         delete me.focusFrame;
28731         delete me.focusFrameWidth;
28732     },
28733
28734     /**
28735      * Removes the specified xtype from the {@link #whitelist}.
28736      * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
28737      */
28738     removeXTypeFromWhitelist: function(xtype) {
28739         var me = this;
28740
28741         if (Ext.isArray(xtype)) {
28742             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
28743             return;
28744         }
28745
28746         Ext.Array.remove(me.whitelist, xtype);
28747     },
28748
28749     setFocus: function(cmp, focusable, options) {
28750         var me = this,
28751             el, dom, data,
28752
28753             needsTabIndex = function(n) {
28754                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
28755                     && n.tabIndex <= 0;
28756             };
28757
28758         options = options || {};
28759
28760         // Come back and do this after the component is rendered
28761         if (!cmp.rendered) {
28762             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
28763             return;
28764         }
28765
28766         el = cmp.getFocusEl();
28767         dom = el.dom;
28768
28769         // Decorate the component's focus el for focus-ability
28770         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
28771             if (focusable) {
28772                 data = {
28773                     focusFrame: options.focusFrame
28774                 };
28775
28776                 // Only set -1 tabIndex if we need it
28777                 // inputs, buttons, and anchor tags do not need it,
28778                 // and neither does any DOM that has it set already
28779                 // programmatically or in markup.
28780                 if (needsTabIndex(dom)) {
28781                     data.tabIndex = dom.tabIndex;
28782                     dom.tabIndex = -1;
28783                 }
28784
28785                 el.on({
28786                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
28787                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
28788                     scope: me
28789                 });
28790                 cmp.on({
28791                     hide: me.onComponentHide,
28792                     close: me.onComponentHide,
28793                     beforedestroy: me.onComponentDestroy,
28794                     scope: me
28795                 });
28796
28797                 me.focusData[cmp.id] = data;
28798             } else {
28799                 data = me.focusData[cmp.id];
28800                 if ('tabIndex' in data) {
28801                     dom.tabIndex = data.tabIndex;
28802                 }
28803                 el.un('focus', data.focusFn, me);
28804                 el.un('blur', data.blurFn, me);
28805                 cmp.un('hide', me.onComponentHide, me);
28806                 cmp.un('close', me.onComponentHide, me);
28807                 cmp.un('beforedestroy', me.onComponentDestroy, me);
28808
28809                 delete me.focusData[cmp.id];
28810             }
28811         }
28812     },
28813
28814     setFocusAll: function(focusable, options) {
28815         var me = this,
28816             cmps = Ext.ComponentManager.all.getArray(),
28817             len = cmps.length,
28818             cmp,
28819             i = 0;
28820
28821         for (; i < len; i++) {
28822             me.setFocus(cmps[i], focusable, options);
28823         }
28824     },
28825
28826     setupSubscriberKeys: function(container, keys) {
28827         var me = this,
28828             el = container.getFocusEl(),
28829             scope = keys.scope,
28830             handlers = {
28831                 backspace: me.focusLast,
28832                 enter: me.navigateIn,
28833                 esc: me.navigateOut,
28834                 scope: me
28835             },
28836
28837             navSiblings = function(e) {
28838                 if (me.focusedCmp === container) {
28839                     // Root the sibling navigation to this container, so that we
28840                     // can automatically dive into the container, rather than forcing
28841                     // the user to hit the enter key to dive in.
28842                     return me.navigateSiblings(e, me, container);
28843                 } else {
28844                     return me.navigateSiblings(e);
28845                 }
28846             };
28847
28848         Ext.iterate(keys, function(key, cb) {
28849             handlers[key] = function(e) {
28850                 var ret = navSiblings(e);
28851
28852                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
28853                     return true;
28854                 }
28855
28856                 return ret;
28857             };
28858         }, me);
28859
28860         return Ext.create('Ext.util.KeyNav', el, handlers);
28861     },
28862
28863     shouldShowFocusFrame: function(cmp) {
28864         var me = this,
28865             opts = me.options || {};
28866
28867         if (!me.focusFrame || !cmp) {
28868             return false;
28869         }
28870
28871         // Global trumps
28872         if (opts.focusFrame) {
28873             return true;
28874         }
28875
28876         if (me.focusData[cmp.id].focusFrame) {
28877             return true;
28878         }
28879
28880         return false;
28881     },
28882
28883     /**
28884      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
28885      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
28886      * @param {Boolean/Object} options An object of the following options
28887      * @param {Array/Object} options.keys
28888      * An array containing the string names of navigation keys to be supported. The allowed values are:
28889      *
28890      *   - 'left'
28891      *   - 'right'
28892      *   - 'up'
28893      *   - 'down'
28894      *
28895      * 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.:
28896      *
28897      *     {
28898      *         left: this.onLeftKey,
28899      *         right: this.onRightKey,
28900      *         scope: this
28901      *     }
28902      *
28903      * @param {Boolean} options.focusFrame
28904      * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
28905      */
28906     subscribe: function(container, options) {
28907         var me = this,
28908             EA = Ext.Array,
28909             data = {},
28910             subs = me.subscribers,
28911
28912             // Recursively add focus ability as long as a descendent container isn't
28913             // itself subscribed to the FocusManager, or else we'd have unwanted side
28914             // effects for subscribing a descendent container twice.
28915             safeSetFocus = function(cmp) {
28916                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
28917                     EA.forEach(cmp.query('>'), safeSetFocus);
28918                     me.setFocus(cmp, true, options);
28919                     cmp.on('add', data.onAdd, me);
28920                 } else if (!cmp.isContainer) {
28921                     me.setFocus(cmp, true, options);
28922                 }
28923             };
28924
28925         // We only accept containers
28926         if (!container || !container.isContainer) {
28927             return;
28928         }
28929
28930         if (!container.rendered) {
28931             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
28932             return;
28933         }
28934
28935         // Init the DOM, incase this is the first time it will be used
28936         me.initDOM(options);
28937
28938         // Create key navigation for subscriber based on keys option
28939         data.keyNav = me.setupSubscriberKeys(container, options.keys);
28940
28941         // We need to keep track of components being added to our subscriber
28942         // and any containers nested deeply within it (omg), so let's do that.
28943         // Components that are removed are globally handled.
28944         // Also keep track of destruction of our container for auto-unsubscribe.
28945         data.onAdd = function(ct, cmp, idx) {
28946             safeSetFocus(cmp);
28947         };
28948         container.on('beforedestroy', me.unsubscribe, me);
28949
28950         // Now we setup focusing abilities for the container and all its components
28951         safeSetFocus(container);
28952
28953         // Add to our subscribers list
28954         subs.add(container.id, data);
28955     },
28956
28957     /**
28958      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
28959      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
28960      */
28961     unsubscribe: function(container) {
28962         var me = this,
28963             EA = Ext.Array,
28964             subs = me.subscribers,
28965             data,
28966
28967             // Recursively remove focus ability as long as a descendent container isn't
28968             // itself subscribed to the FocusManager, or else we'd have unwanted side
28969             // effects for unsubscribing an ancestor container.
28970             safeSetFocus = function(cmp) {
28971                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
28972                     EA.forEach(cmp.query('>'), safeSetFocus);
28973                     me.setFocus(cmp, false);
28974                     cmp.un('add', data.onAdd, me);
28975                 } else if (!cmp.isContainer) {
28976                     me.setFocus(cmp, false);
28977                 }
28978             };
28979
28980         if (!container || !subs.containsKey(container.id)) {
28981             return;
28982         }
28983
28984         data = subs.get(container.id);
28985         data.keyNav.destroy();
28986         container.un('beforedestroy', me.unsubscribe, me);
28987         subs.removeAtKey(container.id);
28988         safeSetFocus(container);
28989         me.removeDOM();
28990     }
28991 });
28992 /**
28993  * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
28994  * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
28995  * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
28996  *
28997  * ## Some items have shortcut strings for creation:
28998  *
28999  * | Shortcut | xtype         | Class                         | Description
29000  * |:---------|:--------------|:------------------------------|:---------------------------------------------------
29001  * | `->`     | `tbfill`      | {@link Ext.toolbar.Fill}      | begin using the right-justified button container
29002  * | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
29003  * | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements
29004  *
29005  *     @example
29006  *     Ext.create('Ext.toolbar.Toolbar', {
29007  *         renderTo: document.body,
29008  *         width   : 500,
29009  *         items: [
29010  *             {
29011  *                 // xtype: 'button', // default for Toolbars
29012  *                 text: 'Button'
29013  *             },
29014  *             {
29015  *                 xtype: 'splitbutton',
29016  *                 text : 'Split Button'
29017  *             },
29018  *             // begin using the right-justified button container
29019  *             '->', // same as { xtype: 'tbfill' }
29020  *             {
29021  *                 xtype    : 'textfield',
29022  *                 name     : 'field1',
29023  *                 emptyText: 'enter search term'
29024  *             },
29025  *             // add a vertical separator bar between toolbar items
29026  *             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
29027  *             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
29028  *             { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
29029  *             'text 2',
29030  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
29031  *             'text 3'
29032  *         ]
29033  *     });
29034  *
29035  * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
29036  *
29037  *     @example
29038  *     Ext.create('Ext.toolbar.Toolbar', {
29039  *         renderTo: document.body,
29040  *         width   : 400,
29041  *         items: [
29042  *             {
29043  *                 text: 'Button'
29044  *             },
29045  *             {
29046  *                 xtype: 'splitbutton',
29047  *                 text : 'Split Button'
29048  *             },
29049  *             '->',
29050  *             {
29051  *                 xtype    : 'textfield',
29052  *                 name     : 'field1',
29053  *                 emptyText: 'enter search term'
29054  *             }
29055  *         ]
29056  *     });
29057  *
29058  * Example
29059  *
29060  *     @example
29061  *     var enableBtn = Ext.create('Ext.button.Button', {
29062  *         text    : 'Enable All Items',
29063  *         disabled: true,
29064  *         scope   : this,
29065  *         handler : function() {
29066  *             //disable the enable button and enable the disable button
29067  *             enableBtn.disable();
29068  *             disableBtn.enable();
29069  *
29070  *             //enable the toolbar
29071  *             toolbar.enable();
29072  *         }
29073  *     });
29074  *
29075  *     var disableBtn = Ext.create('Ext.button.Button', {
29076  *         text    : 'Disable All Items',
29077  *         scope   : this,
29078  *         handler : function() {
29079  *             //enable the enable button and disable button
29080  *             disableBtn.disable();
29081  *             enableBtn.enable();
29082  *
29083  *             //disable the toolbar
29084  *             toolbar.disable();
29085  *         }
29086  *     });
29087  *
29088  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
29089  *         renderTo: document.body,
29090  *         width   : 400,
29091  *         margin  : '5 0 0 0',
29092  *         items   : [enableBtn, disableBtn]
29093  *     });
29094  *
29095  * 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
29096  * which remove all items within the toolbar.
29097  *
29098  *     @example
29099  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
29100  *         renderTo: document.body,
29101  *         width   : 700,
29102  *         items: [
29103  *             {
29104  *                 text: 'Example Button'
29105  *             }
29106  *         ]
29107  *     });
29108  *
29109  *     var addedItems = [];
29110  *
29111  *     Ext.create('Ext.toolbar.Toolbar', {
29112  *         renderTo: document.body,
29113  *         width   : 700,
29114  *         margin  : '5 0 0 0',
29115  *         items   : [
29116  *             {
29117  *                 text   : 'Add a button',
29118  *                 scope  : this,
29119  *                 handler: function() {
29120  *                     var text = prompt('Please enter the text for your button:');
29121  *                     addedItems.push(toolbar.add({
29122  *                         text: text
29123  *                     }));
29124  *                 }
29125  *             },
29126  *             {
29127  *                 text   : 'Add a text item',
29128  *                 scope  : this,
29129  *                 handler: function() {
29130  *                     var text = prompt('Please enter the text for your item:');
29131  *                     addedItems.push(toolbar.add(text));
29132  *                 }
29133  *             },
29134  *             {
29135  *                 text   : 'Add a toolbar seperator',
29136  *                 scope  : this,
29137  *                 handler: function() {
29138  *                     addedItems.push(toolbar.add('-'));
29139  *                 }
29140  *             },
29141  *             {
29142  *                 text   : 'Add a toolbar spacer',
29143  *                 scope  : this,
29144  *                 handler: function() {
29145  *                     addedItems.push(toolbar.add('->'));
29146  *                 }
29147  *             },
29148  *             '->',
29149  *             {
29150  *                 text   : 'Remove last inserted item',
29151  *                 scope  : this,
29152  *                 handler: function() {
29153  *                     if (addedItems.length) {
29154  *                         toolbar.remove(addedItems.pop());
29155  *                     } else if (toolbar.items.length) {
29156  *                         toolbar.remove(toolbar.items.last());
29157  *                     } else {
29158  *                         alert('No items in the toolbar');
29159  *                     }
29160  *                 }
29161  *             },
29162  *             {
29163  *                 text   : 'Remove all items',
29164  *                 scope  : this,
29165  *                 handler: function() {
29166  *                     toolbar.removeAll();
29167  *                 }
29168  *             }
29169  *         ]
29170  *     });
29171  *
29172  * @constructor
29173  * Creates a new Toolbar
29174  * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
29175  * @docauthor Robert Dougan <rob@sencha.com>
29176  */
29177 Ext.define('Ext.toolbar.Toolbar', {
29178     extend: 'Ext.container.Container',
29179     requires: [
29180         'Ext.toolbar.Fill',
29181         'Ext.layout.container.HBox',
29182         'Ext.layout.container.VBox',
29183         'Ext.FocusManager'
29184     ],
29185     uses: [
29186         'Ext.toolbar.Separator'
29187     ],
29188     alias: 'widget.toolbar',
29189     alternateClassName: 'Ext.Toolbar',
29190
29191     isToolbar: true,
29192     baseCls  : Ext.baseCSSPrefix + 'toolbar',
29193     ariaRole : 'toolbar',
29194
29195     defaultType: 'button',
29196
29197     /**
29198      * @cfg {Boolean} vertical
29199      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
29200      */
29201     vertical: false,
29202
29203     /**
29204      * @cfg {String/Object} layout
29205      * This class assigns a default layout (`layout: 'hbox'`).
29206      * Developers _may_ override this configuration option if another layout
29207      * is required (the constructor must be passed a configuration object in this
29208      * case instead of an array).
29209      * See {@link Ext.container.Container#layout} for additional information.
29210      */
29211
29212     /**
29213      * @cfg {Boolean} enableOverflow
29214      * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
29215      * items which overflow the Toolbar's width.
29216      */
29217     enableOverflow: false,
29218
29219     /**
29220      * @cfg {String} menuTriggerCls
29221      * Configure the icon class of the overflow button.
29222      */
29223     menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
29224     
29225     // private
29226     trackMenus: true,
29227
29228     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
29229
29230     initComponent: function() {
29231         var me = this,
29232             keys;
29233
29234         // check for simplified (old-style) overflow config:
29235         if (!me.layout && me.enableOverflow) {
29236             me.layout = { overflowHandler: 'Menu' };
29237         }
29238
29239         if (me.dock === 'right' || me.dock === 'left') {
29240             me.vertical = true;
29241         }
29242
29243         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
29244             type: me.layout
29245         } : me.layout || {}, {
29246             type: me.vertical ? 'vbox' : 'hbox',
29247             align: me.vertical ? 'stretchmax' : 'middle',
29248             clearInnerCtOnLayout: true
29249         });
29250
29251         if (me.vertical) {
29252             me.addClsWithUI('vertical');
29253         }
29254
29255         // @TODO: remove this hack and implement a more general solution
29256         if (me.ui === 'footer') {
29257             me.ignoreBorderManagement = true;
29258         }
29259
29260         me.callParent();
29261
29262         /**
29263          * @event overflowchange
29264          * Fires after the overflow state has changed.
29265          * @param {Object} c The Container
29266          * @param {Boolean} lastOverflow overflow state
29267          */
29268         me.addEvents('overflowchange');
29269
29270         // Subscribe to Ext.FocusManager for key navigation
29271         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
29272         Ext.FocusManager.subscribe(me, {
29273             keys: keys
29274         });
29275     },
29276
29277     getRefItems: function(deep) {
29278         var me = this,
29279             items = me.callParent(arguments),
29280             layout = me.layout,
29281             handler;
29282
29283         if (deep && me.enableOverflow) {
29284             handler = layout.overflowHandler;
29285             if (handler && handler.menu) {
29286                 items = items.concat(handler.menu.getRefItems(deep));
29287             }
29288         }
29289         return items;
29290     },
29291
29292     /**
29293      * Adds element(s) to the toolbar -- this function takes a variable number of
29294      * arguments of mixed type and adds them to the toolbar.
29295      *
29296      * **Note**: See the notes within {@link Ext.container.Container#add}.
29297      *
29298      * @param {Object...} args The following types of arguments are all valid:
29299      *  - `{@link Ext.button.Button config}`: A valid button config object
29300      *  - `HtmlElement`: Any standard HTML element
29301      *  - `Field`: Any form field
29302      *  - `Item`: Any subclass of {@link Ext.toolbar.Item}
29303      *  - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
29304      *  Note that there are a few special strings that are treated differently as explained next.
29305      *  - `'-'`: Creates a separator element
29306      *  - `' '`: Creates a spacer element
29307      *  - `'->'`: Creates a fill element
29308      *
29309      * @method add
29310      */
29311
29312     // private
29313     lookupComponent: function(c) {
29314         if (Ext.isString(c)) {
29315             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
29316             if (shortcut) {
29317                 c = {
29318                     xtype: shortcut
29319                 };
29320             } else {
29321                 c = {
29322                     xtype: 'tbtext',
29323                     text: c
29324                 };
29325             }
29326             this.applyDefaults(c);
29327         }
29328         return this.callParent(arguments);
29329     },
29330
29331     // private
29332     applyDefaults: function(c) {
29333         if (!Ext.isString(c)) {
29334             c = this.callParent(arguments);
29335             var d = this.internalDefaults;
29336             if (c.events) {
29337                 Ext.applyIf(c.initialConfig, d);
29338                 Ext.apply(c, d);
29339             } else {
29340                 Ext.applyIf(c, d);
29341             }
29342         }
29343         return c;
29344     },
29345
29346     // private
29347     trackMenu: function(item, remove) {
29348         if (this.trackMenus && item.menu) {
29349             var method = remove ? 'mun' : 'mon',
29350                 me = this;
29351
29352             me[method](item, 'mouseover', me.onButtonOver, me);
29353             me[method](item, 'menushow', me.onButtonMenuShow, me);
29354             me[method](item, 'menuhide', me.onButtonMenuHide, me);
29355         }
29356     },
29357
29358     // private
29359     constructButton: function(item) {
29360         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
29361     },
29362
29363     // private
29364     onBeforeAdd: function(component) {
29365         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
29366             component.ui = component.ui + '-toolbar';
29367         }
29368
29369         // Any separators needs to know if is vertical or not
29370         if (component instanceof Ext.toolbar.Separator) {
29371             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
29372         }
29373
29374         this.callParent(arguments);
29375     },
29376
29377     // private
29378     onAdd: function(component) {
29379         this.callParent(arguments);
29380
29381         this.trackMenu(component);
29382         if (this.disabled) {
29383             component.disable();
29384         }
29385     },
29386
29387     // private
29388     onRemove: function(c) {
29389         this.callParent(arguments);
29390         this.trackMenu(c, true);
29391     },
29392
29393     // private
29394     onButtonOver: function(btn){
29395         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
29396             this.activeMenuBtn.hideMenu();
29397             btn.showMenu();
29398             this.activeMenuBtn = btn;
29399         }
29400     },
29401
29402     // private
29403     onButtonMenuShow: function(btn) {
29404         this.activeMenuBtn = btn;
29405     },
29406
29407     // private
29408     onButtonMenuHide: function(btn) {
29409         delete this.activeMenuBtn;
29410     }
29411 }, function() {
29412     this.shortcuts = {
29413         '-' : 'tbseparator',
29414         ' ' : 'tbspacer',
29415         '->': 'tbfill'
29416     };
29417 });
29418 /**
29419  * @class Ext.panel.AbstractPanel
29420  * @extends Ext.container.Container
29421  * A base class which provides methods common to Panel classes across the Sencha product range.
29422  * @private
29423  */
29424 Ext.define('Ext.panel.AbstractPanel', {
29425
29426     /* Begin Definitions */
29427
29428     extend: 'Ext.container.Container',
29429
29430     requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
29431
29432     /* End Definitions */
29433
29434     /**
29435      * @cfg {String} [baseCls='x-panel']
29436      * The base CSS class to apply to this panel's element.
29437      */
29438     baseCls : Ext.baseCSSPrefix + 'panel',
29439
29440     /**
29441      * @cfg {Number/String} bodyPadding
29442      * A shortcut for setting a padding style on the body element. The value can either be
29443      * a number to be applied to all sides, or a normal css string describing padding.
29444      */
29445
29446     /**
29447      * @cfg {Boolean} bodyBorder
29448      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
29449      * which has the {@link #frame} configuration set to `true`.
29450      */
29451
29452     /**
29453      * @cfg {String/Object/Function} bodyStyle
29454      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
29455      * an object containing style property name/value pairs or a function that returns such a string or object.
29456      * For example, these two formats are interpreted to be equivalent:<pre><code>
29457 bodyStyle: 'background:#ffc; padding:10px;'
29458
29459 bodyStyle: {
29460     background: '#ffc',
29461     padding: '10px'
29462 }
29463      * </code></pre>
29464      */
29465
29466     /**
29467      * @cfg {String/String[]} bodyCls
29468      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
29469      * The following examples are all valid:<pre><code>
29470 bodyCls: 'foo'
29471 bodyCls: 'foo bar'
29472 bodyCls: ['foo', 'bar']
29473      * </code></pre>
29474      */
29475
29476     isPanel: true,
29477
29478     componentLayout: 'dock',
29479
29480     /**
29481      * @cfg {Object} defaultDockWeights
29482      * This object holds the default weights applied to dockedItems that have no weight. These start with a
29483      * weight of 1, to allow negative weights to insert before top items and are odd numbers
29484      * so that even weights can be used to get between different dock orders.
29485      *
29486      * To make default docking order match border layout, do this:
29487      * <pre><code>
29488 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
29489      * Changing these defaults as above or individually on this object will effect all Panels.
29490      * To change the defaults on a single panel, you should replace the entire object:
29491      * <pre><code>
29492 initComponent: function () {
29493     // NOTE: Don't change members of defaultDockWeights since the object is shared.
29494     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
29495
29496     this.callParent();
29497 }</code></pre>
29498      *
29499      * To change only one of the default values, you do this:
29500      * <pre><code>
29501 initComponent: function () {
29502     // NOTE: Don't change members of defaultDockWeights since the object is shared.
29503     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
29504
29505     this.callParent();
29506 }</code></pre>
29507      */
29508     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
29509
29510     renderTpl: [
29511         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
29512             ' {baseCls}-body-{ui}<tpl if="uiCls">',
29513                 '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
29514             '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
29515         '</div>'
29516     ],
29517
29518     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
29519     /**
29520      * @cfg {Object/Object[]} dockedItems
29521      * A component or series of components to be added as docked items to this panel.
29522      * The docked items can be docked to either the top, right, left or bottom of a panel.
29523      * This is typically used for things like toolbars or tab bars:
29524      * <pre><code>
29525 var panel = new Ext.panel.Panel({
29526     fullscreen: true,
29527     dockedItems: [{
29528         xtype: 'toolbar',
29529         dock: 'top',
29530         items: [{
29531             text: 'Docked to the top'
29532         }]
29533     }]
29534 });</code></pre>
29535      */
29536
29537     border: true,
29538
29539     initComponent : function() {
29540         var me = this;
29541
29542         me.addEvents(
29543             /**
29544              * @event bodyresize
29545              * Fires after the Panel has been resized.
29546              * @param {Ext.panel.Panel} p the Panel which has been resized.
29547              * @param {Number} width The Panel body's new width.
29548              * @param {Number} height The Panel body's new height.
29549              */
29550             'bodyresize'
29551             // // inherited
29552             // 'activate',
29553             // // inherited
29554             // 'deactivate'
29555         );
29556
29557         me.addChildEls('body');
29558
29559         //!frame
29560         //!border
29561
29562         if (me.frame && me.border && me.bodyBorder === undefined) {
29563             me.bodyBorder = false;
29564         }
29565         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
29566             me.manageBodyBorders = true;
29567         }
29568
29569         me.callParent();
29570     },
29571
29572     // @private
29573     initItems : function() {
29574         var me = this,
29575             items = me.dockedItems;
29576
29577         me.callParent();
29578         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
29579         if (items) {
29580             me.addDocked(items);
29581         }
29582     },
29583
29584     /**
29585      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
29586      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
29587      * @return {Ext.Component} The docked component (if found)
29588      */
29589     getDockedComponent: function(comp) {
29590         if (Ext.isObject(comp)) {
29591             comp = comp.getItemId();
29592         }
29593         return this.dockedItems.get(comp);
29594     },
29595
29596     /**
29597      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
29598      * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
29599      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
29600      * @param {String/Number} comp The component id, itemId or position to find
29601      * @return {Ext.Component} The component (if found)
29602      */
29603     getComponent: function(comp) {
29604         var component = this.callParent(arguments);
29605         if (component === undefined && !Ext.isNumber(comp)) {
29606             // If the arg is a numeric index skip docked items
29607             component = this.getDockedComponent(comp);
29608         }
29609         return component;
29610     },
29611
29612     /**
29613      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
29614      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
29615      * @return {String} A CSS style string with body styles, padding and border.
29616      * @private
29617      */
29618     initBodyStyles: function() {
29619         var me = this,
29620             bodyStyle = me.bodyStyle,
29621             styles = [],
29622             Element = Ext.Element,
29623             prop;
29624
29625         if (Ext.isFunction(bodyStyle)) {
29626             bodyStyle = bodyStyle();
29627         }
29628         if (Ext.isString(bodyStyle)) {
29629             styles = bodyStyle.split(';');
29630         } else {
29631             for (prop in bodyStyle) {
29632                 if (bodyStyle.hasOwnProperty(prop)) {
29633                     styles.push(prop + ':' + bodyStyle[prop]);
29634                 }
29635             }
29636         }
29637
29638         if (me.bodyPadding !== undefined) {
29639             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
29640         }
29641         if (me.frame && me.bodyBorder) {
29642             if (!Ext.isNumber(me.bodyBorder)) {
29643                 me.bodyBorder = 1;
29644             }
29645             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
29646         }
29647         delete me.bodyStyle;
29648         return styles.length ? styles.join(';') : undefined;
29649     },
29650
29651     /**
29652      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
29653      * CSS classes to be applied to the body element.
29654      * @return {String} The CSS class(es)
29655      * @private
29656      */
29657     initBodyCls: function() {
29658         var me = this,
29659             cls = '',
29660             bodyCls = me.bodyCls;
29661
29662         if (bodyCls) {
29663             Ext.each(bodyCls, function(v) {
29664                 cls += " " + v;
29665             });
29666             delete me.bodyCls;
29667         }
29668         return cls.length > 0 ? cls : undefined;
29669     },
29670
29671     /**
29672      * Initialized the renderData to be used when rendering the renderTpl.
29673      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
29674      * @private
29675      */
29676     initRenderData: function() {
29677         return Ext.applyIf(this.callParent(), {
29678             bodyStyle: this.initBodyStyles(),
29679             bodyCls: this.initBodyCls()
29680         });
29681     },
29682
29683     /**
29684      * Adds docked item(s) to the panel.
29685      * @param {Object/Object[]} component The Component or array of components to add. The components
29686      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
29687      * 'bottom', 'left').
29688      * @param {Number} pos (optional) The index at which the Component will be added
29689      */
29690     addDocked : function(items, pos) {
29691         var me = this,
29692             i = 0,
29693             item, length;
29694
29695         items = me.prepareItems(items);
29696         length = items.length;
29697
29698         for (; i < length; i++) {
29699             item = items[i];
29700             item.dock = item.dock || 'top';
29701
29702             // Allow older browsers to target docked items to style without borders
29703             if (me.border === false) {
29704                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
29705             }
29706
29707             if (pos !== undefined) {
29708                 me.dockedItems.insert(pos + i, item);
29709             }
29710             else {
29711                 me.dockedItems.add(item);
29712             }
29713             item.onAdded(me, i);
29714             me.onDockedAdd(item);
29715         }
29716
29717         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
29718         me.componentLayout.childrenChanged = true;
29719         if (me.rendered && !me.suspendLayout) {
29720             me.doComponentLayout();
29721         }
29722         return items;
29723     },
29724
29725     // Placeholder empty functions
29726     onDockedAdd : Ext.emptyFn,
29727     onDockedRemove : Ext.emptyFn,
29728
29729     /**
29730      * Inserts docked item(s) to the panel at the indicated position.
29731      * @param {Number} pos The index at which the Component will be inserted
29732      * @param {Object/Object[]} component. The Component or array of components to add. The components
29733      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
29734      * 'bottom', 'left').
29735      */
29736     insertDocked : function(pos, items) {
29737         this.addDocked(items, pos);
29738     },
29739
29740     /**
29741      * Removes the docked item from the panel.
29742      * @param {Ext.Component} item. The Component to remove.
29743      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
29744      */
29745     removeDocked : function(item, autoDestroy) {
29746         var me = this,
29747             layout,
29748             hasLayout;
29749
29750         if (!me.dockedItems.contains(item)) {
29751             return item;
29752         }
29753
29754         layout = me.componentLayout;
29755         hasLayout = layout && me.rendered;
29756
29757         if (hasLayout) {
29758             layout.onRemove(item);
29759         }
29760
29761         me.dockedItems.remove(item);
29762         item.onRemoved();
29763         me.onDockedRemove(item);
29764
29765         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
29766             item.destroy();
29767         } else if (hasLayout) {
29768             // not destroying, make any layout related removals
29769             layout.afterRemove(item);    
29770         }
29771
29772
29773         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
29774         me.componentLayout.childrenChanged = true;
29775         if (!me.destroying && !me.suspendLayout) {
29776             me.doComponentLayout();
29777         }
29778
29779         return item;
29780     },
29781
29782     /**
29783      * Retrieve an array of all currently docked Components.
29784      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
29785      * @return {Ext.Component[]} An array of components.
29786      */
29787     getDockedItems : function(cqSelector) {
29788         var me = this,
29789             defaultWeight = me.defaultDockWeights,
29790             dockedItems;
29791
29792         if (me.dockedItems && me.dockedItems.items.length) {
29793             // Allow filtering of returned docked items by CQ selector.
29794             if (cqSelector) {
29795                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
29796             } else {
29797                 dockedItems = me.dockedItems.items.slice();
29798             }
29799
29800             Ext.Array.sort(dockedItems, function(a, b) {
29801                 // Docked items are ordered by their visual representation by default (t,l,r,b)
29802                 var aw = a.weight || defaultWeight[a.dock],
29803                     bw = b.weight || defaultWeight[b.dock];
29804                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
29805                     return aw - bw;
29806                 }
29807                 return 0;
29808             });
29809
29810             return dockedItems;
29811         }
29812         return [];
29813     },
29814
29815     // inherit docs
29816     addUIClsToElement: function(cls, force) {
29817         var me = this,
29818             result = me.callParent(arguments),
29819             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
29820             array, i;
29821
29822         if (!force && me.rendered) {
29823             if (me.bodyCls) {
29824                 me.body.addCls(me.bodyCls);
29825             } else {
29826                 me.body.addCls(classes);
29827             }
29828         } else {
29829             if (me.bodyCls) {
29830                 array = me.bodyCls.split(' ');
29831
29832                 for (i = 0; i < classes.length; i++) {
29833                     if (!Ext.Array.contains(array, classes[i])) {
29834                         array.push(classes[i]);
29835                     }
29836                 }
29837
29838                 me.bodyCls = array.join(' ');
29839             } else {
29840                 me.bodyCls = classes.join(' ');
29841             }
29842         }
29843
29844         return result;
29845     },
29846
29847     // inherit docs
29848     removeUIClsFromElement: function(cls, force) {
29849         var me = this,
29850             result = me.callParent(arguments),
29851             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
29852             array, i;
29853
29854         if (!force && me.rendered) {
29855             if (me.bodyCls) {
29856                 me.body.removeCls(me.bodyCls);
29857             } else {
29858                 me.body.removeCls(classes);
29859             }
29860         } else {
29861             if (me.bodyCls) {
29862                 array = me.bodyCls.split(' ');
29863
29864                 for (i = 0; i < classes.length; i++) {
29865                     Ext.Array.remove(array, classes[i]);
29866                 }
29867
29868                 me.bodyCls = array.join(' ');
29869             }
29870         }
29871
29872         return result;
29873     },
29874
29875     // inherit docs
29876     addUIToElement: function(force) {
29877         var me = this,
29878             cls = me.baseCls + '-body-' + me.ui,
29879             array;
29880
29881         me.callParent(arguments);
29882
29883         if (!force && me.rendered) {
29884             if (me.bodyCls) {
29885                 me.body.addCls(me.bodyCls);
29886             } else {
29887                 me.body.addCls(cls);
29888             }
29889         } else {
29890             if (me.bodyCls) {
29891                 array = me.bodyCls.split(' ');
29892
29893                 if (!Ext.Array.contains(array, cls)) {
29894                     array.push(cls);
29895                 }
29896
29897                 me.bodyCls = array.join(' ');
29898             } else {
29899                 me.bodyCls = cls;
29900             }
29901         }
29902     },
29903
29904     // inherit docs
29905     removeUIFromElement: function() {
29906         var me = this,
29907             cls = me.baseCls + '-body-' + me.ui,
29908             array;
29909
29910         me.callParent(arguments);
29911
29912         if (me.rendered) {
29913             if (me.bodyCls) {
29914                 me.body.removeCls(me.bodyCls);
29915             } else {
29916                 me.body.removeCls(cls);
29917             }
29918         } else {
29919             if (me.bodyCls) {
29920                 array = me.bodyCls.split(' ');
29921                 Ext.Array.remove(array, cls);
29922                 me.bodyCls = array.join(' ');
29923             } else {
29924                 me.bodyCls = cls;
29925             }
29926         }
29927     },
29928
29929     // @private
29930     getTargetEl : function() {
29931         return this.body;
29932     },
29933
29934     getRefItems: function(deep) {
29935         var items = this.callParent(arguments),
29936             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
29937             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
29938             ln = dockedItems.length,
29939             i = 0,
29940             item;
29941
29942         // Find the index where we go from top/left docked items to right/bottom docked items
29943         for (; i < ln; i++) {
29944             item = dockedItems[i];
29945             if (item.dock === 'right' || item.dock === 'bottom') {
29946                 break;
29947             }
29948         }
29949
29950         // Return docked items in the top/left position before our container items, and
29951         // return right/bottom positioned items after our container items.
29952         // See AbstractDock.renderItems() for more information.
29953         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
29954     },
29955
29956     beforeDestroy: function(){
29957         var docked = this.dockedItems,
29958             c;
29959
29960         if (docked) {
29961             while ((c = docked.first())) {
29962                 this.removeDocked(c, true);
29963             }
29964         }
29965         this.callParent();
29966     },
29967
29968     setBorder: function(border) {
29969         var me = this;
29970         me.border = (border !== undefined) ? border : true;
29971         if (me.rendered) {
29972             me.doComponentLayout();
29973         }
29974     }
29975 });
29976 /**
29977  * @class Ext.panel.Header
29978  * @extends Ext.container.Container
29979  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
29980  */
29981 Ext.define('Ext.panel.Header', {
29982     extend: 'Ext.container.Container',
29983     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
29984     alias: 'widget.header',
29985
29986     isHeader       : true,
29987     defaultType    : 'tool',
29988     indicateDrag   : false,
29989     weight         : -1,
29990
29991     renderTpl: [
29992         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
29993         '<tpl if="uiCls">',
29994             '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
29995         '</tpl>"',
29996         '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
29997
29998     /**
29999      * @cfg {String} title
30000      * The title text to display
30001      */
30002
30003     /**
30004      * @cfg {String} iconCls
30005      * CSS class for icon in header. Used for displaying an icon to the left of a title.
30006      */
30007
30008     initComponent: function() {
30009         var me = this,
30010             ruleStyle,
30011             rule,
30012             style,
30013             titleTextEl,
30014             ui;
30015
30016         me.indicateDragCls = me.baseCls + '-draggable';
30017         me.title = me.title || '&#160;';
30018         me.tools = me.tools || [];
30019         me.items = me.items || [];
30020         me.orientation = me.orientation || 'horizontal';
30021         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
30022
30023         //add the dock as a ui
30024         //this is so we support top/right/left/bottom headers
30025         me.addClsWithUI(me.orientation);
30026         me.addClsWithUI(me.dock);
30027
30028         me.addChildEls('body');
30029
30030         // Add Icon
30031         if (!Ext.isEmpty(me.iconCls)) {
30032             me.initIconCmp();
30033             me.items.push(me.iconCmp);
30034         }
30035
30036         // Add Title
30037         if (me.orientation == 'vertical') {
30038             // Hack for IE6/7's inability to display an inline-block
30039             if (Ext.isIE6 || Ext.isIE7) {
30040                 me.width = this.width || 24;
30041             } else if (Ext.isIEQuirks) {
30042                 me.width = this.width || 25;
30043             }
30044
30045             me.layout = {
30046                 type : 'vbox',
30047                 align: 'center',
30048                 clearInnerCtOnLayout: true,
30049                 bindToOwnerCtContainer: false
30050             };
30051             me.textConfig = {
30052                 cls: me.baseCls + '-text',
30053                 type: 'text',
30054                 text: me.title,
30055                 rotate: {
30056                     degrees: 90
30057                 }
30058             };
30059             ui = me.ui;
30060             if (Ext.isArray(ui)) {
30061                 ui = ui[0];
30062             }
30063             ruleStyle = '.' + me.baseCls + '-text-' + ui;
30064             if (Ext.scopeResetCSS) {
30065                 ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
30066             }
30067             rule = Ext.util.CSS.getRule(ruleStyle);
30068             if (rule) {
30069                 style = rule.style;
30070             }
30071             if (style) {
30072                 Ext.apply(me.textConfig, {
30073                     'font-family': style.fontFamily,
30074                     'font-weight': style.fontWeight,
30075                     'font-size': style.fontSize,
30076                     fill: style.color
30077                 });
30078             }
30079             me.titleCmp = Ext.create('Ext.draw.Component', {
30080                 ariaRole  : 'heading',
30081                 focusable: false,
30082                 viewBox: false,
30083                 flex : 1,
30084                 autoSize: true,
30085                 margins: '5 0 0 0',
30086                 items: [ me.textConfig ],
30087                 // this is a bit of a cheat: we are not selecting an element of titleCmp
30088                 // but rather of titleCmp.items[0] (so we cannot use childEls)
30089                 renderSelectors: {
30090                     textEl: '.' + me.baseCls + '-text'
30091                 }
30092             });
30093         } else {
30094             me.layout = {
30095                 type : 'hbox',
30096                 align: 'middle',
30097                 clearInnerCtOnLayout: true,
30098                 bindToOwnerCtContainer: false
30099             };
30100             me.titleCmp = Ext.create('Ext.Component', {
30101                 xtype     : 'component',
30102                 ariaRole  : 'heading',
30103                 focusable: false,
30104                 flex : 1,
30105                 cls: me.baseCls + '-text-container',
30106                 renderTpl : [
30107                     '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>'
30108                 ],
30109                 renderData: {
30110                     title: me.title,
30111                     cls  : me.baseCls,
30112                     ui   : me.ui
30113                 },
30114                 childEls: ['textEl']
30115             });
30116         }
30117         me.items.push(me.titleCmp);
30118
30119         // Add Tools
30120         me.items = me.items.concat(me.tools);
30121         this.callParent();
30122     },
30123
30124     initIconCmp: function() {
30125         this.iconCmp = Ext.create('Ext.Component', {
30126             focusable: false,
30127             renderTpl : [
30128                 '<img id="{id}-iconEl" alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'
30129             ],
30130             renderData: {
30131                 blank  : Ext.BLANK_IMAGE_URL,
30132                 cls    : this.baseCls,
30133                 iconCls: this.iconCls,
30134                 orientation: this.orientation
30135             },
30136             childEls: ['iconEl'],
30137             iconCls: this.iconCls
30138         });
30139     },
30140
30141     afterRender: function() {
30142         var me = this;
30143
30144         me.el.unselectable();
30145         if (me.indicateDrag) {
30146             me.el.addCls(me.indicateDragCls);
30147         }
30148         me.mon(me.el, {
30149             click: me.onClick,
30150             scope: me
30151         });
30152         me.callParent();
30153     },
30154
30155     afterLayout: function() {
30156         var me = this;
30157         me.callParent(arguments);
30158
30159         // IE7 needs a forced repaint to make the top framing div expand to full width
30160         if (Ext.isIE7) {
30161             me.el.repaint();
30162         }
30163     },
30164
30165     // inherit docs
30166     addUIClsToElement: function(cls, force) {
30167         var me = this,
30168             result = me.callParent(arguments),
30169             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
30170             array, i;
30171
30172         if (!force && me.rendered) {
30173             if (me.bodyCls) {
30174                 me.body.addCls(me.bodyCls);
30175             } else {
30176                 me.body.addCls(classes);
30177             }
30178         } else {
30179             if (me.bodyCls) {
30180                 array = me.bodyCls.split(' ');
30181
30182                 for (i = 0; i < classes.length; i++) {
30183                     if (!Ext.Array.contains(array, classes[i])) {
30184                         array.push(classes[i]);
30185                     }
30186                 }
30187
30188                 me.bodyCls = array.join(' ');
30189             } else {
30190                 me.bodyCls = classes.join(' ');
30191             }
30192         }
30193
30194         return result;
30195     },
30196
30197     // inherit docs
30198     removeUIClsFromElement: function(cls, force) {
30199         var me = this,
30200             result = me.callParent(arguments),
30201             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
30202             array, i;
30203
30204         if (!force && me.rendered) {
30205             if (me.bodyCls) {
30206                 me.body.removeCls(me.bodyCls);
30207             } else {
30208                 me.body.removeCls(classes);
30209             }
30210         } else {
30211             if (me.bodyCls) {
30212                 array = me.bodyCls.split(' ');
30213
30214                 for (i = 0; i < classes.length; i++) {
30215                     Ext.Array.remove(array, classes[i]);
30216                 }
30217
30218                 me.bodyCls = array.join(' ');
30219             }
30220         }
30221
30222        return result;
30223     },
30224
30225     // inherit docs
30226     addUIToElement: function(force) {
30227         var me = this,
30228             array, cls;
30229
30230         me.callParent(arguments);
30231
30232         cls = me.baseCls + '-body-' + me.ui;
30233         if (!force && me.rendered) {
30234             if (me.bodyCls) {
30235                 me.body.addCls(me.bodyCls);
30236             } else {
30237                 me.body.addCls(cls);
30238             }
30239         } else {
30240             if (me.bodyCls) {
30241                 array = me.bodyCls.split(' ');
30242
30243                 if (!Ext.Array.contains(array, cls)) {
30244                     array.push(cls);
30245                 }
30246
30247                 me.bodyCls = array.join(' ');
30248             } else {
30249                 me.bodyCls = cls;
30250             }
30251         }
30252
30253         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
30254             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
30255         }
30256     },
30257
30258     // inherit docs
30259     removeUIFromElement: function() {
30260         var me = this,
30261             array, cls;
30262
30263         me.callParent(arguments);
30264
30265         cls = me.baseCls + '-body-' + me.ui;
30266         if (me.rendered) {
30267             if (me.bodyCls) {
30268                 me.body.removeCls(me.bodyCls);
30269             } else {
30270                 me.body.removeCls(cls);
30271             }
30272         } else {
30273             if (me.bodyCls) {
30274                 array = me.bodyCls.split(' ');
30275                 Ext.Array.remove(array, cls);
30276                 me.bodyCls = array.join(' ');
30277             } else {
30278                 me.bodyCls = cls;
30279             }
30280         }
30281
30282         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
30283             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
30284         }
30285     },
30286
30287     onClick: function(e) {
30288         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
30289             this.fireEvent('click', e);
30290         }
30291     },
30292
30293     getTargetEl: function() {
30294         return this.body || this.frameBody || this.el;
30295     },
30296
30297     /**
30298      * Sets the title of the header.
30299      * @param {String} title The title to be set
30300      */
30301     setTitle: function(title) {
30302         var me = this;
30303         if (me.rendered) {
30304             if (me.titleCmp.rendered) {
30305                 if (me.titleCmp.surface) {
30306                     me.title = title || '';
30307                     var sprite = me.titleCmp.surface.items.items[0],
30308                         surface = me.titleCmp.surface;
30309
30310                     surface.remove(sprite);
30311                     me.textConfig.type = 'text';
30312                     me.textConfig.text = title;
30313                     sprite = surface.add(me.textConfig);
30314                     sprite.setAttributes({
30315                         rotate: {
30316                             degrees: 90
30317                         }
30318                     }, true);
30319                     me.titleCmp.autoSizeSurface();
30320                 } else {
30321                     me.title = title || '&#160;';
30322                     me.titleCmp.textEl.update(me.title);
30323                 }
30324             } else {
30325                 me.titleCmp.on({
30326                     render: function() {
30327                         me.setTitle(title);
30328                     },
30329                     single: true
30330                 });
30331             }
30332         } else {
30333             me.on({
30334                 render: function() {
30335                     me.layout.layout();
30336                     me.setTitle(title);
30337                 },
30338                 single: true
30339             });
30340         }
30341     },
30342
30343     /**
30344      * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
30345      * icon class if one has already been set.
30346      * @param {String} cls The new CSS class name
30347      */
30348     setIconCls: function(cls) {
30349         var me = this,
30350             isEmpty = !cls || !cls.length,
30351             iconCmp = me.iconCmp,
30352             el;
30353         
30354         me.iconCls = cls;
30355         if (!me.iconCmp && !isEmpty) {
30356             me.initIconCmp();
30357             me.insert(0, me.iconCmp);
30358         } else if (iconCmp) {
30359             if (isEmpty) {
30360                 me.iconCmp.destroy();
30361             } else {
30362                 el = iconCmp.iconEl;
30363                 el.removeCls(iconCmp.iconCls);
30364                 el.addCls(cls);
30365                 iconCmp.iconCls = cls;
30366             }
30367         }
30368     },
30369
30370     /**
30371      * Add a tool to the header
30372      * @param {Object} tool
30373      */
30374     addTool: function(tool) {
30375         this.tools.push(this.add(tool));
30376     },
30377
30378     /**
30379      * @private
30380      * Set up the tools.&lt;tool type> link in the owning Panel.
30381      * Bind the tool to its owning Panel.
30382      * @param component
30383      * @param index
30384      */
30385     onAdd: function(component, index) {
30386         this.callParent([arguments]);
30387         if (component instanceof Ext.panel.Tool) {
30388             component.bindTo(this.ownerCt);
30389             this.tools[component.type] = component;
30390         }
30391     }
30392 });
30393
30394 /**
30395  * @class Ext.fx.target.Element
30396  * @extends Ext.fx.target.Target
30397  * 
30398  * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
30399  * created directly, the {@link Ext.Element} will be passed to the animation and
30400  * and the appropriate target will be created.
30401  */
30402 Ext.define('Ext.fx.target.Element', {
30403
30404     /* Begin Definitions */
30405     
30406     extend: 'Ext.fx.target.Target',
30407     
30408     /* End Definitions */
30409
30410     type: 'element',
30411
30412     getElVal: function(el, attr, val) {
30413         if (val == undefined) {
30414             if (attr === 'x') {
30415                 val = el.getX();
30416             }
30417             else if (attr === 'y') {
30418                 val = el.getY();
30419             }
30420             else if (attr === 'scrollTop') {
30421                 val = el.getScroll().top;
30422             }
30423             else if (attr === 'scrollLeft') {
30424                 val = el.getScroll().left;
30425             }
30426             else if (attr === 'height') {
30427                 val = el.getHeight();
30428             }
30429             else if (attr === 'width') {
30430                 val = el.getWidth();
30431             }
30432             else {
30433                 val = el.getStyle(attr);
30434             }
30435         }
30436         return val;
30437     },
30438
30439     getAttr: function(attr, val) {
30440         var el = this.target;
30441         return [[ el, this.getElVal(el, attr, val)]];
30442     },
30443
30444     setAttr: function(targetData) {
30445         var target = this.target,
30446             ln = targetData.length,
30447             attrs, attr, o, i, j, ln2, element, value;
30448         for (i = 0; i < ln; i++) {
30449             attrs = targetData[i].attrs;
30450             for (attr in attrs) {
30451                 if (attrs.hasOwnProperty(attr)) {
30452                     ln2 = attrs[attr].length;
30453                     for (j = 0; j < ln2; j++) {
30454                         o = attrs[attr][j];
30455                         element = o[0];
30456                         value = o[1];
30457                         if (attr === 'x') {
30458                             element.setX(value);
30459                         }
30460                         else if (attr === 'y') {
30461                             element.setY(value);
30462                         }
30463                         else if (attr === 'scrollTop') {
30464                             element.scrollTo('top', value);
30465                         }
30466                         else if (attr === 'scrollLeft') {
30467                             element.scrollTo('left',value);
30468                         }
30469                         else {
30470                             element.setStyle(attr, value);
30471                         }
30472                     }
30473                 }
30474             }
30475         }
30476     }
30477 });
30478
30479 /**
30480  * @class Ext.fx.target.CompositeElement
30481  * @extends Ext.fx.target.Element
30482  * 
30483  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
30484  * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
30485  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
30486  * and the appropriate target will be created.
30487  */
30488 Ext.define('Ext.fx.target.CompositeElement', {
30489
30490     /* Begin Definitions */
30491
30492     extend: 'Ext.fx.target.Element',
30493
30494     /* End Definitions */
30495
30496     isComposite: true,
30497     
30498     constructor: function(target) {
30499         target.id = target.id || Ext.id(null, 'ext-composite-');
30500         this.callParent([target]);
30501     },
30502
30503     getAttr: function(attr, val) {
30504         var out = [],
30505             target = this.target;
30506         target.each(function(el) {
30507             out.push([el, this.getElVal(el, attr, val)]);
30508         }, this);
30509         return out;
30510     }
30511 });
30512
30513 /**
30514  * @class Ext.fx.Manager
30515  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
30516  * @private
30517  * @singleton
30518  */
30519
30520 Ext.define('Ext.fx.Manager', {
30521
30522     /* Begin Definitions */
30523
30524     singleton: true,
30525
30526     requires: ['Ext.util.MixedCollection',
30527                'Ext.fx.target.Element',
30528                'Ext.fx.target.CompositeElement',
30529                'Ext.fx.target.Sprite',
30530                'Ext.fx.target.CompositeSprite',
30531                'Ext.fx.target.Component'],
30532
30533     mixins: {
30534         queue: 'Ext.fx.Queue'
30535     },
30536
30537     /* End Definitions */
30538
30539     constructor: function() {
30540         this.items = Ext.create('Ext.util.MixedCollection');
30541         this.mixins.queue.constructor.call(this);
30542
30543         // this.requestAnimFrame = (function() {
30544         //     var raf = window.requestAnimationFrame ||
30545         //               window.webkitRequestAnimationFrame ||
30546         //               window.mozRequestAnimationFrame ||
30547         //               window.oRequestAnimationFrame ||
30548         //               window.msRequestAnimationFrame;
30549         //     if (raf) {
30550         //         return function(callback, element) {
30551         //             raf(callback);
30552         //         };
30553         //     }
30554         //     else {
30555         //         return function(callback, element) {
30556         //             window.setTimeout(callback, Ext.fx.Manager.interval);
30557         //         };
30558         //     }
30559         // })();
30560     },
30561
30562     /**
30563      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
30564      */
30565     interval: 16,
30566
30567     /**
30568      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
30569      */
30570     forceJS: true,
30571
30572     // @private Target factory
30573     createTarget: function(target) {
30574         var me = this,
30575             useCSS3 = !me.forceJS && Ext.supports.Transitions,
30576             targetObj;
30577
30578         me.useCSS3 = useCSS3;
30579
30580         // dom id
30581         if (Ext.isString(target)) {
30582             target = Ext.get(target);
30583         }
30584         // dom element
30585         if (target && target.tagName) {
30586             target = Ext.get(target);
30587             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
30588             me.targets.add(targetObj);
30589             return targetObj;
30590         }
30591         if (Ext.isObject(target)) {
30592             // Element
30593             if (target.dom) {
30594                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
30595             }
30596             // Element Composite
30597             else if (target.isComposite) {
30598                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
30599             }
30600             // Draw Sprite
30601             else if (target.isSprite) {
30602                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
30603             }
30604             // Draw Sprite Composite
30605             else if (target.isCompositeSprite) {
30606                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
30607             }
30608             // Component
30609             else if (target.isComponent) {
30610                 targetObj = Ext.create('Ext.fx.target.Component', target);
30611             }
30612             else if (target.isAnimTarget) {
30613                 return target;
30614             }
30615             else {
30616                 return null;
30617             }
30618             me.targets.add(targetObj);
30619             return targetObj;
30620         }
30621         else {
30622             return null;
30623         }
30624     },
30625
30626     /**
30627      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
30628      * @param {Ext.fx.Anim} anim
30629      */
30630     addAnim: function(anim) {
30631         var items = this.items,
30632             task = this.task;
30633         // var me = this,
30634         //     items = me.items,
30635         //     cb = function() {
30636         //         if (items.length) {
30637         //             me.task = true;
30638         //             me.runner();
30639         //             me.requestAnimFrame(cb);
30640         //         }
30641         //         else {
30642         //             me.task = false;
30643         //         }
30644         //     };
30645
30646         items.add(anim);
30647
30648         // Start the timer if not already running
30649         if (!task && items.length) {
30650             task = this.task = {
30651                 run: this.runner,
30652                 interval: this.interval,
30653                 scope: this
30654             };
30655             Ext.TaskManager.start(task);
30656         }
30657
30658         // //Start the timer if not already running
30659         // if (!me.task && items.length) {
30660         //     me.requestAnimFrame(cb);
30661         // }
30662     },
30663
30664     /**
30665      * Remove an Anim from the manager. This is done automatically when an Anim ends.
30666      * @param {Ext.fx.Anim} anim
30667      */
30668     removeAnim: function(anim) {
30669         // this.items.remove(anim);
30670         var items = this.items,
30671             task = this.task;
30672         items.remove(anim);
30673         // Stop the timer if there are no more managed Anims
30674         if (task && !items.length) {
30675             Ext.TaskManager.stop(task);
30676             delete this.task;
30677         }
30678     },
30679
30680     /**
30681      * @private
30682      * Filter function to determine which animations need to be started
30683      */
30684     startingFilter: function(o) {
30685         return o.paused === false && o.running === false && o.iterations > 0;
30686     },
30687
30688     /**
30689      * @private
30690      * Filter function to determine which animations are still running
30691      */
30692     runningFilter: function(o) {
30693         return o.paused === false && o.running === true && o.isAnimator !== true;
30694     },
30695
30696     /**
30697      * @private
30698      * Runner function being called each frame
30699      */
30700     runner: function() {
30701         var me = this,
30702             items = me.items;
30703
30704         me.targetData = {};
30705         me.targetArr = {};
30706
30707         // Single timestamp for all animations this interval
30708         me.timestamp = new Date();
30709
30710         // Start any items not current running
30711         items.filterBy(me.startingFilter).each(me.startAnim, me);
30712
30713         // Build the new attributes to be applied for all targets in this frame
30714         items.filterBy(me.runningFilter).each(me.runAnim, me);
30715
30716         // Apply all the pending changes to their targets
30717         me.applyPendingAttrs();
30718     },
30719
30720     /**
30721      * @private
30722      * Start the individual animation (initialization)
30723      */
30724     startAnim: function(anim) {
30725         anim.start(this.timestamp);
30726     },
30727
30728     /**
30729      * @private
30730      * Run the individual animation for this frame
30731      */
30732     runAnim: function(anim) {
30733         if (!anim) {
30734             return;
30735         }
30736         var me = this,
30737             targetId = anim.target.getId(),
30738             useCSS3 = me.useCSS3 && anim.target.type == 'element',
30739             elapsedTime = me.timestamp - anim.startTime,
30740             target, o;
30741
30742         this.collectTargetData(anim, elapsedTime, useCSS3);
30743
30744         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
30745         // to get a good initial state, then add the transition properties and set the final attributes.
30746         if (useCSS3) {
30747             // Flush the collected attributes, without transition
30748             anim.target.setAttr(me.targetData[targetId], true);
30749
30750             // Add the end frame data
30751             me.targetData[targetId] = [];
30752             me.collectTargetData(anim, anim.duration, useCSS3);
30753
30754             // Pause the animation so runAnim doesn't keep getting called
30755             anim.paused = true;
30756
30757             target = anim.target.target;
30758             // We only want to attach an event on the last element in a composite
30759             if (anim.target.isComposite) {
30760                 target = anim.target.target.last();
30761             }
30762
30763             // Listen for the transitionend event
30764             o = {};
30765             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
30766             o.scope = anim;
30767             o.single = true;
30768             target.on(o);
30769         }
30770         // For JS animation, trigger the lastFrame handler if this is the final frame
30771         else if (elapsedTime >= anim.duration) {
30772             me.applyPendingAttrs(true);
30773             delete me.targetData[targetId];
30774             delete me.targetArr[targetId];
30775             anim.lastFrame();
30776         }
30777     },
30778
30779     /**
30780      * Collect target attributes for the given Anim object at the given timestamp
30781      * @param {Ext.fx.Anim} anim The Anim instance
30782      * @param {Number} timestamp Time after the anim's start time
30783      */
30784     collectTargetData: function(anim, elapsedTime, useCSS3) {
30785         var targetId = anim.target.getId(),
30786             targetData = this.targetData[targetId],
30787             data;
30788         
30789         if (!targetData) {
30790             targetData = this.targetData[targetId] = [];
30791             this.targetArr[targetId] = anim.target;
30792         }
30793
30794         data = {
30795             duration: anim.duration,
30796             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
30797             attrs: {}
30798         };
30799         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
30800         targetData.push(data);
30801     },
30802
30803     /**
30804      * @private
30805      * Apply all pending attribute changes to their targets
30806      */
30807     applyPendingAttrs: function(isLastFrame) {
30808         var targetData = this.targetData,
30809             targetArr = this.targetArr,
30810             targetId;
30811         for (targetId in targetData) {
30812             if (targetData.hasOwnProperty(targetId)) {
30813                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
30814             }
30815         }
30816     }
30817 });
30818
30819 /**
30820  * @class Ext.fx.Animator
30821  *
30822  * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
30823  * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
30824  * at various points throughout the animation.
30825  *
30826  * ## Using Keyframes
30827  *
30828  * The {@link #keyframes} option is the most important part of specifying an animation when using this
30829  * class. A key frame is a point in a particular animation. We represent this as a percentage of the
30830  * total animation duration. At each key frame, we can specify the target values at that time. Note that
30831  * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
30832  * event that fires after each key frame is reached.
30833  *
30834  * ## Example
30835  *
30836  * In the example below, we modify the values of the element at each fifth throughout the animation.
30837  *
30838  *     @example
30839  *     Ext.create('Ext.fx.Animator', {
30840  *         target: Ext.getBody().createChild({
30841  *             style: {
30842  *                 width: '100px',
30843  *                 height: '100px',
30844  *                 'background-color': 'red'
30845  *             }
30846  *         }),
30847  *         duration: 10000, // 10 seconds
30848  *         keyframes: {
30849  *             0: {
30850  *                 opacity: 1,
30851  *                 backgroundColor: 'FF0000'
30852  *             },
30853  *             20: {
30854  *                 x: 30,
30855  *                 opacity: 0.5
30856  *             },
30857  *             40: {
30858  *                 x: 130,
30859  *                 backgroundColor: '0000FF'
30860  *             },
30861  *             60: {
30862  *                 y: 80,
30863  *                 opacity: 0.3
30864  *             },
30865  *             80: {
30866  *                 width: 200,
30867  *                 y: 200
30868  *             },
30869  *             100: {
30870  *                 opacity: 1,
30871  *                 backgroundColor: '00FF00'
30872  *             }
30873  *         }
30874  *     });
30875  */
30876 Ext.define('Ext.fx.Animator', {
30877
30878     /* Begin Definitions */
30879
30880     mixins: {
30881         observable: 'Ext.util.Observable'
30882     },
30883
30884     requires: ['Ext.fx.Manager'],
30885
30886     /* End Definitions */
30887
30888     isAnimator: true,
30889
30890     /**
30891      * @cfg {Number} duration
30892      * Time in milliseconds for the animation to last. Defaults to 250.
30893      */
30894     duration: 250,
30895
30896     /**
30897      * @cfg {Number} delay
30898      * Time to delay before starting the animation. Defaults to 0.
30899      */
30900     delay: 0,
30901
30902     /* private used to track a delayed starting time */
30903     delayStart: 0,
30904
30905     /**
30906      * @cfg {Boolean} dynamic
30907      * 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.
30908      */
30909     dynamic: false,
30910
30911     /**
30912      * @cfg {String} easing
30913      *
30914      * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
30915      * speed over its duration.
30916      *
30917      *  - backIn
30918      *  - backOut
30919      *  - bounceIn
30920      *  - bounceOut
30921      *  - ease
30922      *  - easeIn
30923      *  - easeOut
30924      *  - easeInOut
30925      *  - elasticIn
30926      *  - elasticOut
30927      *  - cubic-bezier(x1, y1, x2, y2)
30928      *
30929      * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
30930      * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
30931      * be in the range [0, 1] or the definition is invalid.
30932      *
30933      * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
30934      */
30935     easing: 'ease',
30936
30937     /**
30938      * Flag to determine if the animation has started
30939      * @property running
30940      * @type Boolean
30941      */
30942     running: false,
30943
30944     /**
30945      * Flag to determine if the animation is paused. Only set this to true if you need to
30946      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
30947      * @property paused
30948      * @type Boolean
30949      */
30950     paused: false,
30951
30952     /**
30953      * @private
30954      */
30955     damper: 1,
30956
30957     /**
30958      * @cfg {Number} iterations
30959      * Number of times to execute the animation. Defaults to 1.
30960      */
30961     iterations: 1,
30962
30963     /**
30964      * Current iteration the animation is running.
30965      * @property currentIteration
30966      * @type Number
30967      */
30968     currentIteration: 0,
30969
30970     /**
30971      * Current keyframe step of the animation.
30972      * @property keyframeStep
30973      * @type Number
30974      */
30975     keyframeStep: 0,
30976
30977     /**
30978      * @private
30979      */
30980     animKeyFramesRE: /^(from|to|\d+%?)$/,
30981
30982     /**
30983      * @cfg {Ext.fx.target.Target} target
30984      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
30985      * method to apply the same animation to many targets.
30986      */
30987
30988      /**
30989       * @cfg {Object} keyframes
30990       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
30991       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
30992       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
30993       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
30994       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
30995  <pre><code>
30996 keyframes : {
30997     '0%': {
30998         left: 100
30999     },
31000     '40%': {
31001         left: 150
31002     },
31003     '60%': {
31004         left: 75
31005     },
31006     '100%': {
31007         left: 100
31008     }
31009 }
31010  </code></pre>
31011       */
31012     constructor: function(config) {
31013         var me = this;
31014         config = Ext.apply(me, config || {});
31015         me.config = config;
31016         me.id = Ext.id(null, 'ext-animator-');
31017         me.addEvents(
31018             /**
31019              * @event beforeanimate
31020              * Fires before the animation starts. A handler can return false to cancel the animation.
31021              * @param {Ext.fx.Animator} this
31022              */
31023             'beforeanimate',
31024             /**
31025               * @event keyframe
31026               * Fires at each keyframe.
31027               * @param {Ext.fx.Animator} this
31028               * @param {Number} keyframe step number
31029               */
31030             'keyframe',
31031             /**
31032              * @event afteranimate
31033              * Fires when the animation is complete.
31034              * @param {Ext.fx.Animator} this
31035              * @param {Date} startTime
31036              */
31037             'afteranimate'
31038         );
31039         me.mixins.observable.constructor.call(me, config);
31040         me.timeline = [];
31041         me.createTimeline(me.keyframes);
31042         if (me.target) {
31043             me.applyAnimator(me.target);
31044             Ext.fx.Manager.addAnim(me);
31045         }
31046     },
31047
31048     /**
31049      * @private
31050      */
31051     sorter: function (a, b) {
31052         return a.pct - b.pct;
31053     },
31054
31055     /**
31056      * @private
31057      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
31058      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
31059      */
31060     createTimeline: function(keyframes) {
31061         var me = this,
31062             attrs = [],
31063             to = me.to || {},
31064             duration = me.duration,
31065             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
31066
31067         for (pct in keyframes) {
31068             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
31069                 attr = {attrs: Ext.apply(keyframes[pct], to)};
31070                 // CSS3 spec allow for from/to to be specified.
31071                 if (pct == "from") {
31072                     pct = 0;
31073                 }
31074                 else if (pct == "to") {
31075                     pct = 100;
31076                 }
31077                 // convert % values into integers
31078                 attr.pct = parseInt(pct, 10);
31079                 attrs.push(attr);
31080             }
31081         }
31082         // Sort by pct property
31083         Ext.Array.sort(attrs, me.sorter);
31084         // Only an end
31085         //if (attrs[0].pct) {
31086         //    attrs.unshift({pct: 0, attrs: element.attrs});
31087         //}
31088
31089         ln = attrs.length;
31090         for (i = 0; i < ln; i++) {
31091             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
31092             ms = duration * (attrs[i].pct / 100);
31093             me.timeline.push({
31094                 duration: ms - prevMs,
31095                 attrs: attrs[i].attrs
31096             });
31097         }
31098     },
31099
31100     /**
31101      * Applies animation to the Ext.fx.target
31102      * @private
31103      * @param target
31104      * @type String/Object
31105      */
31106     applyAnimator: function(target) {
31107         var me = this,
31108             anims = [],
31109             timeline = me.timeline,
31110             reverse = me.reverse,
31111             ln = timeline.length,
31112             anim, easing, damper, initial, attrs, lastAttrs, i;
31113
31114         if (me.fireEvent('beforeanimate', me) !== false) {
31115             for (i = 0; i < ln; i++) {
31116                 anim = timeline[i];
31117                 attrs = anim.attrs;
31118                 easing = attrs.easing || me.easing;
31119                 damper = attrs.damper || me.damper;
31120                 delete attrs.easing;
31121                 delete attrs.damper;
31122                 anim = Ext.create('Ext.fx.Anim', {
31123                     target: target,
31124                     easing: easing,
31125                     damper: damper,
31126                     duration: anim.duration,
31127                     paused: true,
31128                     to: attrs
31129                 });
31130                 anims.push(anim);
31131             }
31132             me.animations = anims;
31133             me.target = anim.target;
31134             for (i = 0; i < ln - 1; i++) {
31135                 anim = anims[i];
31136                 anim.nextAnim = anims[i + 1];
31137                 anim.on('afteranimate', function() {
31138                     this.nextAnim.paused = false;
31139                 });
31140                 anim.on('afteranimate', function() {
31141                     this.fireEvent('keyframe', this, ++this.keyframeStep);
31142                 }, me);
31143             }
31144             anims[ln - 1].on('afteranimate', function() {
31145                 this.lastFrame();
31146             }, me);
31147         }
31148     },
31149
31150     /**
31151      * @private
31152      * Fires beforeanimate and sets the running flag.
31153      */
31154     start: function(startTime) {
31155         var me = this,
31156             delay = me.delay,
31157             delayStart = me.delayStart,
31158             delayDelta;
31159         if (delay) {
31160             if (!delayStart) {
31161                 me.delayStart = startTime;
31162                 return;
31163             }
31164             else {
31165                 delayDelta = startTime - delayStart;
31166                 if (delayDelta < delay) {
31167                     return;
31168                 }
31169                 else {
31170                     // Compensate for frame delay;
31171                     startTime = new Date(delayStart.getTime() + delay);
31172                 }
31173             }
31174         }
31175         if (me.fireEvent('beforeanimate', me) !== false) {
31176             me.startTime = startTime;
31177             me.running = true;
31178             me.animations[me.keyframeStep].paused = false;
31179         }
31180     },
31181
31182     /**
31183      * @private
31184      * Perform lastFrame cleanup and handle iterations
31185      * @returns a hash of the new attributes.
31186      */
31187     lastFrame: function() {
31188         var me = this,
31189             iter = me.iterations,
31190             iterCount = me.currentIteration;
31191
31192         iterCount++;
31193         if (iterCount < iter) {
31194             me.startTime = new Date();
31195             me.currentIteration = iterCount;
31196             me.keyframeStep = 0;
31197             me.applyAnimator(me.target);
31198             me.animations[me.keyframeStep].paused = false;
31199         }
31200         else {
31201             me.currentIteration = 0;
31202             me.end();
31203         }
31204     },
31205
31206     /**
31207      * Fire afteranimate event and end the animation. Usually called automatically when the
31208      * animation reaches its final frame, but can also be called manually to pre-emptively
31209      * stop and destroy the running animation.
31210      */
31211     end: function() {
31212         var me = this;
31213         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
31214     }
31215 });
31216 /**
31217  * @class Ext.fx.Easing
31218  *
31219  * This class contains a series of function definitions used to modify values during an animation.
31220  * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
31221  * speed over its duration. The following options are available: 
31222  *
31223  * - linear The default easing type
31224  * - backIn
31225  * - backOut
31226  * - bounceIn
31227  * - bounceOut
31228  * - ease
31229  * - easeIn
31230  * - easeOut
31231  * - easeInOut
31232  * - elasticIn
31233  * - elasticOut
31234  * - cubic-bezier(x1, y1, x2, y2)
31235  *
31236  * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
31237  * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
31238  * be in the range [0, 1] or the definition is invalid.
31239  *
31240  * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
31241  *
31242  * @singleton
31243  */
31244 Ext.ns('Ext.fx');
31245
31246 Ext.require('Ext.fx.CubicBezier', function() {
31247     var math = Math,
31248         pi = math.PI,
31249         pow = math.pow,
31250         sin = math.sin,
31251         sqrt = math.sqrt,
31252         abs = math.abs,
31253         backInSeed = 1.70158;
31254     Ext.fx.Easing = {
31255         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
31256         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
31257         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
31258         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
31259         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
31260         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
31261         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
31262         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
31263     };
31264
31265     Ext.apply(Ext.fx.Easing, {
31266         linear: function(n) {
31267             return n;
31268         },
31269         ease: function(n) {
31270             var q = 0.07813 - n / 2,
31271                 alpha = -0.25,
31272                 Q = sqrt(0.0066 + q * q),
31273                 x = Q - q,
31274                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
31275                 y = -Q - q,
31276                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
31277                 t = X + Y + 0.25;
31278             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
31279         },
31280         easeIn: function (n) {
31281             return pow(n, 1.7);
31282         },
31283         easeOut: function (n) {
31284             return pow(n, 0.48);
31285         },
31286         easeInOut: function(n) {
31287             var q = 0.48 - n / 1.04,
31288                 Q = sqrt(0.1734 + q * q),
31289                 x = Q - q,
31290                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
31291                 y = -Q - q,
31292                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
31293                 t = X + Y + 0.5;
31294             return (1 - t) * 3 * t * t + t * t * t;
31295         },
31296         backIn: function (n) {
31297             return n * n * ((backInSeed + 1) * n - backInSeed);
31298         },
31299         backOut: function (n) {
31300             n = n - 1;
31301             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
31302         },
31303         elasticIn: function (n) {
31304             if (n === 0 || n === 1) {
31305                 return n;
31306             }
31307             var p = 0.3,
31308                 s = p / 4;
31309             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
31310         },
31311         elasticOut: function (n) {
31312             return 1 - Ext.fx.Easing.elasticIn(1 - n);
31313         },
31314         bounceIn: function (n) {
31315             return 1 - Ext.fx.Easing.bounceOut(1 - n);
31316         },
31317         bounceOut: function (n) {
31318             var s = 7.5625,
31319                 p = 2.75,
31320                 l;
31321             if (n < (1 / p)) {
31322                 l = s * n * n;
31323             } else {
31324                 if (n < (2 / p)) {
31325                     n -= (1.5 / p);
31326                     l = s * n * n + 0.75;
31327                 } else {
31328                     if (n < (2.5 / p)) {
31329                         n -= (2.25 / p);
31330                         l = s * n * n + 0.9375;
31331                     } else {
31332                         n -= (2.625 / p);
31333                         l = s * n * n + 0.984375;
31334                     }
31335                 }
31336             }
31337             return l;
31338         }
31339     });
31340     Ext.apply(Ext.fx.Easing, {
31341         'back-in': Ext.fx.Easing.backIn,
31342         'back-out': Ext.fx.Easing.backOut,
31343         'ease-in': Ext.fx.Easing.easeIn,
31344         'ease-out': Ext.fx.Easing.easeOut,
31345         'elastic-in': Ext.fx.Easing.elasticIn,
31346         'elastic-out': Ext.fx.Easing.elasticIn,
31347         'bounce-in': Ext.fx.Easing.bounceIn,
31348         'bounce-out': Ext.fx.Easing.bounceOut,
31349         'ease-in-out': Ext.fx.Easing.easeInOut
31350     });
31351 });
31352 /**
31353  * @class Ext.draw.Draw
31354  * Base Drawing class.  Provides base drawing functions.
31355  * @private
31356  */
31357 Ext.define('Ext.draw.Draw', {
31358     /* Begin Definitions */
31359
31360     singleton: true,
31361
31362     requires: ['Ext.draw.Color'],
31363
31364     /* End Definitions */
31365
31366     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
31367     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
31368     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
31369     stopsRE: /^(\d+%?)$/,
31370     radian: Math.PI / 180,
31371
31372     availableAnimAttrs: {
31373         along: "along",
31374         blur: null,
31375         "clip-rect": "csv",
31376         cx: null,
31377         cy: null,
31378         fill: "color",
31379         "fill-opacity": null,
31380         "font-size": null,
31381         height: null,
31382         opacity: null,
31383         path: "path",
31384         r: null,
31385         rotation: "csv",
31386         rx: null,
31387         ry: null,
31388         scale: "csv",
31389         stroke: "color",
31390         "stroke-opacity": null,
31391         "stroke-width": null,
31392         translation: "csv",
31393         width: null,
31394         x: null,
31395         y: null
31396     },
31397
31398     is: function(o, type) {
31399         type = String(type).toLowerCase();
31400         return (type == "object" && o === Object(o)) ||
31401             (type == "undefined" && typeof o == type) ||
31402             (type == "null" && o === null) ||
31403             (type == "array" && Array.isArray && Array.isArray(o)) ||
31404             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
31405     },
31406
31407     ellipsePath: function(sprite) {
31408         var attr = sprite.attr;
31409         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);
31410     },
31411
31412     rectPath: function(sprite) {
31413         var attr = sprite.attr;
31414         if (attr.radius) {
31415             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);
31416         }
31417         else {
31418             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
31419         }
31420     },
31421
31422     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
31423     path2string: function () {
31424         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
31425     },
31426
31427     // Convert the passed arrayPath to a proper SVG path string (d attribute)
31428     pathToString: function(arrayPath) {
31429         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
31430     },
31431
31432     parsePathString: function (pathString) {
31433         if (!pathString) {
31434             return null;
31435         }
31436         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
31437             data = [],
31438             me = this;
31439         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
31440             data = me.pathClone(pathString);
31441         }
31442         if (!data.length) {
31443             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
31444                 var params = [],
31445                     name = b.toLowerCase();
31446                 c.replace(me.pathValuesRE, function (a, b) {
31447                     b && params.push(+b);
31448                 });
31449                 if (name == "m" && params.length > 2) {
31450                     data.push([b].concat(Ext.Array.splice(params, 0, 2)));
31451                     name = "l";
31452                     b = (b == "m") ? "l" : "L";
31453                 }
31454                 while (params.length >= paramCounts[name]) {
31455                     data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
31456                     if (!paramCounts[name]) {
31457                         break;
31458                     }
31459                 }
31460             });
31461         }
31462         data.toString = me.path2string;
31463         return data;
31464     },
31465
31466     mapPath: function (path, matrix) {
31467         if (!matrix) {
31468             return path;
31469         }
31470         var x, y, i, ii, j, jj, pathi;
31471         path = this.path2curve(path);
31472         for (i = 0, ii = path.length; i < ii; i++) {
31473             pathi = path[i];
31474             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
31475                 x = matrix.x(pathi[j], pathi[j + 1]);
31476                 y = matrix.y(pathi[j], pathi[j + 1]);
31477                 pathi[j] = x;
31478                 pathi[j + 1] = y;
31479             }
31480         }
31481         return path;
31482     },
31483
31484     pathClone: function(pathArray) {
31485         var res = [],
31486             j, jj, i, ii;
31487         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
31488             pathArray = this.parsePathString(pathArray);
31489         }
31490         for (i = 0, ii = pathArray.length; i < ii; i++) {
31491             res[i] = [];
31492             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
31493                 res[i][j] = pathArray[i][j];
31494             }
31495         }
31496         res.toString = this.path2string;
31497         return res;
31498     },
31499
31500     pathToAbsolute: function (pathArray) {
31501         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
31502             pathArray = this.parsePathString(pathArray);
31503         }
31504         var res = [],
31505             x = 0,
31506             y = 0,
31507             mx = 0,
31508             my = 0,
31509             i = 0,
31510             ln = pathArray.length,
31511             r, pathSegment, j, ln2;
31512         // MoveTo initial x/y position
31513         if (ln && pathArray[0][0] == "M") {
31514             x = +pathArray[0][1];
31515             y = +pathArray[0][2];
31516             mx = x;
31517             my = y;
31518             i++;
31519             res[0] = ["M", x, y];
31520         }
31521         for (; i < ln; i++) {
31522             r = res[i] = [];
31523             pathSegment = pathArray[i];
31524             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
31525                 r[0] = pathSegment[0].toUpperCase();
31526                 switch (r[0]) {
31527                     // Elliptical Arc
31528                     case "A":
31529                         r[1] = pathSegment[1];
31530                         r[2] = pathSegment[2];
31531                         r[3] = pathSegment[3];
31532                         r[4] = pathSegment[4];
31533                         r[5] = pathSegment[5];
31534                         r[6] = +(pathSegment[6] + x);
31535                         r[7] = +(pathSegment[7] + y);
31536                         break;
31537                     // Vertical LineTo
31538                     case "V":
31539                         r[1] = +pathSegment[1] + y;
31540                         break;
31541                     // Horizontal LineTo
31542                     case "H":
31543                         r[1] = +pathSegment[1] + x;
31544                         break;
31545                     case "M":
31546                     // MoveTo
31547                         mx = +pathSegment[1] + x;
31548                         my = +pathSegment[2] + y;
31549                     default:
31550                         j = 1;
31551                         ln2 = pathSegment.length;
31552                         for (; j < ln2; j++) {
31553                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
31554                         }
31555                 }
31556             }
31557             else {
31558                 j = 0;
31559                 ln2 = pathSegment.length;
31560                 for (; j < ln2; j++) {
31561                     res[i][j] = pathSegment[j];
31562                 }
31563             }
31564             switch (r[0]) {
31565                 // ClosePath
31566                 case "Z":
31567                     x = mx;
31568                     y = my;
31569                     break;
31570                 // Horizontal LineTo
31571                 case "H":
31572                     x = r[1];
31573                     break;
31574                 // Vertical LineTo
31575                 case "V":
31576                     y = r[1];
31577                     break;
31578                 // MoveTo
31579                 case "M":
31580                     pathSegment = res[i];
31581                     ln2 = pathSegment.length;
31582                     mx = pathSegment[ln2 - 2];
31583                     my = pathSegment[ln2 - 1];
31584                 default:
31585                     pathSegment = res[i];
31586                     ln2 = pathSegment.length;
31587                     x = pathSegment[ln2 - 2];
31588                     y = pathSegment[ln2 - 1];
31589             }
31590         }
31591         res.toString = this.path2string;
31592         return res;
31593     },
31594
31595     // TO BE DEPRECATED
31596     pathToRelative: function (pathArray) {
31597         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
31598             pathArray = this.parsePathString(pathArray);
31599         }
31600         var res = [],
31601             x = 0,
31602             y = 0,
31603             mx = 0,
31604             my = 0,
31605             start = 0;
31606         if (pathArray[0][0] == "M") {
31607             x = pathArray[0][1];
31608             y = pathArray[0][2];
31609             mx = x;
31610             my = y;
31611             start++;
31612             res.push(["M", x, y]);
31613         }
31614         for (var i = start, ii = pathArray.length; i < ii; i++) {
31615             var r = res[i] = [],
31616                 pa = pathArray[i];
31617             if (pa[0] != pa[0].toLowerCase()) {
31618                 r[0] = pa[0].toLowerCase();
31619                 switch (r[0]) {
31620                     case "a":
31621                         r[1] = pa[1];
31622                         r[2] = pa[2];
31623                         r[3] = pa[3];
31624                         r[4] = pa[4];
31625                         r[5] = pa[5];
31626                         r[6] = +(pa[6] - x).toFixed(3);
31627                         r[7] = +(pa[7] - y).toFixed(3);
31628                         break;
31629                     case "v":
31630                         r[1] = +(pa[1] - y).toFixed(3);
31631                         break;
31632                     case "m":
31633                         mx = pa[1];
31634                         my = pa[2];
31635                     default:
31636                         for (var j = 1, jj = pa.length; j < jj; j++) {
31637                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
31638                         }
31639                 }
31640             } else {
31641                 r = res[i] = [];
31642                 if (pa[0] == "m") {
31643                     mx = pa[1] + x;
31644                     my = pa[2] + y;
31645                 }
31646                 for (var k = 0, kk = pa.length; k < kk; k++) {
31647                     res[i][k] = pa[k];
31648                 }
31649             }
31650             var len = res[i].length;
31651             switch (res[i][0]) {
31652                 case "z":
31653                     x = mx;
31654                     y = my;
31655                     break;
31656                 case "h":
31657                     x += +res[i][len - 1];
31658                     break;
31659                 case "v":
31660                     y += +res[i][len - 1];
31661                     break;
31662                 default:
31663                     x += +res[i][len - 2];
31664                     y += +res[i][len - 1];
31665             }
31666         }
31667         res.toString = this.path2string;
31668         return res;
31669     },
31670
31671     // Returns a path converted to a set of curveto commands
31672     path2curve: function (path) {
31673         var me = this,
31674             points = me.pathToAbsolute(path),
31675             ln = points.length,
31676             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
31677             i, seg, segLn, point;
31678             
31679         for (i = 0; i < ln; i++) {
31680             points[i] = me.command2curve(points[i], attrs);
31681             if (points[i].length > 7) {
31682                     points[i].shift();
31683                     point = points[i];
31684                     while (point.length) {
31685                         Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
31686                     }
31687                     Ext.Array.erase(points, i, 1);
31688                     ln = points.length;
31689                 }
31690             seg = points[i];
31691             segLn = seg.length;
31692             attrs.x = seg[segLn - 2];
31693             attrs.y = seg[segLn - 1];
31694             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
31695             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
31696         }
31697         return points;
31698     },
31699     
31700     interpolatePaths: function (path, path2) {
31701         var me = this,
31702             p = me.pathToAbsolute(path),
31703             p2 = me.pathToAbsolute(path2),
31704             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
31705             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
31706             fixArc = function (pp, i) {
31707                 if (pp[i].length > 7) {
31708                     pp[i].shift();
31709                     var pi = pp[i];
31710                     while (pi.length) {
31711                         Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
31712                     }
31713                     Ext.Array.erase(pp, i, 1);
31714                     ii = Math.max(p.length, p2.length || 0);
31715                 }
31716             },
31717             fixM = function (path1, path2, a1, a2, i) {
31718                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
31719                     Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
31720                     a1.bx = 0;
31721                     a1.by = 0;
31722                     a1.x = path1[i][1];
31723                     a1.y = path1[i][2];
31724                     ii = Math.max(p.length, p2.length || 0);
31725                 }
31726             };
31727         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
31728             p[i] = me.command2curve(p[i], attrs);
31729             fixArc(p, i);
31730             (p2[i] = me.command2curve(p2[i], attrs2));
31731             fixArc(p2, i);
31732             fixM(p, p2, attrs, attrs2, i);
31733             fixM(p2, p, attrs2, attrs, i);
31734             var seg = p[i],
31735                 seg2 = p2[i],
31736                 seglen = seg.length,
31737                 seg2len = seg2.length;
31738             attrs.x = seg[seglen - 2];
31739             attrs.y = seg[seglen - 1];
31740             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
31741             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
31742             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
31743             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
31744             attrs2.x = seg2[seg2len - 2];
31745             attrs2.y = seg2[seg2len - 1];
31746         }
31747         return [p, p2];
31748     },
31749     
31750     //Returns any path command as a curveto command based on the attrs passed
31751     command2curve: function (pathCommand, d) {
31752         var me = this;
31753         if (!pathCommand) {
31754             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
31755         }
31756         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
31757             d.qx = d.qy = null;
31758         }
31759         switch (pathCommand[0]) {
31760             case "M":
31761                 d.X = pathCommand[1];
31762                 d.Y = pathCommand[2];
31763                 break;
31764             case "A":
31765                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
31766                 break;
31767             case "S":
31768                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
31769                 break;
31770             case "T":
31771                 d.qx = d.x + (d.x - (d.qx || d.x));
31772                 d.qy = d.y + (d.y - (d.qy || d.y));
31773                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
31774                 break;
31775             case "Q":
31776                 d.qx = pathCommand[1];
31777                 d.qy = pathCommand[2];
31778                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
31779                 break;
31780             case "L":
31781                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
31782                 break;
31783             case "H":
31784                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
31785                 break;
31786             case "V":
31787                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
31788                 break;
31789             case "Z":
31790                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
31791                 break;
31792         }
31793         return pathCommand;
31794     },
31795
31796     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
31797         var _13 = 1 / 3,
31798             _23 = 2 / 3;
31799         return [
31800                 _13 * x1 + _23 * ax,
31801                 _13 * y1 + _23 * ay,
31802                 _13 * x2 + _23 * ax,
31803                 _13 * y2 + _23 * ay,
31804                 x2,
31805                 y2
31806             ];
31807     },
31808     
31809     rotate: function (x, y, rad) {
31810         var cos = Math.cos(rad),
31811             sin = Math.sin(rad),
31812             X = x * cos - y * sin,
31813             Y = x * sin + y * cos;
31814         return {x: X, y: Y};
31815     },
31816
31817     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
31818         // for more information of where this Math came from visit:
31819         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
31820         var me = this,
31821             PI = Math.PI,
31822             radian = me.radian,
31823             _120 = PI * 120 / 180,
31824             rad = radian * (+angle || 0),
31825             res = [],
31826             math = Math,
31827             mcos = math.cos,
31828             msin = math.sin,
31829             msqrt = math.sqrt,
31830             mabs = math.abs,
31831             masin = math.asin,
31832             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
31833             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
31834         if (!recursive) {
31835             xy = me.rotate(x1, y1, -rad);
31836             x1 = xy.x;
31837             y1 = xy.y;
31838             xy = me.rotate(x2, y2, -rad);
31839             x2 = xy.x;
31840             y2 = xy.y;
31841             cos = mcos(radian * angle);
31842             sin = msin(radian * angle);
31843             x = (x1 - x2) / 2;
31844             y = (y1 - y2) / 2;
31845             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
31846             if (h > 1) {
31847                 h = msqrt(h);
31848                 rx = h * rx;
31849                 ry = h * ry;
31850             }
31851             rx2 = rx * rx;
31852             ry2 = ry * ry;
31853             k = (large_arc_flag == sweep_flag ? -1 : 1) *
31854                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
31855             cx = k * rx * y / ry + (x1 + x2) / 2;
31856             cy = k * -ry * x / rx + (y1 + y2) / 2;
31857             f1 = masin(((y1 - cy) / ry).toFixed(7));
31858             f2 = masin(((y2 - cy) / ry).toFixed(7));
31859
31860             f1 = x1 < cx ? PI - f1 : f1;
31861             f2 = x2 < cx ? PI - f2 : f2;
31862             if (f1 < 0) {
31863                 f1 = PI * 2 + f1;
31864             }
31865             if (f2 < 0) {
31866                 f2 = PI * 2 + f2;
31867             }
31868             if (sweep_flag && f1 > f2) {
31869                 f1 = f1 - PI * 2;
31870             }
31871             if (!sweep_flag && f2 > f1) {
31872                 f2 = f2 - PI * 2;
31873             }
31874         }
31875         else {
31876             f1 = recursive[0];
31877             f2 = recursive[1];
31878             cx = recursive[2];
31879             cy = recursive[3];
31880         }
31881         df = f2 - f1;
31882         if (mabs(df) > _120) {
31883             f2old = f2;
31884             x2old = x2;
31885             y2old = y2;
31886             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
31887             x2 = cx + rx * mcos(f2);
31888             y2 = cy + ry * msin(f2);
31889             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
31890         }
31891         df = f2 - f1;
31892         c1 = mcos(f1);
31893         s1 = msin(f1);
31894         c2 = mcos(f2);
31895         s2 = msin(f2);
31896         t = math.tan(df / 4);
31897         hx = 4 / 3 * rx * t;
31898         hy = 4 / 3 * ry * t;
31899         m1 = [x1, y1];
31900         m2 = [x1 + hx * s1, y1 - hy * c1];
31901         m3 = [x2 + hx * s2, y2 - hy * c2];
31902         m4 = [x2, y2];
31903         m2[0] = 2 * m1[0] - m2[0];
31904         m2[1] = 2 * m1[1] - m2[1];
31905         if (recursive) {
31906             return [m2, m3, m4].concat(res);
31907         }
31908         else {
31909             res = [m2, m3, m4].concat(res).join().split(",");
31910             newres = [];
31911             ln = res.length;
31912             for (i = 0;  i < ln; i++) {
31913                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
31914             }
31915             return newres;
31916         }
31917     },
31918
31919     // TO BE DEPRECATED
31920     rotateAndTranslatePath: function (sprite) {
31921         var alpha = sprite.rotation.degrees,
31922             cx = sprite.rotation.x,
31923             cy = sprite.rotation.y,
31924             dx = sprite.translation.x,
31925             dy = sprite.translation.y,
31926             path,
31927             i,
31928             p,
31929             xy,
31930             j,
31931             res = [];
31932         if (!alpha && !dx && !dy) {
31933             return this.pathToAbsolute(sprite.attr.path);
31934         }
31935         dx = dx || 0;
31936         dy = dy || 0;
31937         path = this.pathToAbsolute(sprite.attr.path);
31938         for (i = path.length; i--;) {
31939             p = res[i] = path[i].slice();
31940             if (p[0] == "A") {
31941                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
31942                 p[6] = xy.x + dx;
31943                 p[7] = xy.y + dy;
31944             } else {
31945                 j = 1;
31946                 while (p[j + 1] != null) {
31947                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
31948                     p[j] = xy.x + dx;
31949                     p[j + 1] = xy.y + dy;
31950                     j += 2;
31951                 }
31952             }
31953         }
31954         return res;
31955     },
31956
31957     // TO BE DEPRECATED
31958     rotatePoint: function (x, y, alpha, cx, cy) {
31959         if (!alpha) {
31960             return {
31961                 x: x,
31962                 y: y
31963             };
31964         }
31965         cx = cx || 0;
31966         cy = cy || 0;
31967         x = x - cx;
31968         y = y - cy;
31969         alpha = alpha * this.radian;
31970         var cos = Math.cos(alpha),
31971             sin = Math.sin(alpha);
31972         return {
31973             x: x * cos - y * sin + cx,
31974             y: x * sin + y * cos + cy
31975         };
31976     },
31977
31978     pathDimensions: function (path) {
31979         if (!path || !(path + "")) {
31980             return {x: 0, y: 0, width: 0, height: 0};
31981         }
31982         path = this.path2curve(path);
31983         var x = 0, 
31984             y = 0,
31985             X = [],
31986             Y = [],
31987             i = 0,
31988             ln = path.length,
31989             p, xmin, ymin, dim;
31990         for (; i < ln; i++) {
31991             p = path[i];
31992             if (p[0] == "M") {
31993                 x = p[1];
31994                 y = p[2];
31995                 X.push(x);
31996                 Y.push(y);
31997             }
31998             else {
31999                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
32000                 X = X.concat(dim.min.x, dim.max.x);
32001                 Y = Y.concat(dim.min.y, dim.max.y);
32002                 x = p[5];
32003                 y = p[6];
32004             }
32005         }
32006         xmin = Math.min.apply(0, X);
32007         ymin = Math.min.apply(0, Y);
32008         return {
32009             x: xmin,
32010             y: ymin,
32011             path: path,
32012             width: Math.max.apply(0, X) - xmin,
32013             height: Math.max.apply(0, Y) - ymin
32014         };
32015     },
32016
32017     intersectInside: function(path, cp1, cp2) {
32018         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
32019     },
32020
32021     intersectIntersection: function(s, e, cp1, cp2) {
32022         var p = [],
32023             dcx = cp1[0] - cp2[0],
32024             dcy = cp1[1] - cp2[1],
32025             dpx = s[0] - e[0],
32026             dpy = s[1] - e[1],
32027             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
32028             n2 = s[0] * e[1] - s[1] * e[0],
32029             n3 = 1 / (dcx * dpy - dcy * dpx);
32030
32031         p[0] = (n1 * dpx - n2 * dcx) * n3;
32032         p[1] = (n1 * dpy - n2 * dcy) * n3;
32033         return p;
32034     },
32035
32036     intersect: function(subjectPolygon, clipPolygon) {
32037         var me = this,
32038             i = 0,
32039             ln = clipPolygon.length,
32040             cp1 = clipPolygon[ln - 1],
32041             outputList = subjectPolygon,
32042             cp2, s, e, point, ln2, inputList, j;
32043         for (; i < ln; ++i) {
32044             cp2 = clipPolygon[i];
32045             inputList = outputList;
32046             outputList = [];
32047             s = inputList[inputList.length - 1];
32048             j = 0;
32049             ln2 = inputList.length;
32050             for (; j < ln2; j++) {
32051                 e = inputList[j];
32052                 if (me.intersectInside(e, cp1, cp2)) {
32053                     if (!me.intersectInside(s, cp1, cp2)) {
32054                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
32055                     }
32056                     outputList.push(e);
32057                 }
32058                 else if (me.intersectInside(s, cp1, cp2)) {
32059                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
32060                 }
32061                 s = e;
32062             }
32063             cp1 = cp2;
32064         }
32065         return outputList;
32066     },
32067
32068     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
32069         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
32070             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
32071             c = p1x - c1x,
32072             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
32073             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
32074             y = [p1y, p2y],
32075             x = [p1x, p2x],
32076             dot;
32077         if (Math.abs(t1) > 1e12) {
32078             t1 = 0.5;
32079         }
32080         if (Math.abs(t2) > 1e12) {
32081             t2 = 0.5;
32082         }
32083         if (t1 > 0 && t1 < 1) {
32084             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
32085             x.push(dot.x);
32086             y.push(dot.y);
32087         }
32088         if (t2 > 0 && t2 < 1) {
32089             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
32090             x.push(dot.x);
32091             y.push(dot.y);
32092         }
32093         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
32094         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
32095         c = p1y - c1y;
32096         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
32097         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
32098         if (Math.abs(t1) > 1e12) {
32099             t1 = 0.5;
32100         }
32101         if (Math.abs(t2) > 1e12) {
32102             t2 = 0.5;
32103         }
32104         if (t1 > 0 && t1 < 1) {
32105             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
32106             x.push(dot.x);
32107             y.push(dot.y);
32108         }
32109         if (t2 > 0 && t2 < 1) {
32110             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
32111             x.push(dot.x);
32112             y.push(dot.y);
32113         }
32114         return {
32115             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
32116             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
32117         };
32118     },
32119
32120     /**
32121      * @private
32122      *
32123      * Calculates bezier curve control anchor points for a particular point in a path, with a
32124      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
32125      * Note that this algorithm assumes that the line being smoothed is normalized going from left
32126      * to right; it makes special adjustments assuming this orientation.
32127      *
32128      * @param {Number} prevX X coordinate of the previous point in the path
32129      * @param {Number} prevY Y coordinate of the previous point in the path
32130      * @param {Number} curX X coordinate of the current point in the path
32131      * @param {Number} curY Y coordinate of the current point in the path
32132      * @param {Number} nextX X coordinate of the next point in the path
32133      * @param {Number} nextY Y coordinate of the next point in the path
32134      * @param {Number} value A value to control the smoothness of the curve; this is used to
32135      * divide the distance between points, so a value of 2 corresponds to
32136      * half the distance between points (a very smooth line) while higher values
32137      * result in less smooth curves. Defaults to 4.
32138      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
32139      * are the control point for the curve toward the previous path point, and
32140      * x2 and y2 are the control point for the curve toward the next path point.
32141      */
32142     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
32143         value = value || 4;
32144         var M = Math,
32145             PI = M.PI,
32146             halfPI = PI / 2,
32147             abs = M.abs,
32148             sin = M.sin,
32149             cos = M.cos,
32150             atan = M.atan,
32151             control1Length, control2Length, control1Angle, control2Angle,
32152             control1X, control1Y, control2X, control2Y, alpha;
32153
32154         // Find the length of each control anchor line, by dividing the horizontal distance
32155         // between points by the value parameter.
32156         control1Length = (curX - prevX) / value;
32157         control2Length = (nextX - curX) / value;
32158
32159         // Determine the angle of each control anchor line. If the middle point is a vertical
32160         // turnaround then we force it to a flat horizontal angle to prevent the curve from
32161         // dipping above or below the middle point. Otherwise we use an angle that points
32162         // toward the previous/next target point.
32163         if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
32164             control1Angle = control2Angle = halfPI;
32165         } else {
32166             control1Angle = atan((curX - prevX) / abs(curY - prevY));
32167             if (prevY < curY) {
32168                 control1Angle = PI - control1Angle;
32169             }
32170             control2Angle = atan((nextX - curX) / abs(curY - nextY));
32171             if (nextY < curY) {
32172                 control2Angle = PI - control2Angle;
32173             }
32174         }
32175
32176         // Adjust the calculated angles so they point away from each other on the same line
32177         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
32178         if (alpha > halfPI) {
32179             alpha -= PI;
32180         }
32181         control1Angle += alpha;
32182         control2Angle += alpha;
32183
32184         // Find the control anchor points from the angles and length
32185         control1X = curX - control1Length * sin(control1Angle);
32186         control1Y = curY + control1Length * cos(control1Angle);
32187         control2X = curX + control2Length * sin(control2Angle);
32188         control2Y = curY + control2Length * cos(control2Angle);
32189
32190         // One last adjustment, make sure that no control anchor point extends vertically past
32191         // its target prev/next point, as that results in curves dipping above or below and
32192         // bending back strangely. If we find this happening we keep the control angle but
32193         // reduce the length of the control line so it stays within bounds.
32194         if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
32195             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
32196             control1Y = prevY;
32197         }
32198         if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
32199             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
32200             control2Y = nextY;
32201         }
32202         
32203         return {
32204             x1: control1X,
32205             y1: control1Y,
32206             x2: control2X,
32207             y2: control2Y
32208         };
32209     },
32210
32211     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
32212      * Defaults to a value of 4.
32213      */
32214     smooth: function (originalPath, value) {
32215         var path = this.path2curve(originalPath),
32216             newp = [path[0]],
32217             x = path[0][1],
32218             y = path[0][2],
32219             j,
32220             points,
32221             i = 1,
32222             ii = path.length,
32223             beg = 1,
32224             mx = x,
32225             my = y,
32226             cx = 0,
32227             cy = 0;
32228         for (; i < ii; i++) {
32229             var pathi = path[i],
32230                 pathil = pathi.length,
32231                 pathim = path[i - 1],
32232                 pathiml = pathim.length,
32233                 pathip = path[i + 1],
32234                 pathipl = pathip && pathip.length;
32235             if (pathi[0] == "M") {
32236                 mx = pathi[1];
32237                 my = pathi[2];
32238                 j = i + 1;
32239                 while (path[j][0] != "C") {
32240                     j++;
32241                 }
32242                 cx = path[j][5];
32243                 cy = path[j][6];
32244                 newp.push(["M", mx, my]);
32245                 beg = newp.length;
32246                 x = mx;
32247                 y = my;
32248                 continue;
32249             }
32250             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
32251                 var begl = newp[beg].length;
32252                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
32253                 newp[beg][1] = points.x2;
32254                 newp[beg][2] = points.y2;
32255             }
32256             else if (!pathip || pathip[0] == "M") {
32257                 points = {
32258                     x1: pathi[pathil - 2],
32259                     y1: pathi[pathil - 1]
32260                 };
32261             } else {
32262                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
32263             }
32264             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
32265             x = points.x2;
32266             y = points.y2;
32267         }
32268         return newp;
32269     },
32270
32271     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
32272         var t1 = 1 - t;
32273         return {
32274             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
32275             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
32276         };
32277     },
32278
32279     /**
32280      * A utility method to deduce an appropriate tick configuration for the data set of given
32281      * feature.
32282      * 
32283      * @param {Number/Date} from The minimum value in the data
32284      * @param {Number/Date} to The maximum value in the data
32285      * @param {Number} stepsMax The maximum number of ticks
32286      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
32287      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
32288      * @return {Number} return.from The result start value, which may be lower than the original start value
32289      * @return {Number} return.to The result end value, which may be higher than the original end value
32290      * @return {Number} return.power The calculate power.
32291      * @return {Number} return.step The value size of each step
32292      * @return {Number} return.steps The number of steps.
32293      */
32294     snapEnds: function (from, to, stepsMax) {
32295         if (Ext.isDate(from)) {
32296             return this.snapEndsByDate(from, to, stepsMax);
32297         }
32298         var step = (to - from) / stepsMax,
32299             level = Math.floor(Math.log(step) / Math.LN10) + 1,
32300             m = Math.pow(10, level),
32301             cur,
32302             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
32303             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
32304             stepCount = 0,
32305             value,
32306             weight,
32307             i,
32308             topValue,
32309             topWeight = 1e9,
32310             ln = interval.length;
32311         cur = from = Math.floor(from / m) * m;
32312         for (i = 0; i < ln; i++) {
32313             value = interval[i][0];
32314             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
32315             if (weight < topWeight) {
32316                 topValue = value;
32317                 topWeight = weight;
32318             }
32319         }
32320         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
32321         while (cur < to) {
32322             cur += step;
32323             stepCount++;
32324         }
32325         to = +cur.toFixed(10);
32326         return {
32327             from: from,
32328             to: to,
32329             power: level,
32330             step: step,
32331             steps: stepCount
32332         };
32333     },
32334
32335     /**
32336      * A utility method to deduce an appropriate tick configuration for the data set of given
32337      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
32338      *
32339      * @param {Date} from The minimum value in the data
32340      * @param {Date} to The maximum value in the data
32341      * @param {Number} stepsMax The maximum number of ticks
32342      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
32343      * and will not be adjusted
32344      * @return {Object} The calculated step and ends info; properties are:
32345      * @return {Date} return.from The result start value, which may be lower than the original start value
32346      * @return {Date} return.to The result end value, which may be higher than the original end value
32347      * @return {Number} return.step The value size of each step
32348      * @return {Number} return.steps The number of steps.
32349      * NOTE: the steps may not divide the from/to range perfectly evenly;
32350      * there may be a smaller distance between the last step and the end value than between prior
32351      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
32352      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
32353      * `from` to find the correct point for each tick.
32354      */
32355     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
32356         var selectedStep = false, scales = [
32357                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
32358                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
32359                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
32360                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
32361                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
32362                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
32363             ], j, yearDiff;
32364
32365         // Find the most desirable scale
32366         Ext.each(scales, function(scale, i) {
32367             for (j = 0; j < scale[1].length; j++) {
32368                 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
32369                     selectedStep = [scale[0], scale[1][j]];
32370                     return false;
32371                 }
32372             }
32373         });
32374         if (!selectedStep) {
32375             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
32376             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
32377         }
32378         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
32379     },
32380
32381
32382     /**
32383      * A utility method to deduce an appropriate tick configuration for the data set of given
32384      * feature and specific step size.
32385      * @param {Date} from The minimum value in the data
32386      * @param {Date} to The maximum value in the data
32387      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
32388      * The second one is the number of units for the step (1, 2, etc.).
32389      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
32390      * and will not be adjusted
32391      * @return {Object} See the return value of {@link #snapEndsByDate}.
32392      */
32393     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
32394         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
32395                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
32396             steps = 0, testFrom, testTo;
32397         if (lockEnds) {
32398             testFrom = from;
32399         } else {
32400             switch (step[0]) {
32401                 case Ext.Date.MILLI:
32402                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
32403                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
32404                     break;
32405                 case Ext.Date.SECOND:
32406                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
32407                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
32408                     break;
32409                 case Ext.Date.MINUTE:
32410                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
32411                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
32412                     break;
32413                 case Ext.Date.HOUR:
32414                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
32415                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
32416                     break;
32417                 case Ext.Date.DAY:
32418                     testFrom = new Date(fromStat[0], fromStat[1],
32419                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
32420                     break;
32421                 case Ext.Date.MONTH:
32422                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
32423                     break;
32424                 default: // Ext.Date.YEAR
32425                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
32426                     break;
32427             }
32428         }
32429
32430         testTo = testFrom;
32431         // TODO(zhangbei) : We can do it better somehow...
32432         while (testTo < to) {
32433             testTo = Ext.Date.add(testTo, step[0], step[1]);
32434             steps++;
32435         }
32436
32437         if (lockEnds) {
32438             testTo = to;
32439         }
32440         return {
32441             from : +testFrom,
32442             to : +testTo,
32443             step : (testTo - testFrom) / steps,
32444             steps : steps
32445         };
32446     },
32447
32448     sorter: function (a, b) {
32449         return a.offset - b.offset;
32450     },
32451
32452     rad: function(degrees) {
32453         return degrees % 360 * Math.PI / 180;
32454     },
32455
32456     degrees: function(radian) {
32457         return radian * 180 / Math.PI % 360;
32458     },
32459
32460     withinBox: function(x, y, bbox) {
32461         bbox = bbox || {};
32462         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
32463     },
32464
32465     parseGradient: function(gradient) {
32466         var me = this,
32467             type = gradient.type || 'linear',
32468             angle = gradient.angle || 0,
32469             radian = me.radian,
32470             stops = gradient.stops,
32471             stopsArr = [],
32472             stop,
32473             vector,
32474             max,
32475             stopObj;
32476
32477         if (type == 'linear') {
32478             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
32479             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
32480             vector[2] *= max;
32481             vector[3] *= max;
32482             if (vector[2] < 0) {
32483                 vector[0] = -vector[2];
32484                 vector[2] = 0;
32485             }
32486             if (vector[3] < 0) {
32487                 vector[1] = -vector[3];
32488                 vector[3] = 0;
32489             }
32490         }
32491
32492         for (stop in stops) {
32493             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
32494                 stopObj = {
32495                     offset: parseInt(stop, 10),
32496                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
32497                     opacity: stops[stop].opacity || 1
32498                 };
32499                 stopsArr.push(stopObj);
32500             }
32501         }
32502         // Sort by pct property
32503         Ext.Array.sort(stopsArr, me.sorter);
32504         if (type == 'linear') {
32505             return {
32506                 id: gradient.id,
32507                 type: type,
32508                 vector: vector,
32509                 stops: stopsArr
32510             };
32511         }
32512         else {
32513             return {
32514                 id: gradient.id,
32515                 type: type,
32516                 centerX: gradient.centerX,
32517                 centerY: gradient.centerY,
32518                 focalX: gradient.focalX,
32519                 focalY: gradient.focalY,
32520                 radius: gradient.radius,
32521                 vector: vector,
32522                 stops: stopsArr
32523             };
32524         }
32525     }
32526 });
32527
32528
32529 /**
32530  * @class Ext.fx.PropertyHandler
32531  * @ignore
32532  */
32533 Ext.define('Ext.fx.PropertyHandler', {
32534
32535     /* Begin Definitions */
32536
32537     requires: ['Ext.draw.Draw'],
32538
32539     statics: {
32540         defaultHandler: {
32541             pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
32542             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
32543             scrollRE: /^scroll/i,
32544
32545             computeDelta: function(from, end, damper, initial, attr) {
32546                 damper = (typeof damper == 'number') ? damper : 1;
32547                 var unitRE = this.unitRE,
32548                     match = unitRE.exec(from),
32549                     start, units;
32550                 if (match) {
32551                     from = match[1];
32552                     units = match[2];
32553                     if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
32554                         units = 'px';
32555                     }
32556                 }
32557                 from = +from || 0;
32558
32559                 match = unitRE.exec(end);
32560                 if (match) {
32561                     end = match[1];
32562                     units = match[2] || units;
32563                 }
32564                 end = +end || 0;
32565                 start = (initial != null) ? initial : from;
32566                 return {
32567                     from: from,
32568                     delta: (end - start) * damper,
32569                     units: units
32570                 };
32571             },
32572
32573             get: function(from, end, damper, initialFrom, attr) {
32574                 var ln = from.length,
32575                     out = [],
32576                     i, initial, res, j, len;
32577                 for (i = 0; i < ln; i++) {
32578                     if (initialFrom) {
32579                         initial = initialFrom[i][1].from;
32580                     }
32581                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
32582                         res = [];
32583                         j = 0;
32584                         len = from[i][1].length;
32585                         for (; j < len; j++) {
32586                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
32587                         }
32588                         out.push([from[i][0], res]);
32589                     }
32590                     else {
32591                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
32592                     }
32593                 }
32594                 return out;
32595             },
32596
32597             set: function(values, easing) {
32598                 var ln = values.length,
32599                     out = [],
32600                     i, val, res, len, j;
32601                 for (i = 0; i < ln; i++) {
32602                     val  = values[i][1];
32603                     if (Ext.isArray(val)) {
32604                         res = [];
32605                         j = 0;
32606                         len = val.length;
32607                         for (; j < len; j++) {
32608                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
32609                         }
32610                         out.push([values[i][0], res]);
32611                     } else {
32612                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
32613                     }
32614                 }
32615                 return out;
32616             }
32617         },
32618         color: {
32619             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
32620             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
32621             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
32622
32623             parseColor : function(color, damper) {
32624                 damper = (typeof damper == 'number') ? damper : 1;
32625                 var base,
32626                     out = false,
32627                     match;
32628
32629                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
32630                     base = (idx % 2 == 0) ? 16 : 10;
32631                     match = re.exec(color);
32632                     if (match && match.length == 4) {
32633                         if (idx == 2) {
32634                             match[1] += match[1];
32635                             match[2] += match[2];
32636                             match[3] += match[3];
32637                         }
32638                         out = {
32639                             red: parseInt(match[1], base),
32640                             green: parseInt(match[2], base),
32641                             blue: parseInt(match[3], base)
32642                         };
32643                         return false;
32644                     }
32645                 });
32646                 return out || color;
32647             },
32648
32649             computeDelta: function(from, end, damper, initial) {
32650                 from = this.parseColor(from);
32651                 end = this.parseColor(end, damper);
32652                 var start = initial ? initial : from,
32653                     tfrom = typeof start,
32654                     tend = typeof end;
32655                 //Extra check for when the color string is not recognized.
32656                 if (tfrom == 'string' ||  tfrom == 'undefined' 
32657                   || tend == 'string' || tend == 'undefined') {
32658                     return end || start;
32659                 }
32660                 return {
32661                     from:  from,
32662                     delta: {
32663                         red: Math.round((end.red - start.red) * damper),
32664                         green: Math.round((end.green - start.green) * damper),
32665                         blue: Math.round((end.blue - start.blue) * damper)
32666                     }
32667                 };
32668             },
32669
32670             get: function(start, end, damper, initialFrom) {
32671                 var ln = start.length,
32672                     out = [],
32673                     i, initial;
32674                 for (i = 0; i < ln; i++) {
32675                     if (initialFrom) {
32676                         initial = initialFrom[i][1].from;
32677                     }
32678                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
32679                 }
32680                 return out;
32681             },
32682
32683             set: function(values, easing) {
32684                 var ln = values.length,
32685                     out = [],
32686                     i, val, parsedString, from, delta;
32687                 for (i = 0; i < ln; i++) {
32688                     val = values[i][1];
32689                     if (val) {
32690                         from = val.from;
32691                         delta = val.delta;
32692                         //multiple checks to reformat the color if it can't recognized by computeDelta.
32693                         val = (typeof val == 'object' && 'red' in val)? 
32694                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
32695                         val = (typeof val == 'object' && val.length)? val[0] : val;
32696                         if (typeof val == 'undefined') {
32697                             return [];
32698                         }
32699                         parsedString = typeof val == 'string'? val :
32700                             'rgb(' + [
32701                                   (from.red + Math.round(delta.red * easing)) % 256,
32702                                   (from.green + Math.round(delta.green * easing)) % 256,
32703                                   (from.blue + Math.round(delta.blue * easing)) % 256
32704                               ].join(',') + ')';
32705                         out.push([
32706                             values[i][0],
32707                             parsedString
32708                         ]);
32709                     }
32710                 }
32711                 return out;
32712             }
32713         },
32714         object: {
32715             interpolate: function(prop, damper) {
32716                 damper = (typeof damper == 'number') ? damper : 1;
32717                 var out = {},
32718                     p;
32719                 for(p in prop) {
32720                     out[p] = parseInt(prop[p], 10) * damper;
32721                 }
32722                 return out;
32723             },
32724
32725             computeDelta: function(from, end, damper, initial) {
32726                 from = this.interpolate(from);
32727                 end = this.interpolate(end, damper);
32728                 var start = initial ? initial : from,
32729                     delta = {},
32730                     p;
32731
32732                 for(p in end) {
32733                     delta[p] = end[p] - start[p];
32734                 }
32735                 return {
32736                     from:  from,
32737                     delta: delta
32738                 };
32739             },
32740
32741             get: function(start, end, damper, initialFrom) {
32742                 var ln = start.length,
32743                     out = [],
32744                     i, initial;
32745                 for (i = 0; i < ln; i++) {
32746                     if (initialFrom) {
32747                         initial = initialFrom[i][1].from;
32748                     }
32749                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
32750                 }
32751                 return out;
32752             },
32753
32754             set: function(values, easing) {
32755                 var ln = values.length,
32756                     out = [],
32757                     outObject = {},
32758                     i, from, delta, val, p;
32759                 for (i = 0; i < ln; i++) {
32760                     val  = values[i][1];
32761                     from = val.from;
32762                     delta = val.delta;
32763                     for (p in from) {
32764                         outObject[p] = Math.round(from[p] + delta[p] * easing);
32765                     }
32766                     out.push([
32767                         values[i][0],
32768                         outObject
32769                     ]);
32770                 }
32771                 return out;
32772             }
32773         },
32774
32775         path: {
32776             computeDelta: function(from, end, damper, initial) {
32777                 damper = (typeof damper == 'number') ? damper : 1;
32778                 var start;
32779                 from = +from || 0;
32780                 end = +end || 0;
32781                 start = (initial != null) ? initial : from;
32782                 return {
32783                     from: from,
32784                     delta: (end - start) * damper
32785                 };
32786             },
32787
32788             forcePath: function(path) {
32789                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
32790                     path = Ext.draw.Draw.parsePathString(path);
32791                 }
32792                 return path;
32793             },
32794
32795             get: function(start, end, damper, initialFrom) {
32796                 var endPath = this.forcePath(end),
32797                     out = [],
32798                     startLn = start.length,
32799                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
32800                 for (i = 0; i < startLn; i++) {
32801                     startPath = this.forcePath(start[i][1]);
32802
32803                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
32804                     startPath = deltaPath[0];
32805                     endPath = deltaPath[1];
32806
32807                     startPathLn = startPath.length;
32808                     path = [];
32809                     for (j = 0; j < startPathLn; j++) {
32810                         deltaPath = [startPath[j][0]];
32811                         pointsLn = startPath[j].length;
32812                         for (k = 1; k < pointsLn; k++) {
32813                             initial = initialFrom && initialFrom[0][1][j][k].from;
32814                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
32815                         }
32816                         path.push(deltaPath);
32817                     }
32818                     out.push([start[i][0], path]);
32819                 }
32820                 return out;
32821             },
32822
32823             set: function(values, easing) {
32824                 var ln = values.length,
32825                     out = [],
32826                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
32827                 for (i = 0; i < ln; i++) {
32828                     deltaPath = values[i][1];
32829                     newPath = [];
32830                     deltaPathLn = deltaPath.length;
32831                     for (j = 0; j < deltaPathLn; j++) {
32832                         calcPath = [deltaPath[j][0]];
32833                         pointsLn = deltaPath[j].length;
32834                         for (k = 1; k < pointsLn; k++) {
32835                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
32836                         }
32837                         newPath.push(calcPath.join(','));
32838                     }
32839                     out.push([values[i][0], newPath.join(',')]);
32840                 }
32841                 return out;
32842             }
32843         }
32844         /* End Definitions */
32845     }
32846 }, function() {
32847     Ext.each([
32848         'outlineColor',
32849         'backgroundColor',
32850         'borderColor',
32851         'borderTopColor',
32852         'borderRightColor', 
32853         'borderBottomColor', 
32854         'borderLeftColor',
32855         'fill',
32856         'stroke'
32857     ], function(prop) {
32858         this[prop] = this.color;
32859     }, this);
32860 });
32861 /**
32862  * @class Ext.fx.Anim
32863  *
32864  * This class manages animation for a specific {@link #target}. The animation allows
32865  * animation of various properties on the target, such as size, position, color and others.
32866  *
32867  * ## Starting Conditions
32868  * The starting conditions for the animation are provided by the {@link #from} configuration.
32869  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
32870  * property is not defined, the starting value for that property will be read directly from the target.
32871  *
32872  * ## End Conditions
32873  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
32874  * the final values once the animations has finished. The values in the {@link #from} can mirror
32875  * those in the {@link #to} configuration to provide a starting point.
32876  *
32877  * ## Other Options
32878  *  - {@link #duration}: Specifies the time period of the animation.
32879  *  - {@link #easing}: Specifies the easing of the animation.
32880  *  - {@link #iterations}: Allows the animation to repeat a number of times.
32881  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
32882  *
32883  * ## Example Code
32884  *
32885  *     @example
32886  *     var myComponent = Ext.create('Ext.Component', {
32887  *         renderTo: document.body,
32888  *         width: 200,
32889  *         height: 200,
32890  *         style: 'border: 1px solid red;'
32891  *     });
32892  *
32893  *     Ext.create('Ext.fx.Anim', {
32894  *         target: myComponent,
32895  *         duration: 1000,
32896  *         from: {
32897  *             width: 400 //starting width 400
32898  *         },
32899  *         to: {
32900  *             width: 300, //end width 300
32901  *             height: 300 // end width 300
32902  *         }
32903  *     });
32904  */
32905 Ext.define('Ext.fx.Anim', {
32906
32907     /* Begin Definitions */
32908
32909     mixins: {
32910         observable: 'Ext.util.Observable'
32911     },
32912
32913     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
32914
32915     /* End Definitions */
32916
32917     isAnimation: true,
32918
32919     /**
32920      * @cfg {Function} callback
32921      * A function to be run after the animation has completed.
32922      */
32923
32924     /**
32925      * @cfg {Function} scope
32926      * The scope that the {@link #callback} function will be called with
32927      */
32928
32929     /**
32930      * @cfg {Number} duration
32931      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
32932      * specified, then each animate will take the same duration for each iteration.
32933      */
32934     duration: 250,
32935
32936     /**
32937      * @cfg {Number} delay
32938      * Time to delay before starting the animation. Defaults to 0.
32939      */
32940     delay: 0,
32941
32942     /* private used to track a delayed starting time */
32943     delayStart: 0,
32944
32945     /**
32946      * @cfg {Boolean} dynamic
32947      * 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.
32948      */
32949     dynamic: false,
32950
32951     /**
32952      * @cfg {String} easing
32953 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
32954 speed over its duration.
32955
32956          -backIn
32957          -backOut
32958          -bounceIn
32959          -bounceOut
32960          -ease
32961          -easeIn
32962          -easeOut
32963          -easeInOut
32964          -elasticIn
32965          -elasticOut
32966          -cubic-bezier(x1, y1, x2, y2)
32967
32968 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
32969 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
32970 be in the range [0, 1] or the definition is invalid.
32971
32972 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
32973
32974      * @markdown
32975      */
32976     easing: 'ease',
32977
32978      /**
32979       * @cfg {Object} keyframes
32980       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
32981       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
32982       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
32983       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
32984       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
32985  <pre><code>
32986 keyframes : {
32987     '0%': {
32988         left: 100
32989     },
32990     '40%': {
32991         left: 150
32992     },
32993     '60%': {
32994         left: 75
32995     },
32996     '100%': {
32997         left: 100
32998     }
32999 }
33000  </code></pre>
33001       */
33002
33003     /**
33004      * @private
33005      */
33006     damper: 1,
33007
33008     /**
33009      * @private
33010      */
33011     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
33012
33013     /**
33014      * Run the animation from the end to the beginning
33015      * Defaults to false.
33016      * @cfg {Boolean} reverse
33017      */
33018     reverse: false,
33019
33020     /**
33021      * Flag to determine if the animation has started
33022      * @property running
33023      * @type Boolean
33024      */
33025     running: false,
33026
33027     /**
33028      * Flag to determine if the animation is paused. Only set this to true if you need to
33029      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
33030      * @property paused
33031      * @type Boolean
33032      */
33033     paused: false,
33034
33035     /**
33036      * Number of times to execute the animation. Defaults to 1.
33037      * @cfg {Number} iterations
33038      */
33039     iterations: 1,
33040
33041     /**
33042      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
33043      * @cfg {Boolean} alternate
33044      * Defaults to false.
33045      */
33046     alternate: false,
33047
33048     /**
33049      * Current iteration the animation is running.
33050      * @property currentIteration
33051      * @type Number
33052      */
33053     currentIteration: 0,
33054
33055     /**
33056      * Starting time of the animation.
33057      * @property startTime
33058      * @type Date
33059      */
33060     startTime: 0,
33061
33062     /**
33063      * Contains a cache of the interpolators to be used.
33064      * @private
33065      * @property propHandlers
33066      * @type Object
33067      */
33068
33069     /**
33070      * @cfg {String/Object} target
33071      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
33072      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
33073      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
33074      * automatically.
33075      */
33076
33077     /**
33078      * @cfg {Object} from
33079      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
33080      * Ext.fx.target will be used. For example:
33081 <pre><code>
33082 from : {
33083     opacity: 0,       // Transparent
33084     color: '#ffffff', // White
33085     left: 0
33086 }
33087 </code></pre>
33088      */
33089
33090     /**
33091      * @cfg {Object} to
33092      * An object containing property/value pairs for the end of the animation. For example:
33093  <pre><code>
33094  to : {
33095      opacity: 1,       // Opaque
33096      color: '#00ff00', // Green
33097      left: 500
33098  }
33099  </code></pre>
33100      */
33101
33102     // @private
33103     constructor: function(config) {
33104         var me = this,
33105             curve;
33106             
33107         config = config || {};
33108         // If keyframes are passed, they really want an Animator instead.
33109         if (config.keyframes) {
33110             return Ext.create('Ext.fx.Animator', config);
33111         }
33112         config = Ext.apply(me, config);
33113         if (me.from === undefined) {
33114             me.from = {};
33115         }
33116         me.propHandlers = {};
33117         me.config = config;
33118         me.target = Ext.fx.Manager.createTarget(me.target);
33119         me.easingFn = Ext.fx.Easing[me.easing];
33120         me.target.dynamic = me.dynamic;
33121
33122         // If not a pre-defined curve, try a cubic-bezier
33123         if (!me.easingFn) {
33124             me.easingFn = String(me.easing).match(me.bezierRE);
33125             if (me.easingFn && me.easingFn.length == 5) {
33126                 curve = me.easingFn;
33127                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
33128             }
33129         }
33130         me.id = Ext.id(null, 'ext-anim-');
33131         Ext.fx.Manager.addAnim(me);
33132         me.addEvents(
33133             /**
33134              * @event beforeanimate
33135              * Fires before the animation starts. A handler can return false to cancel the animation.
33136              * @param {Ext.fx.Anim} this
33137              */
33138             'beforeanimate',
33139              /**
33140               * @event afteranimate
33141               * Fires when the animation is complete.
33142               * @param {Ext.fx.Anim} this
33143               * @param {Date} startTime
33144               */
33145             'afteranimate',
33146              /**
33147               * @event lastframe
33148               * Fires when the animation's last frame has been set.
33149               * @param {Ext.fx.Anim} this
33150               * @param {Date} startTime
33151               */
33152             'lastframe'
33153         );
33154         me.mixins.observable.constructor.call(me, config);
33155         if (config.callback) {
33156             me.on('afteranimate', config.callback, config.scope);
33157         }
33158         return me;
33159     },
33160
33161     /**
33162      * @private
33163      * Helper to the target
33164      */
33165     setAttr: function(attr, value) {
33166         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
33167     },
33168
33169     /**
33170      * @private
33171      * Set up the initial currentAttrs hash.
33172      */
33173     initAttrs: function() {
33174         var me = this,
33175             from = me.from,
33176             to = me.to,
33177             initialFrom = me.initialFrom || {},
33178             out = {},
33179             start, end, propHandler, attr;
33180
33181         for (attr in to) {
33182             if (to.hasOwnProperty(attr)) {
33183                 start = me.target.getAttr(attr, from[attr]);
33184                 end = to[attr];
33185                 // Use default (numeric) property handler
33186                 if (!Ext.fx.PropertyHandler[attr]) {
33187                     if (Ext.isObject(end)) {
33188                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
33189                     } else {
33190                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
33191                     }
33192                 }
33193                 // Use custom handler
33194                 else {
33195                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
33196                 }
33197                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
33198             }
33199         }
33200         me.currentAttrs = out;
33201     },
33202
33203     /**
33204      * @private
33205      * Fires beforeanimate and sets the running flag.
33206      */
33207     start: function(startTime) {
33208         var me = this,
33209             delay = me.delay,
33210             delayStart = me.delayStart,
33211             delayDelta;
33212         if (delay) {
33213             if (!delayStart) {
33214                 me.delayStart = startTime;
33215                 return;
33216             }
33217             else {
33218                 delayDelta = startTime - delayStart;
33219                 if (delayDelta < delay) {
33220                     return;
33221                 }
33222                 else {
33223                     // Compensate for frame delay;
33224                     startTime = new Date(delayStart.getTime() + delay);
33225                 }
33226             }
33227         }
33228         if (me.fireEvent('beforeanimate', me) !== false) {
33229             me.startTime = startTime;
33230             if (!me.paused && !me.currentAttrs) {
33231                 me.initAttrs();
33232             }
33233             me.running = true;
33234         }
33235     },
33236
33237     /**
33238      * @private
33239      * Calculate attribute value at the passed timestamp.
33240      * @returns a hash of the new attributes.
33241      */
33242     runAnim: function(elapsedTime) {
33243         var me = this,
33244             attrs = me.currentAttrs,
33245             duration = me.duration,
33246             easingFn = me.easingFn,
33247             propHandlers = me.propHandlers,
33248             ret = {},
33249             easing, values, attr, lastFrame;
33250
33251         if (elapsedTime >= duration) {
33252             elapsedTime = duration;
33253             lastFrame = true;
33254         }
33255         if (me.reverse) {
33256             elapsedTime = duration - elapsedTime;
33257         }
33258
33259         for (attr in attrs) {
33260             if (attrs.hasOwnProperty(attr)) {
33261                 values = attrs[attr];
33262                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
33263                 ret[attr] = propHandlers[attr].set(values, easing);
33264             }
33265         }
33266         return ret;
33267     },
33268
33269     /**
33270      * @private
33271      * Perform lastFrame cleanup and handle iterations
33272      * @returns a hash of the new attributes.
33273      */
33274     lastFrame: function() {
33275         var me = this,
33276             iter = me.iterations,
33277             iterCount = me.currentIteration;
33278
33279         iterCount++;
33280         if (iterCount < iter) {
33281             if (me.alternate) {
33282                 me.reverse = !me.reverse;
33283             }
33284             me.startTime = new Date();
33285             me.currentIteration = iterCount;
33286             // Turn off paused for CSS3 Transitions
33287             me.paused = false;
33288         }
33289         else {
33290             me.currentIteration = 0;
33291             me.end();
33292             me.fireEvent('lastframe', me, me.startTime);
33293         }
33294     },
33295
33296     /**
33297      * Fire afteranimate event and end the animation. Usually called automatically when the
33298      * animation reaches its final frame, but can also be called manually to pre-emptively
33299      * stop and destroy the running animation.
33300      */
33301     end: function() {
33302         var me = this;
33303         me.startTime = 0;
33304         me.paused = false;
33305         me.running = false;
33306         Ext.fx.Manager.removeAnim(me);
33307         me.fireEvent('afteranimate', me, me.startTime);
33308     }
33309 });
33310 // Set flag to indicate that Fx is available. Class might not be available immediately.
33311 Ext.enableFx = true;
33312
33313 /*
33314  * This is a derivative of the similarly named class in the YUI Library.
33315  * The original license:
33316  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
33317  * Code licensed under the BSD License:
33318  * http://developer.yahoo.net/yui/license.txt
33319  */
33320
33321
33322 /**
33323  * Defines the interface and base operation of items that that can be
33324  * dragged or can be drop targets.  It was designed to be extended, overriding
33325  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
33326  * Up to three html elements can be associated with a DragDrop instance:
33327  *
33328  * - linked element: the element that is passed into the constructor.
33329  *   This is the element which defines the boundaries for interaction with
33330  *   other DragDrop objects.
33331  *
33332  * - handle element(s): The drag operation only occurs if the element that
33333  *   was clicked matches a handle element.  By default this is the linked
33334  *   element, but there are times that you will want only a portion of the
33335  *   linked element to initiate the drag operation, and the setHandleElId()
33336  *   method provides a way to define this.
33337  *
33338  * - drag element: this represents the element that would be moved along
33339  *   with the cursor during a drag operation.  By default, this is the linked
33340  *   element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
33341  *   a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
33342  *
33343  * This class should not be instantiated until the onload event to ensure that
33344  * the associated elements are available.
33345  * The following would define a DragDrop obj that would interact with any
33346  * other DragDrop obj in the "group1" group:
33347  *
33348  *     dd = new Ext.dd.DragDrop("div1", "group1");
33349  *
33350  * Since none of the event handlers have been implemented, nothing would
33351  * actually happen if you were to run the code above.  Normally you would
33352  * override this class or one of the default implementations, but you can
33353  * also override the methods you want on an instance of the class...
33354  *
33355  *     dd.onDragDrop = function(e, id) {
33356  *         alert("dd was dropped on " + id);
33357  *     }
33358  *
33359  */
33360 Ext.define('Ext.dd.DragDrop', {
33361     requires: ['Ext.dd.DragDropManager'],
33362
33363     /**
33364      * Creates new DragDrop.
33365      * @param {String} id of the element that is linked to this instance
33366      * @param {String} sGroup the group of related DragDrop objects
33367      * @param {Object} config an object containing configurable attributes.
33368      * Valid properties for DragDrop:
33369      *
33370      * - padding
33371      * - isTarget
33372      * - maintainOffset
33373      * - primaryButtonOnly
33374      */
33375     constructor: function(id, sGroup, config) {
33376         if(id) {
33377             this.init(id, sGroup, config);
33378         }
33379     },
33380
33381     /**
33382      * Set to false to enable a DragDrop object to fire drag events while dragging
33383      * over its own Element. Defaults to true - DragDrop objects do not by default
33384      * fire drag events to themselves.
33385      * @property ignoreSelf
33386      * @type Boolean
33387      */
33388
33389     /**
33390      * The id of the element associated with this object.  This is what we
33391      * refer to as the "linked element" because the size and position of
33392      * this element is used to determine when the drag and drop objects have
33393      * interacted.
33394      * @property id
33395      * @type String
33396      */
33397     id: null,
33398
33399     /**
33400      * Configuration attributes passed into the constructor
33401      * @property config
33402      * @type Object
33403      */
33404     config: null,
33405
33406     /**
33407      * The id of the element that will be dragged.  By default this is same
33408      * as the linked element, but could be changed to another element. Ex:
33409      * Ext.dd.DDProxy
33410      * @property dragElId
33411      * @type String
33412      * @private
33413      */
33414     dragElId: null,
33415
33416     /**
33417      * The ID of the element that initiates the drag operation.  By default
33418      * this is the linked element, but could be changed to be a child of this
33419      * element.  This lets us do things like only starting the drag when the
33420      * header element within the linked html element is clicked.
33421      * @property handleElId
33422      * @type String
33423      * @private
33424      */
33425     handleElId: null,
33426
33427     /**
33428      * An object who's property names identify HTML tags to be considered invalid as drag handles.
33429      * A non-null property value identifies the tag as invalid. Defaults to the
33430      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
33431 {
33432     A: "A"
33433 }</code></pre>
33434      * @property invalidHandleTypes
33435      * @type Object
33436      */
33437     invalidHandleTypes: null,
33438
33439     /**
33440      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
33441      * A non-null property value identifies the ID as invalid. For example, to prevent
33442      * dragging from being initiated on element ID "foo", use:<pre><code>
33443 {
33444     foo: true
33445 }</code></pre>
33446      * @property invalidHandleIds
33447      * @type Object
33448      */
33449     invalidHandleIds: null,
33450
33451     /**
33452      * An Array of CSS class names for elements to be considered in valid as drag handles.
33453      * @property {String[]} invalidHandleClasses
33454      */
33455     invalidHandleClasses: null,
33456
33457     /**
33458      * The linked element's absolute X position at the time the drag was
33459      * started
33460      * @property startPageX
33461      * @type Number
33462      * @private
33463      */
33464     startPageX: 0,
33465
33466     /**
33467      * The linked element's absolute X position at the time the drag was
33468      * started
33469      * @property startPageY
33470      * @type Number
33471      * @private
33472      */
33473     startPageY: 0,
33474
33475     /**
33476      * The group defines a logical collection of DragDrop objects that are
33477      * related.  Instances only get events when interacting with other
33478      * DragDrop object in the same group.  This lets us define multiple
33479      * groups using a single DragDrop subclass if we want.
33480      * @property groups
33481      * @type Object An object in the format {'group1':true, 'group2':true}
33482      */
33483     groups: null,
33484
33485     /**
33486      * Individual drag/drop instances can be locked.  This will prevent
33487      * onmousedown start drag.
33488      * @property locked
33489      * @type Boolean
33490      * @private
33491      */
33492     locked: false,
33493
33494     /**
33495      * Locks this instance
33496      */
33497     lock: function() {
33498         this.locked = true;
33499     },
33500
33501     /**
33502      * When set to true, other DD objects in cooperating DDGroups do not receive
33503      * notification events when this DD object is dragged over them. Defaults to false.
33504      * @property moveOnly
33505      * @type Boolean
33506      */
33507     moveOnly: false,
33508
33509     /**
33510      * Unlocks this instace
33511      */
33512     unlock: function() {
33513         this.locked = false;
33514     },
33515
33516     /**
33517      * By default, all instances can be a drop target.  This can be disabled by
33518      * setting isTarget to false.
33519      * @property isTarget
33520      * @type Boolean
33521      */
33522     isTarget: true,
33523
33524     /**
33525      * The padding configured for this drag and drop object for calculating
33526      * the drop zone intersection with this object.
33527      * An array containing the 4 padding values: [top, right, bottom, left]
33528      * @property {Number[]} padding
33529      */
33530     padding: null,
33531
33532     /**
33533      * Cached reference to the linked element
33534      * @property _domRef
33535      * @private
33536      */
33537     _domRef: null,
33538
33539     /**
33540      * Internal typeof flag
33541      * @property __ygDragDrop
33542      * @private
33543      */
33544     __ygDragDrop: true,
33545
33546     /**
33547      * Set to true when horizontal contraints are applied
33548      * @property constrainX
33549      * @type Boolean
33550      * @private
33551      */
33552     constrainX: false,
33553
33554     /**
33555      * Set to true when vertical contraints are applied
33556      * @property constrainY
33557      * @type Boolean
33558      * @private
33559      */
33560     constrainY: false,
33561
33562     /**
33563      * The left constraint
33564      * @property minX
33565      * @type Number
33566      * @private
33567      */
33568     minX: 0,
33569
33570     /**
33571      * The right constraint
33572      * @property maxX
33573      * @type Number
33574      * @private
33575      */
33576     maxX: 0,
33577
33578     /**
33579      * The up constraint
33580      * @property minY
33581      * @type Number
33582      * @private
33583      */
33584     minY: 0,
33585
33586     /**
33587      * The down constraint
33588      * @property maxY
33589      * @type Number
33590      * @private
33591      */
33592     maxY: 0,
33593
33594     /**
33595      * Maintain offsets when we resetconstraints.  Set to true when you want
33596      * the position of the element relative to its parent to stay the same
33597      * when the page changes
33598      *
33599      * @property maintainOffset
33600      * @type Boolean
33601      */
33602     maintainOffset: false,
33603
33604     /**
33605      * Array of pixel locations the element will snap to if we specified a
33606      * horizontal graduation/interval.  This array is generated automatically
33607      * when you define a tick interval.
33608      * @property {Number[]} xTicks
33609      */
33610     xTicks: null,
33611
33612     /**
33613      * Array of pixel locations the element will snap to if we specified a
33614      * vertical graduation/interval.  This array is generated automatically
33615      * when you define a tick interval.
33616      * @property {Number[]} yTicks
33617      */
33618     yTicks: null,
33619
33620     /**
33621      * By default the drag and drop instance will only respond to the primary
33622      * button click (left button for a right-handed mouse).  Set to true to
33623      * allow drag and drop to start with any mouse click that is propogated
33624      * by the browser
33625      * @property primaryButtonOnly
33626      * @type Boolean
33627      */
33628     primaryButtonOnly: true,
33629
33630     /**
33631      * The available property is false until the linked dom element is accessible.
33632      * @property available
33633      * @type Boolean
33634      */
33635     available: false,
33636
33637     /**
33638      * By default, drags can only be initiated if the mousedown occurs in the
33639      * region the linked element is.  This is done in part to work around a
33640      * bug in some browsers that mis-report the mousedown if the previous
33641      * mouseup happened outside of the window.  This property is set to true
33642      * if outer handles are defined. Defaults to false.
33643      *
33644      * @property hasOuterHandles
33645      * @type Boolean
33646      */
33647     hasOuterHandles: false,
33648
33649     /**
33650      * Code that executes immediately before the startDrag event
33651      * @private
33652      */
33653     b4StartDrag: function(x, y) { },
33654
33655     /**
33656      * Abstract method called after a drag/drop object is clicked
33657      * and the drag or mousedown time thresholds have beeen met.
33658      * @param {Number} X click location
33659      * @param {Number} Y click location
33660      */
33661     startDrag: function(x, y) { /* override this */ },
33662
33663     /**
33664      * Code that executes immediately before the onDrag event
33665      * @private
33666      */
33667     b4Drag: function(e) { },
33668
33669     /**
33670      * Abstract method called during the onMouseMove event while dragging an
33671      * object.
33672      * @param {Event} e the mousemove event
33673      */
33674     onDrag: function(e) { /* override this */ },
33675
33676     /**
33677      * Abstract method called when this element fist begins hovering over
33678      * another DragDrop obj
33679      * @param {Event} e the mousemove event
33680      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
33681      * id this is hovering over.  In INTERSECT mode, an array of one or more
33682      * dragdrop items being hovered over.
33683      */
33684     onDragEnter: function(e, id) { /* override this */ },
33685
33686     /**
33687      * Code that executes immediately before the onDragOver event
33688      * @private
33689      */
33690     b4DragOver: function(e) { },
33691
33692     /**
33693      * Abstract method called when this element is hovering over another
33694      * DragDrop obj
33695      * @param {Event} e the mousemove event
33696      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
33697      * id this is hovering over.  In INTERSECT mode, an array of dd items
33698      * being hovered over.
33699      */
33700     onDragOver: function(e, id) { /* override this */ },
33701
33702     /**
33703      * Code that executes immediately before the onDragOut event
33704      * @private
33705      */
33706     b4DragOut: function(e) { },
33707
33708     /**
33709      * Abstract method called when we are no longer hovering over an element
33710      * @param {Event} e the mousemove event
33711      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
33712      * id this was hovering over.  In INTERSECT mode, an array of dd items
33713      * that the mouse is no longer over.
33714      */
33715     onDragOut: function(e, id) { /* override this */ },
33716
33717     /**
33718      * Code that executes immediately before the onDragDrop event
33719      * @private
33720      */
33721     b4DragDrop: function(e) { },
33722
33723     /**
33724      * Abstract method called when this item is dropped on another DragDrop
33725      * obj
33726      * @param {Event} e the mouseup event
33727      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
33728      * id this was dropped on.  In INTERSECT mode, an array of dd items this
33729      * was dropped on.
33730      */
33731     onDragDrop: function(e, id) { /* override this */ },
33732
33733     /**
33734      * Abstract method called when this item is dropped on an area with no
33735      * drop target
33736      * @param {Event} e the mouseup event
33737      */
33738     onInvalidDrop: function(e) { /* override this */ },
33739
33740     /**
33741      * Code that executes immediately before the endDrag event
33742      * @private
33743      */
33744     b4EndDrag: function(e) { },
33745
33746     /**
33747      * Called when we are done dragging the object
33748      * @param {Event} e the mouseup event
33749      */
33750     endDrag: function(e) { /* override this */ },
33751
33752     /**
33753      * Code executed immediately before the onMouseDown event
33754      * @param {Event} e the mousedown event
33755      * @private
33756      */
33757     b4MouseDown: function(e) {  },
33758
33759     /**
33760      * Called when a drag/drop obj gets a mousedown
33761      * @param {Event} e the mousedown event
33762      */
33763     onMouseDown: function(e) { /* override this */ },
33764
33765     /**
33766      * Called when a drag/drop obj gets a mouseup
33767      * @param {Event} e the mouseup event
33768      */
33769     onMouseUp: function(e) { /* override this */ },
33770
33771     /**
33772      * Override the onAvailable method to do what is needed after the initial
33773      * position was determined.
33774      */
33775     onAvailable: function () {
33776     },
33777
33778     /**
33779      * @property {Object} defaultPadding
33780      * Provides default constraint padding to "constrainTo" elements.
33781      */
33782     defaultPadding: {
33783         left: 0,
33784         right: 0,
33785         top: 0,
33786         bottom: 0
33787     },
33788
33789     /**
33790      * Initializes the drag drop object's constraints to restrict movement to a certain element.
33791      *
33792      * Usage:
33793      *
33794      *     var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
33795      *                    { dragElId: "existingProxyDiv" });
33796      *     dd.startDrag = function(){
33797      *         this.constrainTo("parent-id");
33798      *     };
33799      *
33800      * Or you can initalize it using the {@link Ext.Element} object:
33801      *
33802      *     Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
33803      *         startDrag : function(){
33804      *             this.constrainTo("parent-id");
33805      *         }
33806      *     });
33807      *
33808      * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
33809      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
33810      * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
33811      * an object containing the sides to pad. For example: `{right:10, bottom:10}`
33812      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
33813      */
33814     constrainTo : function(constrainTo, pad, inContent){
33815         if(Ext.isNumber(pad)){
33816             pad = {left: pad, right:pad, top:pad, bottom:pad};
33817         }
33818         pad = pad || this.defaultPadding;
33819         var b = Ext.get(this.getEl()).getBox(),
33820             ce = Ext.get(constrainTo),
33821             s = ce.getScroll(),
33822             c,
33823             cd = ce.dom;
33824         if(cd == document.body){
33825             c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
33826         }else{
33827             var xy = ce.getXY();
33828             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
33829         }
33830
33831
33832         var topSpace = b.y - c.y,
33833             leftSpace = b.x - c.x;
33834
33835         this.resetConstraints();
33836         this.setXConstraint(leftSpace - (pad.left||0), // left
33837                 c.width - leftSpace - b.width - (pad.right||0), //right
33838                                 this.xTickSize
33839         );
33840         this.setYConstraint(topSpace - (pad.top||0), //top
33841                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
33842                                 this.yTickSize
33843         );
33844     },
33845
33846     /**
33847      * Returns a reference to the linked element
33848      * @return {HTMLElement} the html element
33849      */
33850     getEl: function() {
33851         if (!this._domRef) {
33852             this._domRef = Ext.getDom(this.id);
33853         }
33854
33855         return this._domRef;
33856     },
33857
33858     /**
33859      * Returns a reference to the actual element to drag.  By default this is
33860      * the same as the html element, but it can be assigned to another
33861      * element. An example of this can be found in Ext.dd.DDProxy
33862      * @return {HTMLElement} the html element
33863      */
33864     getDragEl: function() {
33865         return Ext.getDom(this.dragElId);
33866     },
33867
33868     /**
33869      * Sets up the DragDrop object.  Must be called in the constructor of any
33870      * Ext.dd.DragDrop subclass
33871      * @param {String} id the id of the linked element
33872      * @param {String} sGroup the group of related items
33873      * @param {Object} config configuration attributes
33874      */
33875     init: function(id, sGroup, config) {
33876         this.initTarget(id, sGroup, config);
33877         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
33878         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
33879     },
33880
33881     /**
33882      * Initializes Targeting functionality only... the object does not
33883      * get a mousedown handler.
33884      * @param {String} id the id of the linked element
33885      * @param {String} sGroup the group of related items
33886      * @param {Object} config configuration attributes
33887      */
33888     initTarget: function(id, sGroup, config) {
33889         // configuration attributes
33890         this.config = config || {};
33891
33892         // create a local reference to the drag and drop manager
33893         this.DDMInstance = Ext.dd.DragDropManager;
33894         // initialize the groups array
33895         this.groups = {};
33896
33897         // assume that we have an element reference instead of an id if the
33898         // parameter is not a string
33899         if (typeof id !== "string") {
33900             id = Ext.id(id);
33901         }
33902
33903         // set the id
33904         this.id = id;
33905
33906         // add to an interaction group
33907         this.addToGroup((sGroup) ? sGroup : "default");
33908
33909         // We don't want to register this as the handle with the manager
33910         // so we just set the id rather than calling the setter.
33911         this.handleElId = id;
33912
33913         // the linked element is the element that gets dragged by default
33914         this.setDragElId(id);
33915
33916         // by default, clicked anchors will not start drag operations.
33917         this.invalidHandleTypes = { A: "A" };
33918         this.invalidHandleIds = {};
33919         this.invalidHandleClasses = [];
33920
33921         this.applyConfig();
33922
33923         this.handleOnAvailable();
33924     },
33925
33926     /**
33927      * Applies the configuration parameters that were passed into the constructor.
33928      * This is supposed to happen at each level through the inheritance chain.  So
33929      * a DDProxy implentation will execute apply config on DDProxy, DD, and
33930      * DragDrop in order to get all of the parameters that are available in
33931      * each object.
33932      */
33933     applyConfig: function() {
33934
33935         // configurable properties:
33936         //    padding, isTarget, maintainOffset, primaryButtonOnly
33937         this.padding           = this.config.padding || [0, 0, 0, 0];
33938         this.isTarget          = (this.config.isTarget !== false);
33939         this.maintainOffset    = (this.config.maintainOffset);
33940         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
33941
33942     },
33943
33944     /**
33945      * Executed when the linked element is available
33946      * @private
33947      */
33948     handleOnAvailable: function() {
33949         this.available = true;
33950         this.resetConstraints();
33951         this.onAvailable();
33952     },
33953
33954     /**
33955      * Configures the padding for the target zone in px.  Effectively expands
33956      * (or reduces) the virtual object size for targeting calculations.
33957      * Supports css-style shorthand; if only one parameter is passed, all sides
33958      * will have that padding, and if only two are passed, the top and bottom
33959      * will have the first param, the left and right the second.
33960      * @param {Number} iTop    Top pad
33961      * @param {Number} iRight  Right pad
33962      * @param {Number} iBot    Bot pad
33963      * @param {Number} iLeft   Left pad
33964      */
33965     setPadding: function(iTop, iRight, iBot, iLeft) {
33966         // this.padding = [iLeft, iRight, iTop, iBot];
33967         if (!iRight && 0 !== iRight) {
33968             this.padding = [iTop, iTop, iTop, iTop];
33969         } else if (!iBot && 0 !== iBot) {
33970             this.padding = [iTop, iRight, iTop, iRight];
33971         } else {
33972             this.padding = [iTop, iRight, iBot, iLeft];
33973         }
33974     },
33975
33976     /**
33977      * Stores the initial placement of the linked element.
33978      * @param {Number} diffX   the X offset, default 0
33979      * @param {Number} diffY   the Y offset, default 0
33980      */
33981     setInitPosition: function(diffX, diffY) {
33982         var el = this.getEl();
33983
33984         if (!this.DDMInstance.verifyEl(el)) {
33985             return;
33986         }
33987
33988         var dx = diffX || 0;
33989         var dy = diffY || 0;
33990
33991         var p = Ext.Element.getXY( el );
33992
33993         this.initPageX = p[0] - dx;
33994         this.initPageY = p[1] - dy;
33995
33996         this.lastPageX = p[0];
33997         this.lastPageY = p[1];
33998
33999         this.setStartPosition(p);
34000     },
34001
34002     /**
34003      * Sets the start position of the element.  This is set when the obj
34004      * is initialized, the reset when a drag is started.
34005      * @param pos current position (from previous lookup)
34006      * @private
34007      */
34008     setStartPosition: function(pos) {
34009         var p = pos || Ext.Element.getXY( this.getEl() );
34010         this.deltaSetXY = null;
34011
34012         this.startPageX = p[0];
34013         this.startPageY = p[1];
34014     },
34015
34016     /**
34017      * Adds this instance to a group of related drag/drop objects.  All
34018      * instances belong to at least one group, and can belong to as many
34019      * groups as needed.
34020      * @param {String} sGroup the name of the group
34021      */
34022     addToGroup: function(sGroup) {
34023         this.groups[sGroup] = true;
34024         this.DDMInstance.regDragDrop(this, sGroup);
34025     },
34026
34027     /**
34028      * Removes this instance from the supplied interaction group
34029      * @param {String} sGroup  The group to drop
34030      */
34031     removeFromGroup: function(sGroup) {
34032         if (this.groups[sGroup]) {
34033             delete this.groups[sGroup];
34034         }
34035
34036         this.DDMInstance.removeDDFromGroup(this, sGroup);
34037     },
34038
34039     /**
34040      * Allows you to specify that an element other than the linked element
34041      * will be moved with the cursor during a drag
34042      * @param {String} id the id of the element that will be used to initiate the drag
34043      */
34044     setDragElId: function(id) {
34045         this.dragElId = id;
34046     },
34047
34048     /**
34049      * Allows you to specify a child of the linked element that should be
34050      * used to initiate the drag operation.  An example of this would be if
34051      * you have a content div with text and links.  Clicking anywhere in the
34052      * content area would normally start the drag operation.  Use this method
34053      * to specify that an element inside of the content div is the element
34054      * that starts the drag operation.
34055      * @param {String} id the id of the element that will be used to
34056      * initiate the drag.
34057      */
34058     setHandleElId: function(id) {
34059         if (typeof id !== "string") {
34060             id = Ext.id(id);
34061         }
34062         this.handleElId = id;
34063         this.DDMInstance.regHandle(this.id, id);
34064     },
34065
34066     /**
34067      * Allows you to set an element outside of the linked element as a drag
34068      * handle
34069      * @param {String} id the id of the element that will be used to initiate the drag
34070      */
34071     setOuterHandleElId: function(id) {
34072         if (typeof id !== "string") {
34073             id = Ext.id(id);
34074         }
34075         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
34076         this.setHandleElId(id);
34077
34078         this.hasOuterHandles = true;
34079     },
34080
34081     /**
34082      * Removes all drag and drop hooks for this element
34083      */
34084     unreg: function() {
34085         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
34086         this._domRef = null;
34087         this.DDMInstance._remove(this);
34088     },
34089
34090     destroy : function(){
34091         this.unreg();
34092     },
34093
34094     /**
34095      * Returns true if this instance is locked, or the drag drop mgr is locked
34096      * (meaning that all drag/drop is disabled on the page.)
34097      * @return {Boolean} true if this obj or all drag/drop is locked, else
34098      * false
34099      */
34100     isLocked: function() {
34101         return (this.DDMInstance.isLocked() || this.locked);
34102     },
34103
34104     /**
34105      * Called when this object is clicked
34106      * @param {Event} e
34107      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
34108      * @private
34109      */
34110     handleMouseDown: function(e, oDD){
34111         if (this.primaryButtonOnly && e.button != 0) {
34112             return;
34113         }
34114
34115         if (this.isLocked()) {
34116             return;
34117         }
34118
34119         this.DDMInstance.refreshCache(this.groups);
34120
34121         var pt = e.getPoint();
34122         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
34123         } else {
34124             if (this.clickValidator(e)) {
34125                 // set the initial element position
34126                 this.setStartPosition();
34127                 this.b4MouseDown(e);
34128                 this.onMouseDown(e);
34129
34130                 this.DDMInstance.handleMouseDown(e, this);
34131
34132                 this.DDMInstance.stopEvent(e);
34133             } else {
34134
34135
34136             }
34137         }
34138     },
34139
34140     clickValidator: function(e) {
34141         var target = e.getTarget();
34142         return ( this.isValidHandleChild(target) &&
34143                     (this.id == this.handleElId ||
34144                         this.DDMInstance.handleWasClicked(target, this.id)) );
34145     },
34146
34147     /**
34148      * Allows you to specify a tag name that should not start a drag operation
34149      * when clicked.  This is designed to facilitate embedding links within a
34150      * drag handle that do something other than start the drag.
34151      * @method addInvalidHandleType
34152      * @param {String} tagName the type of element to exclude
34153      */
34154     addInvalidHandleType: function(tagName) {
34155         var type = tagName.toUpperCase();
34156         this.invalidHandleTypes[type] = type;
34157     },
34158
34159     /**
34160      * Lets you to specify an element id for a child of a drag handle
34161      * that should not initiate a drag
34162      * @method addInvalidHandleId
34163      * @param {String} id the element id of the element you wish to ignore
34164      */
34165     addInvalidHandleId: function(id) {
34166         if (typeof id !== "string") {
34167             id = Ext.id(id);
34168         }
34169         this.invalidHandleIds[id] = id;
34170     },
34171
34172     /**
34173      * Lets you specify a css class of elements that will not initiate a drag
34174      * @param {String} cssClass the class of the elements you wish to ignore
34175      */
34176     addInvalidHandleClass: function(cssClass) {
34177         this.invalidHandleClasses.push(cssClass);
34178     },
34179
34180     /**
34181      * Unsets an excluded tag name set by addInvalidHandleType
34182      * @param {String} tagName the type of element to unexclude
34183      */
34184     removeInvalidHandleType: function(tagName) {
34185         var type = tagName.toUpperCase();
34186         // this.invalidHandleTypes[type] = null;
34187         delete this.invalidHandleTypes[type];
34188     },
34189
34190     /**
34191      * Unsets an invalid handle id
34192      * @param {String} id the id of the element to re-enable
34193      */
34194     removeInvalidHandleId: function(id) {
34195         if (typeof id !== "string") {
34196             id = Ext.id(id);
34197         }
34198         delete this.invalidHandleIds[id];
34199     },
34200
34201     /**
34202      * Unsets an invalid css class
34203      * @param {String} cssClass the class of the element(s) you wish to
34204      * re-enable
34205      */
34206     removeInvalidHandleClass: function(cssClass) {
34207         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
34208             if (this.invalidHandleClasses[i] == cssClass) {
34209                 delete this.invalidHandleClasses[i];
34210             }
34211         }
34212     },
34213
34214     /**
34215      * Checks the tag exclusion list to see if this click should be ignored
34216      * @param {HTMLElement} node the HTMLElement to evaluate
34217      * @return {Boolean} true if this is a valid tag type, false if not
34218      */
34219     isValidHandleChild: function(node) {
34220
34221         var valid = true;
34222         // var n = (node.nodeName == "#text") ? node.parentNode : node;
34223         var nodeName;
34224         try {
34225             nodeName = node.nodeName.toUpperCase();
34226         } catch(e) {
34227             nodeName = node.nodeName;
34228         }
34229         valid = valid && !this.invalidHandleTypes[nodeName];
34230         valid = valid && !this.invalidHandleIds[node.id];
34231
34232         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
34233             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
34234         }
34235
34236
34237         return valid;
34238
34239     },
34240
34241     /**
34242      * Creates the array of horizontal tick marks if an interval was specified
34243      * in setXConstraint().
34244      * @private
34245      */
34246     setXTicks: function(iStartX, iTickSize) {
34247         this.xTicks = [];
34248         this.xTickSize = iTickSize;
34249
34250         var tickMap = {};
34251
34252         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
34253             if (!tickMap[i]) {
34254                 this.xTicks[this.xTicks.length] = i;
34255                 tickMap[i] = true;
34256             }
34257         }
34258
34259         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
34260             if (!tickMap[i]) {
34261                 this.xTicks[this.xTicks.length] = i;
34262                 tickMap[i] = true;
34263             }
34264         }
34265
34266         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
34267     },
34268
34269     /**
34270      * Creates the array of vertical tick marks if an interval was specified in
34271      * setYConstraint().
34272      * @private
34273      */
34274     setYTicks: function(iStartY, iTickSize) {
34275         this.yTicks = [];
34276         this.yTickSize = iTickSize;
34277
34278         var tickMap = {};
34279
34280         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
34281             if (!tickMap[i]) {
34282                 this.yTicks[this.yTicks.length] = i;
34283                 tickMap[i] = true;
34284             }
34285         }
34286
34287         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
34288             if (!tickMap[i]) {
34289                 this.yTicks[this.yTicks.length] = i;
34290                 tickMap[i] = true;
34291             }
34292         }
34293
34294         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
34295     },
34296
34297     /**
34298      * By default, the element can be dragged any place on the screen.  Use
34299      * this method to limit the horizontal travel of the element.  Pass in
34300      * 0,0 for the parameters if you want to lock the drag to the y axis.
34301      * @param {Number} iLeft the number of pixels the element can move to the left
34302      * @param {Number} iRight the number of pixels the element can move to the
34303      * right
34304      * @param {Number} iTickSize (optional) parameter for specifying that the
34305      * element should move iTickSize pixels at a time.
34306      */
34307     setXConstraint: function(iLeft, iRight, iTickSize) {
34308         this.leftConstraint = iLeft;
34309         this.rightConstraint = iRight;
34310
34311         this.minX = this.initPageX - iLeft;
34312         this.maxX = this.initPageX + iRight;
34313         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
34314
34315         this.constrainX = true;
34316     },
34317
34318     /**
34319      * Clears any constraints applied to this instance.  Also clears ticks
34320      * since they can't exist independent of a constraint at this time.
34321      */
34322     clearConstraints: function() {
34323         this.constrainX = false;
34324         this.constrainY = false;
34325         this.clearTicks();
34326     },
34327
34328     /**
34329      * Clears any tick interval defined for this instance
34330      */
34331     clearTicks: function() {
34332         this.xTicks = null;
34333         this.yTicks = null;
34334         this.xTickSize = 0;
34335         this.yTickSize = 0;
34336     },
34337
34338     /**
34339      * By default, the element can be dragged any place on the screen.  Set
34340      * this to limit the vertical travel of the element.  Pass in 0,0 for the
34341      * parameters if you want to lock the drag to the x axis.
34342      * @param {Number} iUp the number of pixels the element can move up
34343      * @param {Number} iDown the number of pixels the element can move down
34344      * @param {Number} iTickSize (optional) parameter for specifying that the
34345      * element should move iTickSize pixels at a time.
34346      */
34347     setYConstraint: function(iUp, iDown, iTickSize) {
34348         this.topConstraint = iUp;
34349         this.bottomConstraint = iDown;
34350
34351         this.minY = this.initPageY - iUp;
34352         this.maxY = this.initPageY + iDown;
34353         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
34354
34355         this.constrainY = true;
34356
34357     },
34358
34359     /**
34360      * Must be called if you manually reposition a dd element.
34361      * @param {Boolean} maintainOffset
34362      */
34363     resetConstraints: function() {
34364         // Maintain offsets if necessary
34365         if (this.initPageX || this.initPageX === 0) {
34366             // figure out how much this thing has moved
34367             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
34368             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
34369
34370             this.setInitPosition(dx, dy);
34371
34372         // This is the first time we have detected the element's position
34373         } else {
34374             this.setInitPosition();
34375         }
34376
34377         if (this.constrainX) {
34378             this.setXConstraint( this.leftConstraint,
34379                                  this.rightConstraint,
34380                                  this.xTickSize        );
34381         }
34382
34383         if (this.constrainY) {
34384             this.setYConstraint( this.topConstraint,
34385                                  this.bottomConstraint,
34386                                  this.yTickSize         );
34387         }
34388     },
34389
34390     /**
34391      * Normally the drag element is moved pixel by pixel, but we can specify
34392      * that it move a number of pixels at a time.  This method resolves the
34393      * location when we have it set up like this.
34394      * @param {Number} val where we want to place the object
34395      * @param {Number[]} tickArray sorted array of valid points
34396      * @return {Number} the closest tick
34397      * @private
34398      */
34399     getTick: function(val, tickArray) {
34400         if (!tickArray) {
34401             // If tick interval is not defined, it is effectively 1 pixel,
34402             // so we return the value passed to us.
34403             return val;
34404         } else if (tickArray[0] >= val) {
34405             // The value is lower than the first tick, so we return the first
34406             // tick.
34407             return tickArray[0];
34408         } else {
34409             for (var i=0, len=tickArray.length; i<len; ++i) {
34410                 var next = i + 1;
34411                 if (tickArray[next] && tickArray[next] >= val) {
34412                     var diff1 = val - tickArray[i];
34413                     var diff2 = tickArray[next] - val;
34414                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
34415                 }
34416             }
34417
34418             // The value is larger than the last tick, so we return the last
34419             // tick.
34420             return tickArray[tickArray.length - 1];
34421         }
34422     },
34423
34424     /**
34425      * toString method
34426      * @return {String} string representation of the dd obj
34427      */
34428     toString: function() {
34429         return ("DragDrop " + this.id);
34430     }
34431
34432 });
34433
34434 /*
34435  * This is a derivative of the similarly named class in the YUI Library.
34436  * The original license:
34437  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
34438  * Code licensed under the BSD License:
34439  * http://developer.yahoo.net/yui/license.txt
34440  */
34441
34442
34443 /**
34444  * @class Ext.dd.DD
34445  * A DragDrop implementation where the linked element follows the
34446  * mouse cursor during a drag.
34447  * @extends Ext.dd.DragDrop
34448  */
34449 Ext.define('Ext.dd.DD', {
34450     extend: 'Ext.dd.DragDrop',
34451     requires: ['Ext.dd.DragDropManager'],
34452
34453     /**
34454      * Creates new DD instance.
34455      * @param {String} id the id of the linked element
34456      * @param {String} sGroup the group of related DragDrop items
34457      * @param {Object} config an object containing configurable attributes.
34458      * Valid properties for DD: scroll
34459      */
34460     constructor: function(id, sGroup, config) {
34461         if (id) {
34462             this.init(id, sGroup, config);
34463         }
34464     },
34465
34466     /**
34467      * When set to true, the utility automatically tries to scroll the browser
34468      * window when a drag and drop element is dragged near the viewport boundary.
34469      * Defaults to true.
34470      * @property scroll
34471      * @type Boolean
34472      */
34473     scroll: true,
34474
34475     /**
34476      * Sets the pointer offset to the distance between the linked element's top
34477      * left corner and the location the element was clicked
34478      * @method autoOffset
34479      * @param {Number} iPageX the X coordinate of the click
34480      * @param {Number} iPageY the Y coordinate of the click
34481      */
34482     autoOffset: function(iPageX, iPageY) {
34483         var x = iPageX - this.startPageX;
34484         var y = iPageY - this.startPageY;
34485         this.setDelta(x, y);
34486     },
34487
34488     /**
34489      * Sets the pointer offset.  You can call this directly to force the
34490      * offset to be in a particular location (e.g., pass in 0,0 to set it
34491      * to the center of the object)
34492      * @method setDelta
34493      * @param {Number} iDeltaX the distance from the left
34494      * @param {Number} iDeltaY the distance from the top
34495      */
34496     setDelta: function(iDeltaX, iDeltaY) {
34497         this.deltaX = iDeltaX;
34498         this.deltaY = iDeltaY;
34499     },
34500
34501     /**
34502      * Sets the drag element to the location of the mousedown or click event,
34503      * maintaining the cursor location relative to the location on the element
34504      * that was clicked.  Override this if you want to place the element in a
34505      * location other than where the cursor is.
34506      * @method setDragElPos
34507      * @param {Number} iPageX the X coordinate of the mousedown or drag event
34508      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
34509      */
34510     setDragElPos: function(iPageX, iPageY) {
34511         // the first time we do this, we are going to check to make sure
34512         // the element has css positioning
34513
34514         var el = this.getDragEl();
34515         this.alignElWithMouse(el, iPageX, iPageY);
34516     },
34517
34518     /**
34519      * Sets the element to the location of the mousedown or click event,
34520      * maintaining the cursor location relative to the location on the element
34521      * that was clicked.  Override this if you want to place the element in a
34522      * location other than where the cursor is.
34523      * @method alignElWithMouse
34524      * @param {HTMLElement} el the element to move
34525      * @param {Number} iPageX the X coordinate of the mousedown or drag event
34526      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
34527      */
34528     alignElWithMouse: function(el, iPageX, iPageY) {
34529         var oCoord = this.getTargetCoord(iPageX, iPageY),
34530             fly = el.dom ? el : Ext.fly(el, '_dd'),
34531             elSize = fly.getSize(),
34532             EL = Ext.Element,
34533             vpSize;
34534
34535         if (!this.deltaSetXY) {
34536             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
34537             var aCoord = [
34538                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
34539                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
34540             ];
34541             fly.setXY(aCoord);
34542             var newLeft = fly.getLeft(true);
34543             var newTop  = fly.getTop(true);
34544             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
34545         } else {
34546             vpSize = this.cachedViewportSize;
34547             fly.setLeftTop(
34548                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
34549                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
34550             );
34551         }
34552
34553         this.cachePosition(oCoord.x, oCoord.y);
34554         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
34555         return oCoord;
34556     },
34557
34558     /**
34559      * Saves the most recent position so that we can reset the constraints and
34560      * tick marks on-demand.  We need to know this so that we can calculate the
34561      * number of pixels the element is offset from its original position.
34562      * @method cachePosition
34563      * @param {Number} iPageX (optional) the current x position (this just makes it so we
34564      * don't have to look it up again)
34565      * @param {Number} iPageY (optional) the current y position (this just makes it so we
34566      * don't have to look it up again)
34567      */
34568     cachePosition: function(iPageX, iPageY) {
34569         if (iPageX) {
34570             this.lastPageX = iPageX;
34571             this.lastPageY = iPageY;
34572         } else {
34573             var aCoord = Ext.Element.getXY(this.getEl());
34574             this.lastPageX = aCoord[0];
34575             this.lastPageY = aCoord[1];
34576         }
34577     },
34578
34579     /**
34580      * Auto-scroll the window if the dragged object has been moved beyond the
34581      * visible window boundary.
34582      * @method autoScroll
34583      * @param {Number} x the drag element's x position
34584      * @param {Number} y the drag element's y position
34585      * @param {Number} h the height of the drag element
34586      * @param {Number} w the width of the drag element
34587      * @private
34588      */
34589     autoScroll: function(x, y, h, w) {
34590
34591         if (this.scroll) {
34592             // The client height
34593             var clientH = Ext.Element.getViewHeight();
34594
34595             // The client width
34596             var clientW = Ext.Element.getViewWidth();
34597
34598             // The amt scrolled down
34599             var st = this.DDMInstance.getScrollTop();
34600
34601             // The amt scrolled right
34602             var sl = this.DDMInstance.getScrollLeft();
34603
34604             // Location of the bottom of the element
34605             var bot = h + y;
34606
34607             // Location of the right of the element
34608             var right = w + x;
34609
34610             // The distance from the cursor to the bottom of the visible area,
34611             // adjusted so that we don't scroll if the cursor is beyond the
34612             // element drag constraints
34613             var toBot = (clientH + st - y - this.deltaY);
34614
34615             // The distance from the cursor to the right of the visible area
34616             var toRight = (clientW + sl - x - this.deltaX);
34617
34618
34619             // How close to the edge the cursor must be before we scroll
34620             // var thresh = (document.all) ? 100 : 40;
34621             var thresh = 40;
34622
34623             // How many pixels to scroll per autoscroll op.  This helps to reduce
34624             // clunky scrolling. IE is more sensitive about this ... it needs this
34625             // value to be higher.
34626             var scrAmt = (document.all) ? 80 : 30;
34627
34628             // Scroll down if we are near the bottom of the visible page and the
34629             // obj extends below the crease
34630             if ( bot > clientH && toBot < thresh ) {
34631                 window.scrollTo(sl, st + scrAmt);
34632             }
34633
34634             // Scroll up if the window is scrolled down and the top of the object
34635             // goes above the top border
34636             if ( y < st && st > 0 && y - st < thresh ) {
34637                 window.scrollTo(sl, st - scrAmt);
34638             }
34639
34640             // Scroll right if the obj is beyond the right border and the cursor is
34641             // near the border.
34642             if ( right > clientW && toRight < thresh ) {
34643                 window.scrollTo(sl + scrAmt, st);
34644             }
34645
34646             // Scroll left if the window has been scrolled to the right and the obj
34647             // extends past the left border
34648             if ( x < sl && sl > 0 && x - sl < thresh ) {
34649                 window.scrollTo(sl - scrAmt, st);
34650             }
34651         }
34652     },
34653
34654     /**
34655      * Finds the location the element should be placed if we want to move
34656      * it to where the mouse location less the click offset would place us.
34657      * @method getTargetCoord
34658      * @param {Number} iPageX the X coordinate of the click
34659      * @param {Number} iPageY the Y coordinate of the click
34660      * @return an object that contains the coordinates (Object.x and Object.y)
34661      * @private
34662      */
34663     getTargetCoord: function(iPageX, iPageY) {
34664         var x = iPageX - this.deltaX;
34665         var y = iPageY - this.deltaY;
34666
34667         if (this.constrainX) {
34668             if (x < this.minX) {
34669                 x = this.minX;
34670             }
34671             if (x > this.maxX) {
34672                 x = this.maxX;
34673             }
34674         }
34675
34676         if (this.constrainY) {
34677             if (y < this.minY) {
34678                 y = this.minY;
34679             }
34680             if (y > this.maxY) {
34681                 y = this.maxY;
34682             }
34683         }
34684
34685         x = this.getTick(x, this.xTicks);
34686         y = this.getTick(y, this.yTicks);
34687
34688
34689         return {x: x, y: y};
34690     },
34691
34692     /**
34693      * Sets up config options specific to this class. Overrides
34694      * Ext.dd.DragDrop, but all versions of this method through the
34695      * inheritance chain are called
34696      */
34697     applyConfig: function() {
34698         this.callParent();
34699         this.scroll = (this.config.scroll !== false);
34700     },
34701
34702     /**
34703      * Event that fires prior to the onMouseDown event.  Overrides
34704      * Ext.dd.DragDrop.
34705      */
34706     b4MouseDown: function(e) {
34707         // this.resetConstraints();
34708         this.autoOffset(e.getPageX(), e.getPageY());
34709     },
34710
34711     /**
34712      * Event that fires prior to the onDrag event.  Overrides
34713      * Ext.dd.DragDrop.
34714      */
34715     b4Drag: function(e) {
34716         this.setDragElPos(e.getPageX(), e.getPageY());
34717     },
34718
34719     toString: function() {
34720         return ("DD " + this.id);
34721     }
34722
34723     //////////////////////////////////////////////////////////////////////////
34724     // Debugging ygDragDrop events that can be overridden
34725     //////////////////////////////////////////////////////////////////////////
34726     /*
34727     startDrag: function(x, y) {
34728     },
34729
34730     onDrag: function(e) {
34731     },
34732
34733     onDragEnter: function(e, id) {
34734     },
34735
34736     onDragOver: function(e, id) {
34737     },
34738
34739     onDragOut: function(e, id) {
34740     },
34741
34742     onDragDrop: function(e, id) {
34743     },
34744
34745     endDrag: function(e) {
34746     }
34747
34748     */
34749
34750 });
34751
34752 /*
34753  * This is a derivative of the similarly named class in the YUI Library.
34754  * The original license:
34755  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
34756  * Code licensed under the BSD License:
34757  * http://developer.yahoo.net/yui/license.txt
34758  */
34759
34760 /**
34761  * @class Ext.dd.DDProxy
34762  * @extends Ext.dd.DD
34763  * A DragDrop implementation that inserts an empty, bordered div into
34764  * the document that follows the cursor during drag operations.  At the time of
34765  * the click, the frame div is resized to the dimensions of the linked html
34766  * element, and moved to the exact location of the linked element.
34767  *
34768  * References to the "frame" element refer to the single proxy element that
34769  * was created to be dragged in place of all DDProxy elements on the
34770  * page.
34771  */
34772 Ext.define('Ext.dd.DDProxy', {
34773     extend: 'Ext.dd.DD',
34774
34775     statics: {
34776         /**
34777          * The default drag frame div id
34778          * @static
34779          */
34780         dragElId: "ygddfdiv"
34781     },
34782
34783     /**
34784      * Creates new DDProxy.
34785      * @param {String} id the id of the linked html element
34786      * @param {String} sGroup the group of related DragDrop objects
34787      * @param {Object} config an object containing configurable attributes.
34788      * Valid properties for DDProxy in addition to those in DragDrop:
34789      * 
34790      * - resizeFrame
34791      * - centerFrame
34792      * - dragElId
34793      */
34794     constructor: function(id, sGroup, config) {
34795         if (id) {
34796             this.init(id, sGroup, config);
34797             this.initFrame();
34798         }
34799     },
34800
34801     /**
34802      * By default we resize the drag frame to be the same size as the element
34803      * we want to drag (this is to get the frame effect).  We can turn it off
34804      * if we want a different behavior.
34805      * @property resizeFrame
34806      * @type Boolean
34807      */
34808     resizeFrame: true,
34809
34810     /**
34811      * By default the frame is positioned exactly where the drag element is, so
34812      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
34813      * you do not have constraints on the obj is to have the drag frame centered
34814      * around the cursor.  Set centerFrame to true for this effect.
34815      * @property centerFrame
34816      * @type Boolean
34817      */
34818     centerFrame: false,
34819
34820     /**
34821      * Creates the proxy element if it does not yet exist
34822      * @method createFrame
34823      */
34824     createFrame: function() {
34825         var self = this;
34826         var body = document.body;
34827
34828         if (!body || !body.firstChild) {
34829             setTimeout( function() { self.createFrame(); }, 50 );
34830             return;
34831         }
34832
34833         var div = this.getDragEl();
34834
34835         if (!div) {
34836             div    = document.createElement("div");
34837             div.id = this.dragElId;
34838             var s  = div.style;
34839
34840             s.position   = "absolute";
34841             s.visibility = "hidden";
34842             s.cursor     = "move";
34843             s.border     = "2px solid #aaa";
34844             s.zIndex     = 999;
34845
34846             // appendChild can blow up IE if invoked prior to the window load event
34847             // while rendering a table.  It is possible there are other scenarios
34848             // that would cause this to happen as well.
34849             body.insertBefore(div, body.firstChild);
34850         }
34851     },
34852
34853     /**
34854      * Initialization for the drag frame element.  Must be called in the
34855      * constructor of all subclasses
34856      * @method initFrame
34857      */
34858     initFrame: function() {
34859         this.createFrame();
34860     },
34861
34862     applyConfig: function() {
34863         this.callParent();
34864
34865         this.resizeFrame = (this.config.resizeFrame !== false);
34866         this.centerFrame = (this.config.centerFrame);
34867         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
34868     },
34869
34870     /**
34871      * Resizes the drag frame to the dimensions of the clicked object, positions
34872      * it over the object, and finally displays it
34873      * @method showFrame
34874      * @param {Number} iPageX X click position
34875      * @param {Number} iPageY Y click position
34876      * @private
34877      */
34878     showFrame: function(iPageX, iPageY) {
34879         var el = this.getEl();
34880         var dragEl = this.getDragEl();
34881         var s = dragEl.style;
34882
34883         this._resizeProxy();
34884
34885         if (this.centerFrame) {
34886             this.setDelta( Math.round(parseInt(s.width,  10)/2),
34887                            Math.round(parseInt(s.height, 10)/2) );
34888         }
34889
34890         this.setDragElPos(iPageX, iPageY);
34891
34892         Ext.fly(dragEl).show();
34893     },
34894
34895     /**
34896      * The proxy is automatically resized to the dimensions of the linked
34897      * element when a drag is initiated, unless resizeFrame is set to false
34898      * @method _resizeProxy
34899      * @private
34900      */
34901     _resizeProxy: function() {
34902         if (this.resizeFrame) {
34903             var el = this.getEl();
34904             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
34905         }
34906     },
34907
34908     // overrides Ext.dd.DragDrop
34909     b4MouseDown: function(e) {
34910         var x = e.getPageX();
34911         var y = e.getPageY();
34912         this.autoOffset(x, y);
34913         this.setDragElPos(x, y);
34914     },
34915
34916     // overrides Ext.dd.DragDrop
34917     b4StartDrag: function(x, y) {
34918         // show the drag frame
34919         this.showFrame(x, y);
34920     },
34921
34922     // overrides Ext.dd.DragDrop
34923     b4EndDrag: function(e) {
34924         Ext.fly(this.getDragEl()).hide();
34925     },
34926
34927     // overrides Ext.dd.DragDrop
34928     // By default we try to move the element to the last location of the frame.
34929     // This is so that the default behavior mirrors that of Ext.dd.DD.
34930     endDrag: function(e) {
34931
34932         var lel = this.getEl();
34933         var del = this.getDragEl();
34934
34935         // Show the drag frame briefly so we can get its position
34936         del.style.visibility = "";
34937
34938         this.beforeMove();
34939         // Hide the linked element before the move to get around a Safari
34940         // rendering bug.
34941         lel.style.visibility = "hidden";
34942         Ext.dd.DDM.moveToEl(lel, del);
34943         del.style.visibility = "hidden";
34944         lel.style.visibility = "";
34945
34946         this.afterDrag();
34947     },
34948
34949     beforeMove : function(){
34950
34951     },
34952
34953     afterDrag : function(){
34954
34955     },
34956
34957     toString: function() {
34958         return ("DDProxy " + this.id);
34959     }
34960
34961 });
34962
34963 /**
34964  * @class Ext.dd.DragSource
34965  * @extends Ext.dd.DDProxy
34966  * A simple class that provides the basic implementation needed to make any element draggable.
34967  */
34968 Ext.define('Ext.dd.DragSource', {
34969     extend: 'Ext.dd.DDProxy',
34970     requires: [
34971         'Ext.dd.StatusProxy',
34972         'Ext.dd.DragDropManager'
34973     ],
34974
34975     /**
34976      * @cfg {String} ddGroup
34977      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
34978      * interact with other drag drop objects in the same group.
34979      */
34980
34981     /**
34982      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
34983      * The CSS class returned to the drag source when drop is allowed.
34984      */
34985     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
34986     /**
34987      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
34988      * The CSS class returned to the drag source when drop is not allowed.
34989      */
34990     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
34991
34992     /**
34993      * @cfg {Boolean} animRepair
34994      * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
34995      */
34996     animRepair: true,
34997
34998     /**
34999      * @cfg {String} repairHighlightColor
35000      * The color to use when visually highlighting the drag source in the afterRepair
35001      * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
35002      * a preceding '#'.
35003      */
35004     repairHighlightColor: 'c3daf9',
35005
35006     /**
35007      * Creates new drag-source.
35008      * @constructor
35009      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
35010      * @param {Object} config (optional) Config object.
35011      */
35012     constructor: function(el, config) {
35013         this.el = Ext.get(el);
35014         if(!this.dragData){
35015             this.dragData = {};
35016         }
35017
35018         Ext.apply(this, config);
35019
35020         if(!this.proxy){
35021             this.proxy = Ext.create('Ext.dd.StatusProxy', {
35022                 animRepair: this.animRepair
35023             });
35024         }
35025         this.callParent([this.el.dom, this.ddGroup || this.group,
35026               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
35027
35028         this.dragging = false;
35029     },
35030
35031     /**
35032      * Returns the data object associated with this drag source
35033      * @return {Object} data An object containing arbitrary data
35034      */
35035     getDragData : function(e){
35036         return this.dragData;
35037     },
35038
35039     // private
35040     onDragEnter : function(e, id){
35041         var target = Ext.dd.DragDropManager.getDDById(id);
35042         this.cachedTarget = target;
35043         if (this.beforeDragEnter(target, e, id) !== false) {
35044             if (target.isNotifyTarget) {
35045                 var status = target.notifyEnter(this, e, this.dragData);
35046                 this.proxy.setStatus(status);
35047             } else {
35048                 this.proxy.setStatus(this.dropAllowed);
35049             }
35050
35051             if (this.afterDragEnter) {
35052                 /**
35053                  * An empty function by default, but provided so that you can perform a custom action
35054                  * when the dragged item enters the drop target by providing an implementation.
35055                  * @param {Ext.dd.DragDrop} target The drop target
35056                  * @param {Event} e The event object
35057                  * @param {String} id The id of the dragged element
35058                  * @method afterDragEnter
35059                  */
35060                 this.afterDragEnter(target, e, id);
35061             }
35062         }
35063     },
35064
35065     /**
35066      * An empty function by default, but provided so that you can perform a custom action
35067      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
35068      * @param {Ext.dd.DragDrop} target The drop target
35069      * @param {Event} e The event object
35070      * @param {String} id The id of the dragged element
35071      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
35072      */
35073     beforeDragEnter: function(target, e, id) {
35074         return true;
35075     },
35076
35077     // private
35078     alignElWithMouse: function() {
35079         this.callParent(arguments);
35080         this.proxy.sync();
35081     },
35082
35083     // private
35084     onDragOver: function(e, id) {
35085         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
35086         if (this.beforeDragOver(target, e, id) !== false) {
35087             if(target.isNotifyTarget){
35088                 var status = target.notifyOver(this, e, this.dragData);
35089                 this.proxy.setStatus(status);
35090             }
35091
35092             if (this.afterDragOver) {
35093                 /**
35094                  * An empty function by default, but provided so that you can perform a custom action
35095                  * while the dragged item is over the drop target by providing an implementation.
35096                  * @param {Ext.dd.DragDrop} target The drop target
35097                  * @param {Event} e The event object
35098                  * @param {String} id The id of the dragged element
35099                  * @method afterDragOver
35100                  */
35101                 this.afterDragOver(target, e, id);
35102             }
35103         }
35104     },
35105
35106     /**
35107      * An empty function by default, but provided so that you can perform a custom action
35108      * while the dragged item is over the drop target and optionally cancel the onDragOver.
35109      * @param {Ext.dd.DragDrop} target The drop target
35110      * @param {Event} e The event object
35111      * @param {String} id The id of the dragged element
35112      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
35113      */
35114     beforeDragOver: function(target, e, id) {
35115         return true;
35116     },
35117
35118     // private
35119     onDragOut: function(e, id) {
35120         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
35121         if (this.beforeDragOut(target, e, id) !== false) {
35122             if (target.isNotifyTarget) {
35123                 target.notifyOut(this, e, this.dragData);
35124             }
35125             this.proxy.reset();
35126             if (this.afterDragOut) {
35127                 /**
35128                  * An empty function by default, but provided so that you can perform a custom action
35129                  * after the dragged item is dragged out of the target without dropping.
35130                  * @param {Ext.dd.DragDrop} target The drop target
35131                  * @param {Event} e The event object
35132                  * @param {String} id The id of the dragged element
35133                  * @method afterDragOut
35134                  */
35135                 this.afterDragOut(target, e, id);
35136             }
35137         }
35138         this.cachedTarget = null;
35139     },
35140
35141     /**
35142      * An empty function by default, but provided so that you can perform a custom action before the dragged
35143      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
35144      * @param {Ext.dd.DragDrop} target The drop target
35145      * @param {Event} e The event object
35146      * @param {String} id The id of the dragged element
35147      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
35148      */
35149     beforeDragOut: function(target, e, id){
35150         return true;
35151     },
35152
35153     // private
35154     onDragDrop: function(e, id){
35155         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
35156         if (this.beforeDragDrop(target, e, id) !== false) {
35157             if (target.isNotifyTarget) {
35158                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
35159                     this.onValidDrop(target, e, id);
35160                 } else {
35161                     this.onInvalidDrop(target, e, id);
35162                 }
35163             } else {
35164                 this.onValidDrop(target, e, id);
35165             }
35166
35167             if (this.afterDragDrop) {
35168                 /**
35169                  * An empty function by default, but provided so that you can perform a custom action
35170                  * after a valid drag drop has occurred by providing an implementation.
35171                  * @param {Ext.dd.DragDrop} target The drop target
35172                  * @param {Event} e The event object
35173                  * @param {String} id The id of the dropped element
35174                  * @method afterDragDrop
35175                  */
35176                 this.afterDragDrop(target, e, id);
35177             }
35178         }
35179         delete this.cachedTarget;
35180     },
35181
35182     /**
35183      * An empty function by default, but provided so that you can perform a custom action before the dragged
35184      * item is dropped onto the target and optionally cancel the onDragDrop.
35185      * @param {Ext.dd.DragDrop} target The drop target
35186      * @param {Event} e The event object
35187      * @param {String} id The id of the dragged element
35188      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
35189      */
35190     beforeDragDrop: function(target, e, id){
35191         return true;
35192     },
35193
35194     // private
35195     onValidDrop: function(target, e, id){
35196         this.hideProxy();
35197         if(this.afterValidDrop){
35198             /**
35199              * An empty function by default, but provided so that you can perform a custom action
35200              * after a valid drop has occurred by providing an implementation.
35201              * @param {Object} target The target DD
35202              * @param {Event} e The event object
35203              * @param {String} id The id of the dropped element
35204              * @method afterValidDrop
35205              */
35206             this.afterValidDrop(target, e, id);
35207         }
35208     },
35209
35210     // private
35211     getRepairXY: function(e, data){
35212         return this.el.getXY();
35213     },
35214
35215     // private
35216     onInvalidDrop: function(target, e, id) {
35217         this.beforeInvalidDrop(target, e, id);
35218         if (this.cachedTarget) {
35219             if(this.cachedTarget.isNotifyTarget){
35220                 this.cachedTarget.notifyOut(this, e, this.dragData);
35221             }
35222             this.cacheTarget = null;
35223         }
35224         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
35225
35226         if (this.afterInvalidDrop) {
35227             /**
35228              * An empty function by default, but provided so that you can perform a custom action
35229              * after an invalid drop has occurred by providing an implementation.
35230              * @param {Event} e The event object
35231              * @param {String} id The id of the dropped element
35232              * @method afterInvalidDrop
35233              */
35234             this.afterInvalidDrop(e, id);
35235         }
35236     },
35237
35238     // private
35239     afterRepair: function() {
35240         var me = this;
35241         if (Ext.enableFx) {
35242             me.el.highlight(me.repairHighlightColor);
35243         }
35244         me.dragging = false;
35245     },
35246
35247     /**
35248      * An empty function by default, but provided so that you can perform a custom action after an invalid
35249      * drop has occurred.
35250      * @param {Ext.dd.DragDrop} target The drop target
35251      * @param {Event} e The event object
35252      * @param {String} id The id of the dragged element
35253      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
35254      */
35255     beforeInvalidDrop: function(target, e, id) {
35256         return true;
35257     },
35258
35259     // private
35260     handleMouseDown: function(e) {
35261         if (this.dragging) {
35262             return;
35263         }
35264         var data = this.getDragData(e);
35265         if (data && this.onBeforeDrag(data, e) !== false) {
35266             this.dragData = data;
35267             this.proxy.stop();
35268             this.callParent(arguments);
35269         }
35270     },
35271
35272     /**
35273      * An empty function by default, but provided so that you can perform a custom action before the initial
35274      * drag event begins and optionally cancel it.
35275      * @param {Object} data An object containing arbitrary data to be shared with drop targets
35276      * @param {Event} e The event object
35277      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
35278      */
35279     onBeforeDrag: function(data, e){
35280         return true;
35281     },
35282
35283     /**
35284      * An empty function by default, but provided so that you can perform a custom action once the initial
35285      * drag event has begun.  The drag cannot be canceled from this function.
35286      * @param {Number} x The x position of the click on the dragged object
35287      * @param {Number} y The y position of the click on the dragged object
35288      * @method
35289      */
35290     onStartDrag: Ext.emptyFn,
35291
35292     // private override
35293     startDrag: function(x, y) {
35294         this.proxy.reset();
35295         this.dragging = true;
35296         this.proxy.update("");
35297         this.onInitDrag(x, y);
35298         this.proxy.show();
35299     },
35300
35301     // private
35302     onInitDrag: function(x, y) {
35303         var clone = this.el.dom.cloneNode(true);
35304         clone.id = Ext.id(); // prevent duplicate ids
35305         this.proxy.update(clone);
35306         this.onStartDrag(x, y);
35307         return true;
35308     },
35309
35310     /**
35311      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
35312      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
35313      */
35314     getProxy: function() {
35315         return this.proxy;
35316     },
35317
35318     /**
35319      * Hides the drag source's {@link Ext.dd.StatusProxy}
35320      */
35321     hideProxy: function() {
35322         this.proxy.hide();
35323         this.proxy.reset(true);
35324         this.dragging = false;
35325     },
35326
35327     // private
35328     triggerCacheRefresh: function() {
35329         Ext.dd.DDM.refreshCache(this.groups);
35330     },
35331
35332     // private - override to prevent hiding
35333     b4EndDrag: function(e) {
35334     },
35335
35336     // private - override to prevent moving
35337     endDrag : function(e){
35338         this.onEndDrag(this.dragData, e);
35339     },
35340
35341     // private
35342     onEndDrag : function(data, e){
35343     },
35344
35345     // private - pin to cursor
35346     autoOffset : function(x, y) {
35347         this.setDelta(-12, -20);
35348     },
35349
35350     destroy: function(){
35351         this.callParent();
35352         Ext.destroy(this.proxy);
35353     }
35354 });
35355
35356 // private - DD implementation for Panels
35357 Ext.define('Ext.panel.DD', {
35358     extend: 'Ext.dd.DragSource',
35359     requires: ['Ext.panel.Proxy'],
35360
35361     constructor : function(panel, cfg){
35362         this.panel = panel;
35363         this.dragData = {panel: panel};
35364         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
35365
35366         this.callParent([panel.el, cfg]);
35367
35368         Ext.defer(function() {
35369             var header = panel.header,
35370                 el = panel.body;
35371
35372             if(header){
35373                 this.setHandleElId(header.id);
35374                 el = header.el;
35375             }
35376             el.setStyle('cursor', 'move');
35377             this.scroll = false;
35378         }, 200, this);
35379     },
35380
35381     showFrame: Ext.emptyFn,
35382     startDrag: Ext.emptyFn,
35383     b4StartDrag: function(x, y) {
35384         this.proxy.show();
35385     },
35386     b4MouseDown: function(e) {
35387         var x = e.getPageX(),
35388             y = e.getPageY();
35389         this.autoOffset(x, y);
35390     },
35391     onInitDrag : function(x, y){
35392         this.onStartDrag(x, y);
35393         return true;
35394     },
35395     createFrame : Ext.emptyFn,
35396     getDragEl : function(e){
35397         return this.proxy.ghost.el.dom;
35398     },
35399     endDrag : function(e){
35400         this.proxy.hide();
35401         this.panel.saveState();
35402     },
35403
35404     autoOffset : function(x, y) {
35405         x -= this.startPageX;
35406         y -= this.startPageY;
35407         this.setDelta(x, y);
35408     }
35409 });
35410
35411 /**
35412  * @class Ext.layout.component.Dock
35413  * @extends Ext.layout.component.AbstractDock
35414  * @private
35415  */
35416 Ext.define('Ext.layout.component.Dock', {
35417
35418     /* Begin Definitions */
35419
35420     alias: ['layout.dock'],
35421
35422     extend: 'Ext.layout.component.AbstractDock'
35423
35424     /* End Definitions */
35425
35426 });
35427 /**
35428  * Panel is a container that has specific functionality and structural components that make it the perfect building
35429  * block for application-oriented user interfaces.
35430  *
35431  * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
35432  * {@link Ext.container.Container#layout layout}, and containing child Components.
35433  *
35434  * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
35435  * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
35436  * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
35437  * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
35438  * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
35439  *
35440  * {@img Ext.panel.Panel/panel.png Panel components}
35441  *
35442  * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
35443  * Ext.panel.Header header}, {@link #fbar footer} and body sections.
35444  *
35445  * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
35446  * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
35447  * is {@link Ext.container.Container#add completely managed by the framework}.
35448  *
35449  * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
35450  * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
35451  * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
35452  * `{@link #closeAction closeAction}: 'hide'`.
35453  *
35454  * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
35455  * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
35456  * Panel into the document, here's how to do it:
35457  *
35458  *     @example
35459  *     Ext.create('Ext.panel.Panel', {
35460  *         title: 'Hello',
35461  *         width: 200,
35462  *         html: '<p>World!</p>',
35463  *         renderTo: Ext.getBody()
35464  *     });
35465  *
35466  * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
35467  * constituent part of a Container:
35468  *
35469  *     @example
35470  *     var filterPanel = Ext.create('Ext.panel.Panel', {
35471  *         bodyPadding: 5,  // Don't want content to crunch against the borders
35472  *         width: 300,
35473  *         title: 'Filters',
35474  *         items: [{
35475  *             xtype: 'datefield',
35476  *             fieldLabel: 'Start date'
35477  *         }, {
35478  *             xtype: 'datefield',
35479  *             fieldLabel: 'End date'
35480  *         }],
35481  *         renderTo: Ext.getBody()
35482  *     });
35483  *
35484  * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
35485  * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
35486  * size and position its child Components.
35487  *
35488  * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
35489  * arranging child Components:
35490  *
35491  *     @example
35492  *     var resultsPanel = Ext.create('Ext.panel.Panel', {
35493  *         title: 'Results',
35494  *         width: 600,
35495  *         height: 400,
35496  *         renderTo: Ext.getBody(),
35497  *         layout: {
35498  *             type: 'vbox',       // Arrange child items vertically
35499  *             align: 'stretch',    // Each takes up full width
35500  *             padding: 5
35501  *         },
35502  *         items: [{               // Results grid specified as a config object with an xtype of 'grid'
35503  *             xtype: 'grid',
35504  *             columns: [{header: 'Column One'}],            // One header just for show. There's no data,
35505  *             store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
35506  *             flex: 1                                       // Use 1/3 of Container's height (hint to Box layout)
35507  *         }, {
35508  *             xtype: 'splitter'   // A splitter between the two child items
35509  *         }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
35510  *             title: 'Details',
35511  *             bodyPadding: 5,
35512  *             items: [{
35513  *                 fieldLabel: 'Data item',
35514  *                 xtype: 'textfield'
35515  *             }], // An array of form fields
35516  *             flex: 2             // Use 2/3 of Container's height (hint to Box layout)
35517  *         }]
35518  *     });
35519  *
35520  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
35521  * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
35522  * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
35523  * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
35524  * distribute available space proportionately.
35525  *
35526  * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
35527  * within its content area.
35528  *
35529  * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
35530  * level of nested containment, all dynamically sized according to configuration, the user's preference and available
35531  * browser size.
35532  */
35533 Ext.define('Ext.panel.Panel', {
35534     extend: 'Ext.panel.AbstractPanel',
35535     requires: [
35536         'Ext.panel.Header',
35537         'Ext.fx.Anim',
35538         'Ext.util.KeyMap',
35539         'Ext.panel.DD',
35540         'Ext.XTemplate',
35541         'Ext.layout.component.Dock',
35542         'Ext.util.Memento'
35543     ],
35544     alias: 'widget.panel',
35545     alternateClassName: 'Ext.Panel',
35546
35547     /**
35548      * @cfg {String} collapsedCls
35549      * A CSS class to add to the panel's element after it has been collapsed.
35550      */
35551     collapsedCls: 'collapsed',
35552
35553     /**
35554      * @cfg {Boolean} animCollapse
35555      * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
35556      * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
35557      * duration in milliseconds.
35558      */
35559     animCollapse: Ext.enableFx,
35560
35561     /**
35562      * @cfg {Number} minButtonWidth
35563      * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
35564      * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
35565      * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
35566      * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
35567      * their parent container.
35568      */
35569     minButtonWidth: 75,
35570
35571     /**
35572      * @cfg {Boolean} collapsed
35573      * `true` to render the panel collapsed, `false` to render it expanded.
35574      */
35575     collapsed: false,
35576
35577     /**
35578      * @cfg {Boolean} collapseFirst
35579      * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
35580      * the panel's title bar, `false` to render it last.
35581      */
35582     collapseFirst: true,
35583
35584     /**
35585      * @cfg {Boolean} hideCollapseTool
35586      * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
35587      */
35588     hideCollapseTool: false,
35589
35590     /**
35591      * @cfg {Boolean} titleCollapse
35592      * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
35593      * the header bar, `false`) to allow it only by clicking to tool butto).
35594      */
35595     titleCollapse: false,
35596
35597     /**
35598      * @cfg {String} collapseMode
35599      * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
35600      * {@link Ext.layout.container.Border border layout}.**
35601      *
35602      * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
35603      * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
35604      * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
35605      * expand tool.
35606      *
35607      * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
35608      *
35609      * - **`undefined/omitted`**
35610      *
35611      *   When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
35612      *   and to provide a UI with a Tool to allow the user to re-expand the Panel.
35613      *
35614      * - **`header`** :
35615      *
35616      *   The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
35617      *   layout}.
35618      */
35619
35620     /**
35621      * @cfg {Ext.Component/Object} placeholder
35622      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
35623      * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
35624      *
35625      * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
35626      * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
35627      * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
35628      */
35629
35630     /**
35631      * @cfg {Boolean} floatable
35632      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
35633      * {@link Ext.layout.container.Border border layout}.**
35634      *
35635      * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
35636      * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
35637      */
35638     floatable: true,
35639
35640     /**
35641      * @cfg {Boolean} overlapHeader
35642      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
35643      * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
35644      * which does not have frame:true, this will need to be set to true.
35645      */
35646
35647     /**
35648      * @cfg {Boolean} collapsible
35649      * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
35650      * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
35651      *
35652      * See {@link #collapseMode} and {@link #collapseDirection}
35653      */
35654     collapsible: false,
35655
35656     /**
35657      * @cfg {Boolean} collapseDirection
35658      * The direction to collapse the Panel when the toggle button is clicked.
35659      *
35660      * Defaults to the {@link #headerPosition}
35661      *
35662      * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
35663      * Ext.layout.container.Border border layout}.**
35664      *
35665      * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
35666      */
35667
35668     /**
35669      * @cfg {Boolean} closable
35670      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
35671      * disallow closing the window.
35672      *
35673      * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
35674      * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
35675      * reused.
35676      *
35677      * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
35678      */
35679     closable: false,
35680
35681     /**
35682      * @cfg {String} closeAction
35683      * The action to take when the close header tool is clicked:
35684      *
35685      * - **`'{@link #destroy}'`** :
35686      *
35687      *   {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
35688      *   Components. The window will **not** be available to be redisplayed via the {@link #show} method.
35689      *
35690      * - **`'{@link #hide}'`** :
35691      *
35692      *   {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
35693      *   available to be redisplayed via the {@link #show} method.
35694      *
35695      * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
35696      * approriate closeAction.
35697      */
35698     closeAction: 'destroy',
35699
35700     /**
35701      * @cfg {Object/Object[]} dockedItems
35702      * A component or series of components to be added as docked items to this panel. The docked items can be docked to
35703      * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
35704      *
35705      *     var panel = new Ext.panel.Panel({
35706      *         dockedItems: [{
35707      *             xtype: 'toolbar',
35708      *             dock: 'top',
35709      *             items: [{
35710      *                 text: 'Docked to the top'
35711      *             }]
35712      *         }]
35713      *     });
35714      */
35715
35716     /**
35717       * @cfg {Boolean} preventHeader
35718       * Prevent a Header from being created and shown.
35719       */
35720     preventHeader: false,
35721
35722      /**
35723       * @cfg {String} headerPosition
35724       * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
35725       */
35726     headerPosition: 'top',
35727
35728      /**
35729      * @cfg {Boolean} frame
35730      * True to apply a frame to the panel.
35731      */
35732     frame: false,
35733
35734     /**
35735      * @cfg {Boolean} frameHeader
35736      * True to apply a frame to the panel panels header (if 'frame' is true).
35737      */
35738     frameHeader: true,
35739
35740     /**
35741      * @cfg {Object[]/Ext.panel.Tool[]} tools
35742      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
35743      * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
35744      * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
35745      *
35746      * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
35747      * visual button. Any required functionality must be provided by adding handlers that implement the necessary
35748      * behavior.
35749      *
35750      * Example usage:
35751      *
35752      *     tools:[{
35753      *         type:'refresh',
35754      *         tooltip: 'Refresh form Data',
35755      *         // hidden:true,
35756      *         handler: function(event, toolEl, panel){
35757      *             // refresh logic
35758      *         }
35759      *     },
35760      *     {
35761      *         type:'help',
35762      *         tooltip: 'Get Help',
35763      *         handler: function(event, toolEl, panel){
35764      *             // show help here
35765      *         }
35766      *     }]
35767      */
35768
35769     /**
35770      * @cfg {String} [title='']
35771      * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
35772      * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
35773      * {@link #preventHeader} is set to `true`.
35774      */
35775
35776     /**
35777      * @cfg {String} iconCls
35778      * CSS class for icon in header. Used for displaying an icon to the left of a title.
35779      */
35780
35781     initComponent: function() {
35782         var me = this,
35783             cls;
35784
35785         me.addEvents(
35786
35787             /**
35788              * @event beforeclose
35789              * Fires before the user closes the panel. Return false from any listener to stop the close event being
35790              * fired
35791              * @param {Ext.panel.Panel} panel The Panel object
35792              */
35793             'beforeclose',
35794
35795             /**
35796              * @event beforeexpand
35797              * Fires before this panel is expanded. Return false to prevent the expand.
35798              * @param {Ext.panel.Panel} p The Panel being expanded.
35799              * @param {Boolean} animate True if the expand is animated, else false.
35800              */
35801             "beforeexpand",
35802
35803             /**
35804              * @event beforecollapse
35805              * Fires before this panel is collapsed. Return false to prevent the collapse.
35806              * @param {Ext.panel.Panel} p The Panel being collapsed.
35807              * @param {String} direction . The direction of the collapse. One of
35808              *
35809              *   - Ext.Component.DIRECTION_TOP
35810              *   - Ext.Component.DIRECTION_RIGHT
35811              *   - Ext.Component.DIRECTION_BOTTOM
35812              *   - Ext.Component.DIRECTION_LEFT
35813              *
35814              * @param {Boolean} animate True if the collapse is animated, else false.
35815              */
35816             "beforecollapse",
35817
35818             /**
35819              * @event expand
35820              * Fires after this Panel has expanded.
35821              * @param {Ext.panel.Panel} p The Panel that has been expanded.
35822              */
35823             "expand",
35824
35825             /**
35826              * @event collapse
35827              * Fires after this Panel hass collapsed.
35828              * @param {Ext.panel.Panel} p The Panel that has been collapsed.
35829              */
35830             "collapse",
35831
35832             /**
35833              * @event titlechange
35834              * Fires after the Panel title has been set or changed.
35835              * @param {Ext.panel.Panel} p the Panel which has been resized.
35836              * @param {String} newTitle The new title.
35837              * @param {String} oldTitle The previous panel title.
35838              */
35839             'titlechange',
35840
35841             /**
35842              * @event iconchange
35843              * Fires after the Panel iconCls has been set or changed.
35844              * @param {Ext.panel.Panel} p the Panel which has been resized.
35845              * @param {String} newIconCls The new iconCls.
35846              * @param {String} oldIconCls The previous panel iconCls.
35847              */
35848             'iconchange'
35849         );
35850
35851         // Save state on these two events.
35852         this.addStateEvents('expand', 'collapse');
35853
35854         if (me.unstyled) {
35855             me.setUI('plain');
35856         }
35857
35858         if (me.frame) {
35859             me.setUI(me.ui + '-framed');
35860         }
35861
35862         // Backwards compatibility
35863         me.bridgeToolbars();
35864
35865         me.callParent();
35866         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
35867     },
35868
35869     setBorder: function(border) {
35870         // var me     = this,
35871         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
35872         //
35873         // me.callParent(arguments);
35874         //
35875         // if (me.collapsed) {
35876         //     me[method](me.collapsedCls + '-noborder');
35877         // }
35878         //
35879         // if (me.header) {
35880         //     me.header.setBorder(border);
35881         //     if (me.collapsed) {
35882         //         me.header[method](me.collapsedCls + '-noborder');
35883         //     }
35884         // }
35885
35886         this.callParent(arguments);
35887     },
35888
35889     beforeDestroy: function() {
35890         Ext.destroy(
35891             this.ghostPanel,
35892             this.dd
35893         );
35894         this.callParent();
35895     },
35896
35897     initAria: function() {
35898         this.callParent();
35899         this.initHeaderAria();
35900     },
35901
35902     initHeaderAria: function() {
35903         var me = this,
35904             el = me.el,
35905             header = me.header;
35906         if (el && header) {
35907             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
35908         }
35909     },
35910
35911     getHeader: function() {
35912         return this.header;
35913     },
35914
35915     /**
35916      * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
35917      * @param {String} newTitle
35918      */
35919     setTitle: function(newTitle) {
35920         var me = this,
35921         oldTitle = this.title;
35922
35923         me.title = newTitle;
35924         if (me.header) {
35925             me.header.setTitle(newTitle);
35926         } else {
35927             me.updateHeader();
35928         }
35929
35930         if (me.reExpander) {
35931             me.reExpander.setTitle(newTitle);
35932         }
35933         me.fireEvent('titlechange', me, newTitle, oldTitle);
35934     },
35935
35936     /**
35937      * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
35938      * {@link #iconchange} event after completion.
35939      * @param {String} newIconCls The new CSS class name
35940      */
35941     setIconCls: function(newIconCls) {
35942         var me = this,
35943             oldIconCls = me.iconCls;
35944
35945         me.iconCls = newIconCls;
35946         var header = me.header;
35947         if (header) {
35948             header.setIconCls(newIconCls);
35949         }
35950         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
35951     },
35952
35953     bridgeToolbars: function() {
35954         var me = this,
35955             docked = [],
35956             fbar,
35957             fbarDefaults,
35958             minButtonWidth = me.minButtonWidth;
35959
35960         function initToolbar (toolbar, pos, useButtonAlign) {
35961             if (Ext.isArray(toolbar)) {
35962                 toolbar = {
35963                     xtype: 'toolbar',
35964                     items: toolbar
35965                 };
35966             }
35967             else if (!toolbar.xtype) {
35968                 toolbar.xtype = 'toolbar';
35969             }
35970             toolbar.dock = pos;
35971             if (pos == 'left' || pos == 'right') {
35972                 toolbar.vertical = true;
35973             }
35974
35975             // Legacy support for buttonAlign (only used by buttons/fbar)
35976             if (useButtonAlign) {
35977                 toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
35978                     // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
35979                     pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
35980                 });
35981             }
35982             return toolbar;
35983         }
35984
35985         // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
35986
35987         /**
35988          * @cfg {String} buttonAlign
35989          * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
35990          * 'right' for buttons/fbar, 'left' for other toolbar types).
35991          *
35992          * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
35993          * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
35994          */
35995
35996         /**
35997          * @cfg {Object/Object[]} tbar
35998          * Convenience config. Short for 'Top Bar'.
35999          *
36000          *     tbar: [
36001          *       { xtype: 'button', text: 'Button 1' }
36002          *     ]
36003          *
36004          * is equivalent to
36005          *
36006          *     dockedItems: [{
36007          *         xtype: 'toolbar',
36008          *         dock: 'top',
36009          *         items: [
36010          *             { xtype: 'button', text: 'Button 1' }
36011          *         ]
36012          *     }]
36013          */
36014         if (me.tbar) {
36015             docked.push(initToolbar(me.tbar, 'top'));
36016             me.tbar = null;
36017         }
36018
36019         /**
36020          * @cfg {Object/Object[]} bbar
36021          * Convenience config. Short for 'Bottom Bar'.
36022          *
36023          *     bbar: [
36024          *       { xtype: 'button', text: 'Button 1' }
36025          *     ]
36026          *
36027          * is equivalent to
36028          *
36029          *     dockedItems: [{
36030          *         xtype: 'toolbar',
36031          *         dock: 'bottom',
36032          *         items: [
36033          *             { xtype: 'button', text: 'Button 1' }
36034          *         ]
36035          *     }]
36036          */
36037         if (me.bbar) {
36038             docked.push(initToolbar(me.bbar, 'bottom'));
36039             me.bbar = null;
36040         }
36041
36042         /**
36043          * @cfg {Object/Object[]} buttons
36044          * Convenience config used for adding buttons docked to the bottom of the panel. This is a
36045          * synonym for the {@link #fbar} config.
36046          *
36047          *     buttons: [
36048          *       { text: 'Button 1' }
36049          *     ]
36050          *
36051          * is equivalent to
36052          *
36053          *     dockedItems: [{
36054          *         xtype: 'toolbar',
36055          *         dock: 'bottom',
36056          *         ui: 'footer',
36057          *         defaults: {minWidth: {@link #minButtonWidth}},
36058          *         items: [
36059          *             { xtype: 'component', flex: 1 },
36060          *             { xtype: 'button', text: 'Button 1' }
36061          *         ]
36062          *     }]
36063          *
36064          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
36065          * each of the buttons in the buttons toolbar.
36066          */
36067         if (me.buttons) {
36068             me.fbar = me.buttons;
36069             me.buttons = null;
36070         }
36071
36072         /**
36073          * @cfg {Object/Object[]} fbar
36074          * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
36075          *
36076          *     fbar: [
36077          *       { type: 'button', text: 'Button 1' }
36078          *     ]
36079          *
36080          * is equivalent to
36081          *
36082          *     dockedItems: [{
36083          *         xtype: 'toolbar',
36084          *         dock: 'bottom',
36085          *         ui: 'footer',
36086          *         defaults: {minWidth: {@link #minButtonWidth}},
36087          *         items: [
36088          *             { xtype: 'component', flex: 1 },
36089          *             { xtype: 'button', text: 'Button 1' }
36090          *         ]
36091          *     }]
36092          *
36093          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
36094          * each of the buttons in the fbar.
36095          */
36096         if (me.fbar) {
36097             fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
36098             fbar.ui = 'footer';
36099
36100             // Apply the minButtonWidth config to buttons in the toolbar
36101             if (minButtonWidth) {
36102                 fbarDefaults = fbar.defaults;
36103                 fbar.defaults = function(config) {
36104                     var defaults = fbarDefaults || {};
36105                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
36106                             !('minWidth' in defaults)) {
36107                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
36108                     }
36109                     return defaults;
36110                 };
36111             }
36112
36113             docked.push(fbar);
36114             me.fbar = null;
36115         }
36116
36117         /**
36118          * @cfg {Object/Object[]} lbar
36119          * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
36120          *
36121          *     lbar: [
36122          *       { xtype: 'button', text: 'Button 1' }
36123          *     ]
36124          *
36125          * is equivalent to
36126          *
36127          *     dockedItems: [{
36128          *         xtype: 'toolbar',
36129          *         dock: 'left',
36130          *         items: [
36131          *             { xtype: 'button', text: 'Button 1' }
36132          *         ]
36133          *     }]
36134          */
36135         if (me.lbar) {
36136             docked.push(initToolbar(me.lbar, 'left'));
36137             me.lbar = null;
36138         }
36139
36140         /**
36141          * @cfg {Object/Object[]} rbar
36142          * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
36143          *
36144          *     rbar: [
36145          *       { xtype: 'button', text: 'Button 1' }
36146          *     ]
36147          *
36148          * is equivalent to
36149          *
36150          *     dockedItems: [{
36151          *         xtype: 'toolbar',
36152          *         dock: 'right',
36153          *         items: [
36154          *             { xtype: 'button', text: 'Button 1' }
36155          *         ]
36156          *     }]
36157          */
36158         if (me.rbar) {
36159             docked.push(initToolbar(me.rbar, 'right'));
36160             me.rbar = null;
36161         }
36162
36163         if (me.dockedItems) {
36164             if (!Ext.isArray(me.dockedItems)) {
36165                 me.dockedItems = [me.dockedItems];
36166             }
36167             me.dockedItems = me.dockedItems.concat(docked);
36168         } else {
36169             me.dockedItems = docked;
36170         }
36171     },
36172
36173     /**
36174      * @private
36175      * Tools are a Panel-specific capabilty.
36176      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
36177      */
36178     initTools: function() {
36179         var me = this;
36180
36181         me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
36182
36183         // Add a collapse tool unless configured to not show a collapse tool
36184         // or to not even show a header.
36185         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
36186             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
36187             me.collapseTool = me.expandTool = me.createComponent({
36188                 xtype: 'tool',
36189                 type: 'collapse-' + me.collapseDirection,
36190                 expandType: me.getOppositeDirection(me.collapseDirection),
36191                 handler: me.toggleCollapse,
36192                 scope: me
36193             });
36194
36195             // Prepend collapse tool is configured to do so.
36196             if (me.collapseFirst) {
36197                 me.tools.unshift(me.collapseTool);
36198             }
36199         }
36200
36201         // Add subclass-specific tools.
36202         me.addTools();
36203
36204         // Make Panel closable.
36205         if (me.closable) {
36206             me.addClsWithUI('closable');
36207             me.addTool({
36208                 type: 'close',
36209                 handler: Ext.Function.bind(me.close, this, [])
36210             });
36211         }
36212
36213         // Append collapse tool if needed.
36214         if (me.collapseTool && !me.collapseFirst) {
36215             me.tools.push(me.collapseTool);
36216         }
36217     },
36218
36219     /**
36220      * @private
36221      * @template
36222      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
36223      */
36224     addTools: Ext.emptyFn,
36225
36226     /**
36227      * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
36228      * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
36229      * close happens and will cancel the close action if it returns false.
36230      *
36231      * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
36232      * {@link #destroy} and {@link #hide} methods.
36233      */
36234     close: function() {
36235         if (this.fireEvent('beforeclose', this) !== false) {
36236             this.doClose();
36237         }
36238     },
36239
36240     // private
36241     doClose: function() {
36242         this.fireEvent('close', this);
36243         this[this.closeAction]();
36244     },
36245
36246     onRender: function(ct, position) {
36247         var me = this,
36248             topContainer;
36249
36250         // Add class-specific header tools.
36251         // Panel adds collapsible and closable.
36252         me.initTools();
36253
36254         // Dock the header/title
36255         me.updateHeader();
36256
36257         // Call to super after adding the header, to prevent an unnecessary re-layout
36258         me.callParent(arguments);
36259     },
36260
36261     afterRender: function() {
36262         var me = this;
36263
36264         me.callParent(arguments);
36265
36266         // Instate the collapsed state after render. We need to wait for
36267         // this moment so that we have established at least some of our size (from our
36268         // configured dimensions or from content via the component layout)
36269         if (me.collapsed) {
36270             me.collapsed = false;
36271             me.collapse(null, false, true);
36272         }
36273     },
36274
36275     /**
36276      * Create, hide, or show the header component as appropriate based on the current config.
36277      * @private
36278      * @param {Boolean} force True to force the header to be created
36279      */
36280     updateHeader: function(force) {
36281         var me = this,
36282             header = me.header,
36283             title = me.title,
36284             tools = me.tools;
36285
36286         if (!me.preventHeader && (force || title || (tools && tools.length))) {
36287             if (!header) {
36288                 header = me.header = Ext.create('Ext.panel.Header', {
36289                     title       : title,
36290                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
36291                     dock        : me.headerPosition || 'top',
36292                     textCls     : me.headerTextCls,
36293                     iconCls     : me.iconCls,
36294                     baseCls     : me.baseCls + '-header',
36295                     tools       : tools,
36296                     ui          : me.ui,
36297                     indicateDrag: me.draggable,
36298                     border      : me.border,
36299                     frame       : me.frame && me.frameHeader,
36300                     ignoreParentFrame : me.frame || me.overlapHeader,
36301                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
36302                     listeners   : me.collapsible && me.titleCollapse ? {
36303                         click: me.toggleCollapse,
36304                         scope: me
36305                     } : null
36306                 });
36307                 me.addDocked(header, 0);
36308
36309                 // Reference the Header's tool array.
36310                 // Header injects named references.
36311                 me.tools = header.tools;
36312             }
36313             header.show();
36314             me.initHeaderAria();
36315         } else if (header) {
36316             header.hide();
36317         }
36318     },
36319
36320     // inherit docs
36321     setUI: function(ui) {
36322         var me = this;
36323
36324         me.callParent(arguments);
36325
36326         if (me.header) {
36327             me.header.setUI(ui);
36328         }
36329     },
36330
36331     // private
36332     getContentTarget: function() {
36333         return this.body;
36334     },
36335
36336     getTargetEl: function() {
36337         return this.body || this.frameBody || this.el;
36338     },
36339
36340     // the overrides below allow for collapsed regions inside the border layout to be hidden
36341
36342     // inherit docs
36343     isVisible: function(deep){
36344         var me = this;
36345         if (me.collapsed && me.placeholder) {
36346             return me.placeholder.isVisible(deep);
36347         }
36348         return me.callParent(arguments);
36349     },
36350
36351     // inherit docs
36352     onHide: function(){
36353         var me = this;
36354         if (me.collapsed && me.placeholder) {
36355             me.placeholder.hide();
36356         } else {
36357             me.callParent(arguments);
36358         }
36359     },
36360
36361     // inherit docs
36362     onShow: function(){
36363         var me = this;
36364         if (me.collapsed && me.placeholder) {
36365             // force hidden back to true, since this gets set by the layout
36366             me.hidden = true;
36367             me.placeholder.show();
36368         } else {
36369             me.callParent(arguments);
36370         }
36371     },
36372
36373     addTool: function(tool) {
36374         var me = this,
36375             header = me.header;
36376
36377         if (Ext.isArray(tool)) {
36378             Ext.each(tool, me.addTool, me);
36379             return;
36380         }
36381         me.tools.push(tool);
36382         if (header) {
36383             header.addTool(tool);
36384         }
36385         me.updateHeader();
36386     },
36387
36388     getOppositeDirection: function(d) {
36389         var c = Ext.Component;
36390         switch (d) {
36391             case c.DIRECTION_TOP:
36392                 return c.DIRECTION_BOTTOM;
36393             case c.DIRECTION_RIGHT:
36394                 return c.DIRECTION_LEFT;
36395             case c.DIRECTION_BOTTOM:
36396                 return c.DIRECTION_TOP;
36397             case c.DIRECTION_LEFT:
36398                 return c.DIRECTION_RIGHT;
36399         }
36400     },
36401
36402     /**
36403      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
36404      * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
36405      * collapse action if it returns false.
36406      *
36407      * @param {String} direction . The direction to collapse towards. Must be one of
36408      *
36409      *   - Ext.Component.DIRECTION_TOP
36410      *   - Ext.Component.DIRECTION_RIGHT
36411      *   - Ext.Component.DIRECTION_BOTTOM
36412      *   - Ext.Component.DIRECTION_LEFT
36413      *
36414      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
36415      * {@link #animCollapse} panel config)
36416      * @return {Ext.panel.Panel} this
36417      */
36418     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
36419         var me = this,
36420             c = Ext.Component,
36421             height = me.getHeight(),
36422             width = me.getWidth(),
36423             frameInfo,
36424             newSize = 0,
36425             dockedItems = me.dockedItems.items,
36426             dockedItemCount = dockedItems.length,
36427             i = 0,
36428             comp,
36429             pos,
36430             anim = {
36431                 from: {
36432                     height: height,
36433                     width: width
36434                 },
36435                 to: {
36436                     height: height,
36437                     width: width
36438                 },
36439                 listeners: {
36440                     afteranimate: me.afterCollapse,
36441                     scope: me
36442                 },
36443                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
36444             },
36445             reExpander,
36446             reExpanderOrientation,
36447             reExpanderDock,
36448             getDimension,
36449             collapseDimension;
36450
36451         if (!direction) {
36452             direction = me.collapseDirection;
36453         }
36454
36455         // If internal (Called because of initial collapsed state), then no animation, and no events.
36456         if (internal) {
36457             animate = false;
36458         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
36459             return false;
36460         }
36461
36462         reExpanderDock = direction;
36463         me.expandDirection = me.getOppositeDirection(direction);
36464
36465         // Track docked items which we hide during collapsed state
36466         me.hiddenDocked = [];
36467
36468         switch (direction) {
36469             case c.DIRECTION_TOP:
36470             case c.DIRECTION_BOTTOM:
36471                 reExpanderOrientation = 'horizontal';
36472                 collapseDimension = 'height';
36473                 getDimension = 'getHeight';
36474
36475                 // Attempt to find a reExpander Component (docked in a horizontal orientation)
36476                 // Also, collect all other docked items which we must hide after collapse.
36477                 for (; i < dockedItemCount; i++) {
36478                     comp = dockedItems[i];
36479                     if (comp.isVisible()) {
36480                         if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
36481                             reExpander = comp;
36482                         } else {
36483                             me.hiddenDocked.push(comp);
36484                         }
36485                     } else if (comp === me.reExpander) {
36486                         reExpander = comp;
36487                     }
36488                 }
36489
36490                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
36491                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
36492                     anim.from.top = pos;
36493                 }
36494                 break;
36495
36496             case c.DIRECTION_LEFT:
36497             case c.DIRECTION_RIGHT:
36498                 reExpanderOrientation = 'vertical';
36499                 collapseDimension = 'width';
36500                 getDimension = 'getWidth';
36501
36502                 // Attempt to find a reExpander Component (docked in a vecrtical orientation)
36503                 // Also, collect all other docked items which we must hide after collapse.
36504                 for (; i < dockedItemCount; i++) {
36505                     comp = dockedItems[i];
36506                     if (comp.isVisible()) {
36507                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
36508                             reExpander = comp;
36509                         } else {
36510                             me.hiddenDocked.push(comp);
36511                         }
36512                     } else if (comp === me.reExpander) {
36513                         reExpander = comp;
36514                     }
36515                 }
36516
36517                 if (direction == Ext.Component.DIRECTION_RIGHT) {
36518                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
36519                     anim.from.left = pos;
36520                 }
36521                 break;
36522
36523             default:
36524                 throw('Panel collapse must be passed a valid Component collapse direction');
36525         }
36526
36527         // Disable toggle tool during animated collapse
36528         if (animate && me.collapseTool) {
36529             me.collapseTool.disable();
36530         }
36531
36532         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
36533         me.addClsWithUI(me.collapsedCls);
36534         // if (me.border === false) {
36535         //     me.addClsWithUI(me.collapsedCls + '-noborder');
36536         // }
36537
36538         // We found a header: Measure it to find the collapse-to size.
36539         if (reExpander && reExpander.rendered) {
36540
36541             //we must add the collapsed cls to the header and then remove to get the proper height
36542             reExpander.addClsWithUI(me.collapsedCls);
36543             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
36544             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
36545                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
36546             }
36547
36548             frameInfo = reExpander.getFrameInfo();
36549
36550             //get the size
36551             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
36552
36553             //and remove
36554             reExpander.removeClsWithUI(me.collapsedCls);
36555             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);
36556             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
36557                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
36558             }
36559         }
36560         // No header: Render and insert a temporary one, and then measure it.
36561         else {
36562             reExpander = {
36563                 hideMode: 'offsets',
36564                 temporary: true,
36565                 title: me.title,
36566                 orientation: reExpanderOrientation,
36567                 dock: reExpanderDock,
36568                 textCls: me.headerTextCls,
36569                 iconCls: me.iconCls,
36570                 baseCls: me.baseCls + '-header',
36571                 ui: me.ui,
36572                 frame: me.frame && me.frameHeader,
36573                 ignoreParentFrame: me.frame || me.overlapHeader,
36574                 indicateDrag: me.draggable,
36575                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
36576                 renderTo: me.el
36577             };
36578             if (!me.hideCollapseTool) {
36579                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
36580                     xtype: 'tool',
36581                     type: 'expand-' + me.expandDirection,
36582                     handler: me.toggleCollapse,
36583                     scope: me
36584                 }];
36585             }
36586
36587             // Capture the size of the re-expander.
36588             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
36589             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
36590             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
36591             reExpander.hide();
36592
36593             // Insert the new docked item
36594             me.insertDocked(0, reExpander);
36595         }
36596
36597         me.reExpander = reExpander;
36598         me.reExpander.addClsWithUI(me.collapsedCls);
36599         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
36600         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
36601             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
36602         }
36603
36604         // If collapsing right or down, we'll be also animating the left or top.
36605         if (direction == Ext.Component.DIRECTION_RIGHT) {
36606             anim.to.left = pos + (width - newSize);
36607         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
36608             anim.to.top = pos + (height - newSize);
36609         }
36610
36611         // Animate to the new size
36612         anim.to[collapseDimension] = newSize;
36613
36614         // When we collapse a panel, the panel is in control of one dimension (depending on
36615         // collapse direction) and sets that on the component. We must restore the user's
36616         // original value (including non-existance) when we expand. Using this technique, we
36617         // mimic setCalculatedSize for the dimension we do not control and setSize for the
36618         // one we do (only while collapsed).
36619         if (!me.collapseMemento) {
36620             me.collapseMemento = new Ext.util.Memento(me);
36621         }
36622         me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
36623
36624         // Remove any flex config before we attempt to collapse.
36625         me.savedFlex = me.flex;
36626         me.minWidth = 0;
36627         me.minHeight = 0;
36628         delete me.flex;
36629         me.suspendLayout = true;
36630
36631         if (animate) {
36632             me.animate(anim);
36633         } else {
36634             me.setSize(anim.to.width, anim.to.height);
36635             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
36636                 me.setPosition(anim.to.left, anim.to.top);
36637             }
36638             me.afterCollapse(false, internal);
36639         }
36640         return me;
36641     },
36642
36643     afterCollapse: function(animated, internal) {
36644         var me = this,
36645             i = 0,
36646             l = me.hiddenDocked.length;
36647
36648         me.collapseMemento.restore(['minWidth', 'minHeight']);
36649
36650         // Now we can restore the dimension we don't control to its original state
36651         // Leave the value in the memento so that it can be correctly restored
36652         // if it is set by animation.
36653         if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
36654             me.layoutManagedHeight = 2;
36655             me.collapseMemento.restore('width', false);
36656         } else {
36657             me.layoutManagedWidth = 2;
36658             me.collapseMemento.restore('height', false);
36659         }
36660
36661         // We must hide the body, otherwise it overlays docked items which come before
36662         // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
36663         me.saveScrollTop = me.body.dom.scrollTop;
36664         me.body.setStyle('display', 'none');
36665
36666         for (; i < l; i++) {
36667             me.hiddenDocked[i].hide();
36668         }
36669         if (me.reExpander) {
36670             me.reExpander.updateFrame();
36671             me.reExpander.show();
36672         }
36673         me.collapsed = true;
36674         me.suspendLayout = false;
36675
36676         if (!internal) {
36677             if (me.ownerCt) {
36678                 // Because Component layouts only inform upstream containers if they have changed size,
36679                 // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
36680                 if (animated) {
36681                     me.ownerCt.layout.layout();
36682                 }
36683             } else if (me.reExpander.temporary) {
36684                 me.doComponentLayout();
36685             }
36686         }
36687
36688         if (me.resizer) {
36689             me.resizer.disable();
36690         }
36691
36692         // If me Panel was configured with a collapse tool in its header, flip it's type
36693         if (me.collapseTool) {
36694             me.collapseTool.setType('expand-' + me.expandDirection);
36695         }
36696         if (!internal) {
36697             me.fireEvent('collapse', me);
36698         }
36699
36700         // Re-enable the toggle tool after an animated collapse
36701         if (animated && me.collapseTool) {
36702             me.collapseTool.enable();
36703         }
36704     },
36705
36706     /**
36707      * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
36708      * expand action if it returns false.
36709      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
36710      * {@link #animCollapse} panel config)
36711      * @return {Ext.panel.Panel} this
36712      */
36713     expand: function(animate) {
36714         var me = this;
36715         if (!me.collapsed || me.fireEvent('beforeexpand', me, animate) === false) {
36716             return false;
36717         }
36718
36719         var i = 0,
36720             l = me.hiddenDocked.length,
36721             direction = me.expandDirection,
36722             height = me.getHeight(),
36723             width = me.getWidth(),
36724             pos, anim;
36725
36726         // Disable toggle tool during animated expand
36727         if (animate && me.collapseTool) {
36728             me.collapseTool.disable();
36729         }
36730
36731         // Show any docked items that we hid on collapse
36732         // And hide the injected reExpander Header
36733         for (; i < l; i++) {
36734             me.hiddenDocked[i].hidden = false;
36735             me.hiddenDocked[i].el.show();
36736         }
36737         if (me.reExpander) {
36738             if (me.reExpander.temporary) {
36739                 me.reExpander.hide();
36740             } else {
36741                 me.reExpander.removeClsWithUI(me.collapsedCls);
36742                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
36743                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
36744                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
36745                 }
36746                 me.reExpander.updateFrame();
36747             }
36748         }
36749
36750         // If me Panel was configured with a collapse tool in its header, flip it's type
36751         if (me.collapseTool) {
36752             me.collapseTool.setType('collapse-' + me.collapseDirection);
36753         }
36754
36755         // Restore body display and scroll position
36756         me.body.setStyle('display', '');
36757         me.body.dom.scrollTop = me.saveScrollTop;
36758
36759         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
36760         me.collapsed = false;
36761
36762         // Remove any collapsed styling before any animation begins
36763         me.removeClsWithUI(me.collapsedCls);
36764         // if (me.border === false) {
36765         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
36766         // }
36767
36768         anim = {
36769             to: {
36770             },
36771             from: {
36772                 height: height,
36773                 width: width
36774             },
36775             listeners: {
36776                 afteranimate: me.afterExpand,
36777                 scope: me
36778             }
36779         };
36780
36781         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
36782
36783             // Restore the collapsed dimension.
36784             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
36785             me.collapseMemento.restore('height', false);
36786
36787             // If autoHeight, measure the height now we have shown the body element.
36788             if (me.height === undefined) {
36789                 me.setCalculatedSize(me.width, null);
36790                 anim.to.height = me.getHeight();
36791
36792                 // Must size back down to collapsed for the animation.
36793                 me.setCalculatedSize(me.width, anim.from.height);
36794             }
36795             // If we were flexed, then we can't just restore to the saved size.
36796             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
36797             else if (me.savedFlex) {
36798                 me.flex = me.savedFlex;
36799                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
36800                 delete me.flex;
36801             }
36802             // Else, restore to saved height
36803             else {
36804                 anim.to.height = me.height;
36805             }
36806
36807             // top needs animating upwards
36808             if (direction == Ext.Component.DIRECTION_TOP) {
36809                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
36810                 anim.from.top = pos;
36811                 anim.to.top = pos - (anim.to.height - height);
36812             }
36813         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
36814
36815             // Restore the collapsed dimension.
36816             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
36817             me.collapseMemento.restore('width', false);
36818
36819             // If autoWidth, measure the width now we have shown the body element.
36820             if (me.width === undefined) {
36821                 me.setCalculatedSize(null, me.height);
36822                 anim.to.width = me.getWidth();
36823
36824                 // Must size back down to collapsed for the animation.
36825                 me.setCalculatedSize(anim.from.width, me.height);
36826             }
36827             // If we were flexed, then we can't just restore to the saved size.
36828             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
36829             else if (me.savedFlex) {
36830                 me.flex = me.savedFlex;
36831                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
36832                 delete me.flex;
36833             }
36834             // Else, restore to saved width
36835             else {
36836                 anim.to.width = me.width;
36837             }
36838
36839             // left needs animating leftwards
36840             if (direction == Ext.Component.DIRECTION_LEFT) {
36841                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
36842                 anim.from.left = pos;
36843                 anim.to.left = pos - (anim.to.width - width);
36844             }
36845         }
36846
36847         if (animate) {
36848             me.animate(anim);
36849         } else {
36850             me.setCalculatedSize(anim.to.width, anim.to.height);
36851             if (anim.to.x) {
36852                 me.setLeft(anim.to.x);
36853             }
36854             if (anim.to.y) {
36855                 me.setTop(anim.to.y);
36856             }
36857             me.afterExpand(false);
36858         }
36859
36860         return me;
36861     },
36862
36863     afterExpand: function(animated) {
36864         var me = this;
36865
36866         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
36867         if (me.savedFlex) {
36868             me.flex = me.savedFlex;
36869             delete me.savedFlex;
36870             delete me.width;
36871             delete me.height;
36872         }
36873
36874         // Restore width/height and dimension management flags to original values
36875         if (me.collapseMemento) {
36876             me.collapseMemento.restoreAll();
36877         }
36878
36879         if (animated && me.ownerCt) {
36880             // IE 6 has an intermittent repaint issue in this case so give
36881             // it a little extra time to catch up before laying out.
36882             Ext.defer(me.ownerCt.doLayout, Ext.isIE6 ? 1 : 0, me);
36883         }
36884
36885         if (me.resizer) {
36886             me.resizer.enable();
36887         }
36888
36889         me.fireEvent('expand', me);
36890
36891         // Re-enable the toggle tool after an animated expand
36892         if (animated && me.collapseTool) {
36893             me.collapseTool.enable();
36894         }
36895     },
36896
36897     /**
36898      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
36899      * @return {Ext.panel.Panel} this
36900      */
36901     toggleCollapse: function() {
36902         if (this.collapsed) {
36903             this.expand(this.animCollapse);
36904         } else {
36905             this.collapse(this.collapseDirection, this.animCollapse);
36906         }
36907         return this;
36908     },
36909
36910     // private
36911     getKeyMap : function(){
36912         if(!this.keyMap){
36913             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
36914         }
36915         return this.keyMap;
36916     },
36917
36918     // private
36919     initDraggable : function(){
36920         /**
36921          * @property {Ext.dd.DragSource} dd
36922          * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
36923          * Ext.dd.DragSource} which handles dragging the Panel.
36924          *
36925          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
36926          * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
36927          */
36928         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
36929     },
36930
36931     // private - helper function for ghost
36932     ghostTools : function() {
36933         var tools = [],
36934             headerTools = this.header.query('tool[hidden=false]');
36935
36936         if (headerTools.length) {
36937             Ext.each(headerTools, function(tool) {
36938                 // Some tools can be full components, and copying them into the ghost
36939                 // actually removes them from the owning panel. You could also potentially
36940                 // end up with duplicate DOM ids as well. To avoid any issues we just make
36941                 // a simple bare-minimum clone of each tool for ghosting purposes.
36942                 tools.push({
36943                     type: tool.type
36944                 });
36945             });
36946         } else {
36947             tools = [{
36948                 type: 'placeholder'
36949             }];
36950         }
36951         return tools;
36952     },
36953
36954     // private - used for dragging
36955     ghost: function(cls) {
36956         var me = this,
36957             ghostPanel = me.ghostPanel,
36958             box = me.getBox(),
36959             header;
36960
36961         if (!ghostPanel) {
36962             ghostPanel = Ext.create('Ext.panel.Panel', {
36963                 renderTo: me.floating ? me.el.dom.parentNode : document.body,
36964                 floating: {
36965                     shadow: false
36966                 },
36967                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
36968                 overlapHeader: me.overlapHeader,
36969                 headerPosition: me.headerPosition,
36970                 baseCls: me.baseCls,
36971                 cls: me.baseCls + '-ghost ' + (cls ||'')
36972             });
36973             me.ghostPanel = ghostPanel;
36974         }
36975         ghostPanel.floatParent = me.floatParent;
36976         if (me.floating) {
36977             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
36978         } else {
36979             ghostPanel.toFront();
36980         }
36981         header = ghostPanel.header;
36982         // restore options
36983         if (header) {
36984             header.suspendLayout = true;
36985             Ext.Array.forEach(header.query('tool'), function(tool){
36986                 header.remove(tool);
36987             });
36988             header.suspendLayout = false;
36989         }
36990         ghostPanel.addTool(me.ghostTools());
36991         ghostPanel.setTitle(me.title);
36992         ghostPanel.setIconCls(me.iconCls);
36993
36994         ghostPanel.el.show();
36995         ghostPanel.setPosition(box.x, box.y);
36996         ghostPanel.setSize(box.width, box.height);
36997         me.el.hide();
36998         if (me.floatingItems) {
36999             me.floatingItems.hide();
37000         }
37001         return ghostPanel;
37002     },
37003
37004     // private
37005     unghost: function(show, matchPosition) {
37006         var me = this;
37007         if (!me.ghostPanel) {
37008             return;
37009         }
37010         if (show !== false) {
37011             me.el.show();
37012             if (matchPosition !== false) {
37013                 me.setPosition(me.ghostPanel.getPosition());
37014             }
37015             if (me.floatingItems) {
37016                 me.floatingItems.show();
37017             }
37018             Ext.defer(me.focus, 10, me);
37019         }
37020         me.ghostPanel.el.hide();
37021     },
37022
37023     initResizable: function(resizable) {
37024         if (this.collapsed) {
37025             resizable.disabled = true;
37026         }
37027         this.callParent([resizable]);
37028     }
37029 }, function(){
37030     this.prototype.animCollapse = Ext.enableFx;
37031 });
37032
37033 /**
37034  * Component layout for Tip/ToolTip/etc. components
37035  * @class Ext.layout.component.Tip
37036  * @extends Ext.layout.component.Dock
37037  * @private
37038  */
37039
37040 Ext.define('Ext.layout.component.Tip', {
37041
37042     /* Begin Definitions */
37043
37044     alias: ['layout.tip'],
37045
37046     extend: 'Ext.layout.component.Dock',
37047
37048     /* End Definitions */
37049
37050     type: 'tip',
37051     
37052     onLayout: function(width, height) {
37053         var me = this,
37054             owner = me.owner,
37055             el = owner.el,
37056             minWidth,
37057             maxWidth,
37058             naturalWidth,
37059             constrainedWidth,
37060             xy = el.getXY();
37061
37062         // Position offscreen so the natural width is not affected by the viewport's right edge
37063         el.setXY([-9999,-9999]);
37064
37065         // Calculate initial layout
37066         this.callParent(arguments);
37067
37068         // Handle min/maxWidth for auto-width tips
37069         if (!Ext.isNumber(width)) {
37070             minWidth = owner.minWidth;
37071             maxWidth = owner.maxWidth;
37072             // IE6/7 in strict mode have a problem doing an autoWidth
37073             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
37074                 constrainedWidth = me.doAutoWidth();
37075             } else {
37076                 naturalWidth = el.getWidth();
37077             }
37078             if (naturalWidth < minWidth) {
37079                 constrainedWidth = minWidth;
37080             }
37081             else if (naturalWidth > maxWidth) {
37082                 constrainedWidth = maxWidth;
37083             }
37084             if (constrainedWidth) {
37085                 this.callParent([constrainedWidth, height]);
37086             }
37087         }
37088
37089         // Restore position
37090         el.setXY(xy);
37091     },
37092     
37093     doAutoWidth: function(){
37094         var me = this,
37095             owner = me.owner,
37096             body = owner.body,
37097             width = body.getTextWidth();
37098             
37099         if (owner.header) {
37100             width = Math.max(width, owner.header.getWidth());
37101         }
37102         if (!Ext.isDefined(me.frameWidth)) {
37103             me.frameWidth = owner.el.getWidth() - body.getWidth();
37104         }
37105         width += me.frameWidth + body.getPadding('lr');
37106         return width;
37107     }
37108 });
37109
37110 /**
37111  * @class Ext.tip.Tip
37112  * @extends Ext.panel.Panel
37113  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
37114  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
37115  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
37116  * @xtype tip
37117  */
37118 Ext.define('Ext.tip.Tip', {
37119     extend: 'Ext.panel.Panel',
37120     requires: [ 'Ext.layout.component.Tip' ],
37121     alternateClassName: 'Ext.Tip',
37122     /**
37123      * @cfg {Boolean} [closable=false]
37124      * True to render a close tool button into the tooltip header.
37125      */
37126     /**
37127      * @cfg {Number} width
37128      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
37129      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
37130      */
37131     /**
37132      * @cfg {Number} minWidth The minimum width of the tip in pixels.
37133      */
37134     minWidth : 40,
37135     /**
37136      * @cfg {Number} maxWidth The maximum width of the tip in pixels.  The maximum supported value is 500.
37137      */
37138     maxWidth : 300,
37139     /**
37140      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
37141      * for bottom-right shadow.
37142      */
37143     shadow : "sides",
37144
37145     /**
37146      * @cfg {String} defaultAlign
37147      * <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value for this tip relative
37148      * to its element of origin.
37149      */
37150     defaultAlign : "tl-bl?",
37151     /**
37152      * @cfg {Boolean} constrainPosition
37153      * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
37154      */
37155     constrainPosition : true,
37156
37157     // @inherited
37158     frame: false,
37159
37160     // private panel overrides
37161     autoRender: true,
37162     hidden: true,
37163     baseCls: Ext.baseCSSPrefix + 'tip',
37164     floating: {
37165         shadow: true,
37166         shim: true,
37167         constrain: true
37168     },
37169     focusOnToFront: false,
37170     componentLayout: 'tip',
37171
37172     /**
37173      * @cfg {String} closeAction
37174      * <p>The action to take when the close header tool is clicked:
37175      * <div class="mdetail-params"><ul>
37176      * <li><b><code>'{@link #destroy}'</code></b> : <div class="sub-desc">
37177      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
37178      * it and all descendant Components. The window will <b>not</b> be available to be
37179      * redisplayed via the {@link #show} method.
37180      * </div></li>
37181      * <li><b><code>'{@link #hide}'</code></b> : <b>Default</b><div class="sub-desc">
37182      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
37183      * The window will be available to be redisplayed via the {@link #show} method.
37184      * </div></li>
37185      * </ul></div>
37186      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
37187      * which will invoke the approriate closeAction.
37188      */
37189     closeAction: 'hide',
37190
37191     ariaRole: 'tooltip',
37192
37193     initComponent: function() {
37194         var me = this;
37195
37196         me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
37197         me.callParent(arguments);
37198
37199         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
37200         me.constrain = me.constrain || me.constrainPosition;
37201     },
37202
37203     /**
37204      * Shows this tip at the specified XY position.  Example usage:
37205      * <pre><code>
37206 // Show the tip at x:50 and y:100
37207 tip.showAt([50,100]);
37208 </code></pre>
37209      * @param {Number[]} xy An array containing the x and y coordinates
37210      */
37211     showAt : function(xy){
37212         var me = this;
37213         this.callParent(arguments);
37214         // Show may have been vetoed.
37215         if (me.isVisible()) {
37216             me.setPagePosition(xy[0], xy[1]);
37217             if (me.constrainPosition || me.constrain) {
37218                 me.doConstrain();
37219             }
37220             me.toFront(true);
37221         }
37222     },
37223
37224     /**
37225      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
37226      * anchor position value.  Example usage:
37227      * <pre><code>
37228 // Show the tip at the default position ('tl-br?')
37229 tip.showBy('my-el');
37230
37231 // Show the tip's top-left corner anchored to the element's top-right corner
37232 tip.showBy('my-el', 'tl-tr');
37233 </code></pre>
37234      * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
37235      * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
37236      * {@link #defaultAlign} if specified).
37237      */
37238     showBy : function(el, pos) {
37239         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
37240     },
37241
37242     /**
37243      * @private
37244      * @override
37245      * Set Tip draggable using base Component's draggability
37246      */
37247     initDraggable : function(){
37248         var me = this;
37249         me.draggable = {
37250             el: me.getDragEl(),
37251             delegate: me.header.el,
37252             constrain: me,
37253             constrainTo: me.el.getScopeParent()
37254         };
37255         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
37256         Ext.Component.prototype.initDraggable.call(me);
37257     },
37258
37259     // Tip does not ghost. Drag is "live"
37260     ghost: undefined,
37261     unghost: undefined
37262 });
37263
37264 /**
37265  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
37266  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
37267  * control over the tooltip's alignment relative to the target element or mouse, and the timing
37268  * of when it is automatically shown and hidden.
37269  *
37270  * This implementation does **not** have a built-in method of automatically populating the tooltip's
37271  * text based on the target element; you must either configure a fixed {@link #html} value for each
37272  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
37273  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
37274  * convenient way of automatically populating and configuring a tooltip based on specific DOM
37275  * attributes of each target element.
37276  *
37277  * # Basic Example
37278  *
37279  *     var tip = Ext.create('Ext.tip.ToolTip', {
37280  *         target: 'clearButton',
37281  *         html: 'Press this button to clear the form'
37282  *     });
37283  *
37284  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
37285  *
37286  * # Delegation
37287  *
37288  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
37289  * one ToolTip to many elements under a common parent. This is more efficient than creating many
37290  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
37291  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
37292  * appropriate sub-elements.
37293  *
37294  * When using delegation, it is likely that you will want to programmatically change the content
37295  * of the ToolTip based on each delegate element; you can do this by implementing a custom
37296  * listener for the {@link #beforeshow} event. Example:
37297  *
37298  *     var store = Ext.create('Ext.data.ArrayStore', {
37299  *         fields: ['company', 'price', 'change'],
37300  *         data: [
37301  *             ['3m Co',                               71.72, 0.02],
37302  *             ['Alcoa Inc',                           29.01, 0.42],
37303  *             ['Altria Group Inc',                    83.81, 0.28],
37304  *             ['American Express Company',            52.55, 0.01],
37305  *             ['American International Group, Inc.',  64.13, 0.31],
37306  *             ['AT&T Inc.',                           31.61, -0.48]
37307  *         ]
37308  *     });
37309  *
37310  *     var grid = Ext.create('Ext.grid.Panel', {
37311  *         title: 'Array Grid',
37312  *         store: store,
37313  *         columns: [
37314  *             {text: 'Company', flex: 1, dataIndex: 'company'},
37315  *             {text: 'Price', width: 75, dataIndex: 'price'},
37316  *             {text: 'Change', width: 75, dataIndex: 'change'}
37317  *         ],
37318  *         height: 200,
37319  *         width: 400,
37320  *         renderTo: Ext.getBody()
37321  *     });
37322  *
37323  *     grid.getView().on('render', function(view) {
37324  *         view.tip = Ext.create('Ext.tip.ToolTip', {
37325  *             // The overall target element.
37326  *             target: view.el,
37327  *             // Each grid row causes its own seperate show and hide.
37328  *             delegate: view.itemSelector,
37329  *             // Moving within the row should not hide the tip.
37330  *             trackMouse: true,
37331  *             // Render immediately so that tip.body can be referenced prior to the first show.
37332  *             renderTo: Ext.getBody(),
37333  *             listeners: {
37334  *                 // Change content dynamically depending on which element triggered the show.
37335  *                 beforeshow: function updateTipBody(tip) {
37336  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
37337  *                 }
37338  *             }
37339  *         });
37340  *     });
37341  *
37342  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
37343  *
37344  * # Alignment
37345  *
37346  * The following configuration properties allow control over how the ToolTip is aligned relative to
37347  * the target element and/or mouse pointer:
37348  *
37349  * - {@link #anchor}
37350  * - {@link #anchorToTarget}
37351  * - {@link #anchorOffset}
37352  * - {@link #trackMouse}
37353  * - {@link #mouseOffset}
37354  *
37355  * # Showing/Hiding
37356  *
37357  * The following configuration properties allow control over how and when the ToolTip is automatically
37358  * shown and hidden:
37359  *
37360  * - {@link #autoHide}
37361  * - {@link #showDelay}
37362  * - {@link #hideDelay}
37363  * - {@link #dismissDelay}
37364  *
37365  * @docauthor Jason Johnston <jason@sencha.com>
37366  */
37367 Ext.define('Ext.tip.ToolTip', {
37368     extend: 'Ext.tip.Tip',
37369     alias: 'widget.tooltip',
37370     alternateClassName: 'Ext.ToolTip',
37371     /**
37372      * @property {HTMLElement} triggerElement
37373      * When a ToolTip is configured with the `{@link #delegate}`
37374      * option to cause selected child elements of the `{@link #target}`
37375      * Element to each trigger a seperate show event, this property is set to
37376      * the DOM element which triggered the show.
37377      */
37378     /**
37379      * @cfg {HTMLElement/Ext.Element/String} target
37380      * The target element or string id to monitor for mouseover events to trigger
37381      * showing this ToolTip.
37382      */
37383     /**
37384      * @cfg {Boolean} [autoHide=true]
37385      * True to automatically hide the tooltip after the
37386      * mouse exits the target element or after the `{@link #dismissDelay}`
37387      * has expired if set.  If `{@link #closable} = true`
37388      * a close tool button will be rendered into the tooltip header.
37389      */
37390     /**
37391      * @cfg {Number} showDelay
37392      * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
37393      */
37394     showDelay: 500,
37395     /**
37396      * @cfg {Number} hideDelay
37397      * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
37398      * Set to 0 for the tooltip to hide immediately.
37399      */
37400     hideDelay: 200,
37401     /**
37402      * @cfg {Number} dismissDelay
37403      * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
37404      * dismissDelay = 0.
37405      */
37406     dismissDelay: 5000,
37407     /**
37408      * @cfg {Number[]} [mouseOffset=[15,18]]
37409      * An XY offset from the mouse position where the tooltip should be shown.
37410      */
37411     /**
37412      * @cfg {Boolean} trackMouse
37413      * True to have the tooltip follow the mouse as it moves over the target element.
37414      */
37415     trackMouse: false,
37416     /**
37417      * @cfg {String} anchor
37418      * If specified, indicates that the tip should be anchored to a
37419      * particular side of the target element or mouse pointer ("top", "right", "bottom",
37420      * or "left"), with an arrow pointing back at the target or mouse pointer. If
37421      * {@link #constrainPosition} is enabled, this will be used as a preferred value
37422      * only and may be flipped as needed.
37423      */
37424     /**
37425      * @cfg {Boolean} anchorToTarget
37426      * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
37427      * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
37428      * target element.  When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
37429      */
37430     anchorToTarget: true,
37431     /**
37432      * @cfg {Number} anchorOffset
37433      * A numeric pixel value used to offset the default position of the anchor arrow.  When the anchor
37434      * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
37435      * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
37436      * a vertical offset.
37437      */
37438     anchorOffset: 0,
37439     /**
37440      * @cfg {String} delegate
37441      *
37442      * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
37443      * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
37444      * target.
37445      *
37446      * When specified, the child element of the target which caused a show event is placed into the
37447      * `{@link #triggerElement}` property before the ToolTip is shown.
37448      *
37449      * This may be useful when a Component has regular, repeating elements in it, each of which need a
37450      * ToolTip which contains information specific to that element.
37451      *
37452      * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
37453      */
37454
37455     // private
37456     targetCounter: 0,
37457     quickShowInterval: 250,
37458
37459     // private
37460     initComponent: function() {
37461         var me = this;
37462         me.callParent(arguments);
37463         me.lastActive = new Date();
37464         me.setTarget(me.target);
37465         me.origAnchor = me.anchor;
37466     },
37467
37468     // private
37469     onRender: function(ct, position) {
37470         var me = this;
37471         me.callParent(arguments);
37472         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
37473         me.anchorEl = me.el.createChild({
37474             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
37475         });
37476     },
37477
37478     // private
37479     afterRender: function() {
37480         var me = this,
37481             zIndex;
37482
37483         me.callParent(arguments);
37484         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
37485         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
37486     },
37487
37488     /**
37489      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
37490      * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
37491      */
37492     setTarget: function(target) {
37493         var me = this,
37494             t = Ext.get(target),
37495             tg;
37496
37497         if (me.target) {
37498             tg = Ext.get(me.target);
37499             me.mun(tg, 'mouseover', me.onTargetOver, me);
37500             me.mun(tg, 'mouseout', me.onTargetOut, me);
37501             me.mun(tg, 'mousemove', me.onMouseMove, me);
37502         }
37503
37504         me.target = t;
37505         if (t) {
37506
37507             me.mon(t, {
37508                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
37509                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
37510                 freezeEvent: true,
37511
37512                 mouseover: me.onTargetOver,
37513                 mouseout: me.onTargetOut,
37514                 mousemove: me.onMouseMove,
37515                 scope: me
37516             });
37517         }
37518         if (me.anchor) {
37519             me.anchorTarget = me.target;
37520         }
37521     },
37522
37523     // private
37524     onMouseMove: function(e) {
37525         var me = this,
37526             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
37527             xy;
37528         if (t) {
37529             me.targetXY = e.getXY();
37530             if (t === me.triggerElement) {
37531                 if (!me.hidden && me.trackMouse) {
37532                     xy = me.getTargetXY();
37533                     if (me.constrainPosition) {
37534                         xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
37535                     }
37536                     me.setPagePosition(xy);
37537                 }
37538             } else {
37539                 me.hide();
37540                 me.lastActive = new Date(0);
37541                 me.onTargetOver(e);
37542             }
37543         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
37544             me.hide();
37545         }
37546     },
37547
37548     // private
37549     getTargetXY: function() {
37550         var me = this,
37551             mouseOffset;
37552         if (me.delegate) {
37553             me.anchorTarget = me.triggerElement;
37554         }
37555         if (me.anchor) {
37556             me.targetCounter++;
37557                 var offsets = me.getOffsets(),
37558                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
37559                     dw = Ext.Element.getViewWidth() - 5,
37560                     dh = Ext.Element.getViewHeight() - 5,
37561                     de = document.documentElement,
37562                     bd = document.body,
37563                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
37564                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
37565                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
37566                     sz = me.getSize(),
37567                     constrainPosition = me.constrainPosition;
37568
37569             me.anchorEl.removeCls(me.anchorCls);
37570
37571             if (me.targetCounter < 2 && constrainPosition) {
37572                 if (axy[0] < scrollX) {
37573                     if (me.anchorToTarget) {
37574                         me.defaultAlign = 'l-r';
37575                         if (me.mouseOffset) {
37576                             me.mouseOffset[0] *= -1;
37577                         }
37578                     }
37579                     me.anchor = 'left';
37580                     return me.getTargetXY();
37581                 }
37582                 if (axy[0] + sz.width > dw) {
37583                     if (me.anchorToTarget) {
37584                         me.defaultAlign = 'r-l';
37585                         if (me.mouseOffset) {
37586                             me.mouseOffset[0] *= -1;
37587                         }
37588                     }
37589                     me.anchor = 'right';
37590                     return me.getTargetXY();
37591                 }
37592                 if (axy[1] < scrollY) {
37593                     if (me.anchorToTarget) {
37594                         me.defaultAlign = 't-b';
37595                         if (me.mouseOffset) {
37596                             me.mouseOffset[1] *= -1;
37597                         }
37598                     }
37599                     me.anchor = 'top';
37600                     return me.getTargetXY();
37601                 }
37602                 if (axy[1] + sz.height > dh) {
37603                     if (me.anchorToTarget) {
37604                         me.defaultAlign = 'b-t';
37605                         if (me.mouseOffset) {
37606                             me.mouseOffset[1] *= -1;
37607                         }
37608                     }
37609                     me.anchor = 'bottom';
37610                     return me.getTargetXY();
37611                 }
37612             }
37613
37614             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
37615             me.anchorEl.addCls(me.anchorCls);
37616             me.targetCounter = 0;
37617             return axy;
37618         } else {
37619             mouseOffset = me.getMouseOffset();
37620             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
37621         }
37622     },
37623
37624     getMouseOffset: function() {
37625         var me = this,
37626         offset = me.anchor ? [0, 0] : [15, 18];
37627         if (me.mouseOffset) {
37628             offset[0] += me.mouseOffset[0];
37629             offset[1] += me.mouseOffset[1];
37630         }
37631         return offset;
37632     },
37633
37634     // private
37635     getAnchorPosition: function() {
37636         var me = this,
37637             m;
37638         if (me.anchor) {
37639             me.tipAnchor = me.anchor.charAt(0);
37640         } else {
37641             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
37642             //<debug>
37643             if (!m) {
37644                 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
37645             }
37646             //</debug>
37647             me.tipAnchor = m[1].charAt(0);
37648         }
37649
37650         switch (me.tipAnchor) {
37651         case 't':
37652             return 'top';
37653         case 'b':
37654             return 'bottom';
37655         case 'r':
37656             return 'right';
37657         }
37658         return 'left';
37659     },
37660
37661     // private
37662     getAnchorAlign: function() {
37663         switch (this.anchor) {
37664         case 'top':
37665             return 'tl-bl';
37666         case 'left':
37667             return 'tl-tr';
37668         case 'right':
37669             return 'tr-tl';
37670         default:
37671             return 'bl-tl';
37672         }
37673     },
37674
37675     // private
37676     getOffsets: function() {
37677         var me = this,
37678             mouseOffset,
37679             offsets,
37680             ap = me.getAnchorPosition().charAt(0);
37681         if (me.anchorToTarget && !me.trackMouse) {
37682             switch (ap) {
37683             case 't':
37684                 offsets = [0, 9];
37685                 break;
37686             case 'b':
37687                 offsets = [0, -13];
37688                 break;
37689             case 'r':
37690                 offsets = [ - 13, 0];
37691                 break;
37692             default:
37693                 offsets = [9, 0];
37694                 break;
37695             }
37696         } else {
37697             switch (ap) {
37698             case 't':
37699                 offsets = [ - 15 - me.anchorOffset, 30];
37700                 break;
37701             case 'b':
37702                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
37703                 break;
37704             case 'r':
37705                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
37706                 break;
37707             default:
37708                 offsets = [25, -13 - me.anchorOffset];
37709                 break;
37710             }
37711         }
37712         mouseOffset = me.getMouseOffset();
37713         offsets[0] += mouseOffset[0];
37714         offsets[1] += mouseOffset[1];
37715
37716         return offsets;
37717     },
37718
37719     // private
37720     onTargetOver: function(e) {
37721         var me = this,
37722             t;
37723
37724         if (me.disabled || e.within(me.target.dom, true)) {
37725             return;
37726         }
37727         t = e.getTarget(me.delegate);
37728         if (t) {
37729             me.triggerElement = t;
37730             me.clearTimer('hide');
37731             me.targetXY = e.getXY();
37732             me.delayShow();
37733         }
37734     },
37735
37736     // private
37737     delayShow: function() {
37738         var me = this;
37739         if (me.hidden && !me.showTimer) {
37740             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
37741                 me.show();
37742             } else {
37743                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
37744             }
37745         }
37746         else if (!me.hidden && me.autoHide !== false) {
37747             me.show();
37748         }
37749     },
37750
37751     // private
37752     onTargetOut: function(e) {
37753         var me = this;
37754         if (me.disabled || e.within(me.target.dom, true)) {
37755             return;
37756         }
37757         me.clearTimer('show');
37758         if (me.autoHide !== false) {
37759             me.delayHide();
37760         }
37761     },
37762
37763     // private
37764     delayHide: function() {
37765         var me = this;
37766         if (!me.hidden && !me.hideTimer) {
37767             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
37768         }
37769     },
37770
37771     /**
37772      * Hides this tooltip if visible.
37773      */
37774     hide: function() {
37775         var me = this;
37776         me.clearTimer('dismiss');
37777         me.lastActive = new Date();
37778         if (me.anchorEl) {
37779             me.anchorEl.hide();
37780         }
37781         me.callParent(arguments);
37782         delete me.triggerElement;
37783     },
37784
37785     /**
37786      * Shows this tooltip at the current event target XY position.
37787      */
37788     show: function() {
37789         var me = this;
37790
37791         // Show this Component first, so that sizing can be calculated
37792         // pre-show it off screen so that the el will have dimensions
37793         this.callParent();
37794         if (this.hidden === false) {
37795             me.setPagePosition(-10000, -10000);
37796
37797             if (me.anchor) {
37798                 me.anchor = me.origAnchor;
37799             }
37800             me.showAt(me.getTargetXY());
37801
37802             if (me.anchor) {
37803                 me.syncAnchor();
37804                 me.anchorEl.show();
37805             } else {
37806                 me.anchorEl.hide();
37807             }
37808         }
37809     },
37810
37811     // inherit docs
37812     showAt: function(xy) {
37813         var me = this;
37814         me.lastActive = new Date();
37815         me.clearTimers();
37816
37817         // Only call if this is hidden. May have been called from show above.
37818         if (!me.isVisible()) {
37819             this.callParent(arguments);
37820         }
37821
37822         // Show may have been vetoed.
37823         if (me.isVisible()) {
37824             me.setPagePosition(xy[0], xy[1]);
37825             if (me.constrainPosition || me.constrain) {
37826                 me.doConstrain();
37827             }
37828             me.toFront(true);
37829         }
37830
37831         if (me.dismissDelay && me.autoHide !== false) {
37832             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
37833         }
37834         if (me.anchor) {
37835             me.syncAnchor();
37836             if (!me.anchorEl.isVisible()) {
37837                 me.anchorEl.show();
37838             }
37839         } else {
37840             me.anchorEl.hide();
37841         }
37842     },
37843
37844     // private
37845     syncAnchor: function() {
37846         var me = this,
37847             anchorPos,
37848             targetPos,
37849             offset;
37850         switch (me.tipAnchor.charAt(0)) {
37851         case 't':
37852             anchorPos = 'b';
37853             targetPos = 'tl';
37854             offset = [20 + me.anchorOffset, 1];
37855             break;
37856         case 'r':
37857             anchorPos = 'l';
37858             targetPos = 'tr';
37859             offset = [ - 1, 12 + me.anchorOffset];
37860             break;
37861         case 'b':
37862             anchorPos = 't';
37863             targetPos = 'bl';
37864             offset = [20 + me.anchorOffset, -1];
37865             break;
37866         default:
37867             anchorPos = 'r';
37868             targetPos = 'tl';
37869             offset = [1, 12 + me.anchorOffset];
37870             break;
37871         }
37872         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
37873     },
37874
37875     // private
37876     setPagePosition: function(x, y) {
37877         var me = this;
37878         me.callParent(arguments);
37879         if (me.anchor) {
37880             me.syncAnchor();
37881         }
37882     },
37883
37884     // private
37885     clearTimer: function(name) {
37886         name = name + 'Timer';
37887         clearTimeout(this[name]);
37888         delete this[name];
37889     },
37890
37891     // private
37892     clearTimers: function() {
37893         var me = this;
37894         me.clearTimer('show');
37895         me.clearTimer('dismiss');
37896         me.clearTimer('hide');
37897     },
37898
37899     // private
37900     onShow: function() {
37901         var me = this;
37902         me.callParent();
37903         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
37904     },
37905
37906     // private
37907     onHide: function() {
37908         var me = this;
37909         me.callParent();
37910         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
37911     },
37912
37913     // private
37914     onDocMouseDown: function(e) {
37915         var me = this;
37916         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
37917             me.disable();
37918             Ext.defer(me.doEnable, 100, me);
37919         }
37920     },
37921
37922     // private
37923     doEnable: function() {
37924         if (!this.isDestroyed) {
37925             this.enable();
37926         }
37927     },
37928
37929     // private
37930     onDisable: function() {
37931         this.callParent();
37932         this.clearTimers();
37933         this.hide();
37934     },
37935
37936     beforeDestroy: function() {
37937         var me = this;
37938         me.clearTimers();
37939         Ext.destroy(me.anchorEl);
37940         delete me.anchorEl;
37941         delete me.target;
37942         delete me.anchorTarget;
37943         delete me.triggerElement;
37944         me.callParent();
37945     },
37946
37947     // private
37948     onDestroy: function() {
37949         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
37950         this.callParent();
37951     }
37952 });
37953
37954 /**
37955  * @class Ext.tip.QuickTip
37956  * @extends Ext.tip.ToolTip
37957  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
37958  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager documentation for additional usage details and examples.
37959  * @xtype quicktip
37960  */
37961 Ext.define('Ext.tip.QuickTip', {
37962     extend: 'Ext.tip.ToolTip',
37963     alternateClassName: 'Ext.QuickTip',
37964     /**
37965      * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
37966      */
37967     /**
37968      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
37969      */
37970     interceptTitles : false,
37971
37972     // Force creation of header Component
37973     title: '&#160;',
37974
37975     // private
37976     tagConfig : {
37977         namespace : "data-",
37978         attribute : "qtip",
37979         width : "qwidth",
37980         target : "target",
37981         title : "qtitle",
37982         hide : "hide",
37983         cls : "qclass",
37984         align : "qalign",
37985         anchor : "anchor"
37986     },
37987
37988     // private
37989     initComponent : function(){
37990         var me = this;
37991
37992         me.target = me.target || Ext.getDoc();
37993         me.targets = me.targets || {};
37994         me.callParent();
37995     },
37996
37997     /**
37998      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
37999      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
38000      * <div class="mdetail-params"><ul>
38001      * <li>autoHide</li>
38002      * <li>cls</li>
38003      * <li>dismissDelay (overrides the singleton value)</li>
38004      * <li>target (required)</li>
38005      * <li>text (required)</li>
38006      * <li>title</li>
38007      * <li>width</li></ul></div>
38008      * @param {Object} config The config object
38009      */
38010     register : function(config){
38011         var configs = Ext.isArray(config) ? config : arguments,
38012             i = 0,
38013             len = configs.length,
38014             target, j, targetLen;
38015
38016         for (; i < len; i++) {
38017             config = configs[i];
38018             target = config.target;
38019             if (target) {
38020                 if (Ext.isArray(target)) {
38021                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
38022                         this.targets[Ext.id(target[j])] = config;
38023                     }
38024                 } else{
38025                     this.targets[Ext.id(target)] = config;
38026                 }
38027             }
38028         }
38029     },
38030
38031     /**
38032      * Removes this quick tip from its element and destroys it.
38033      * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
38034      */
38035     unregister : function(el){
38036         delete this.targets[Ext.id(el)];
38037     },
38038
38039     /**
38040      * Hides a visible tip or cancels an impending show for a particular element.
38041      * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
38042      */
38043     cancelShow: function(el){
38044         var me = this,
38045             activeTarget = me.activeTarget;
38046
38047         el = Ext.get(el).dom;
38048         if (me.isVisible()) {
38049             if (activeTarget && activeTarget.el == el) {
38050                 me.hide();
38051             }
38052         } else if (activeTarget && activeTarget.el == el) {
38053             me.clearTimer('show');
38054         }
38055     },
38056
38057     /**
38058      * @private
38059      * Reads the tip text from the closest node to the event target which contains the attribute we
38060      * are configured to look for. Returns an object containing the text from the attribute, and the target element from
38061      * which the text was read.
38062      */
38063     getTipCfg: function(e) {
38064         var t = e.getTarget(),
38065             titleText = t.title,
38066             cfg;
38067
38068         if (this.interceptTitles && titleText && Ext.isString(titleText)) {
38069             t.qtip = titleText;
38070             t.removeAttribute("title");
38071             e.preventDefault();
38072             return {
38073                 text: titleText
38074             };
38075         }
38076         else {
38077             cfg = this.tagConfig;
38078             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
38079             if (t) {
38080                 return {
38081                     target: t,
38082                     text: t.getAttribute(cfg.namespace + cfg.attribute)
38083                 };
38084             }
38085         }
38086     },
38087
38088     // private
38089     onTargetOver : function(e){
38090         var me = this,
38091             target = e.getTarget(),
38092             elTarget,
38093             cfg,
38094             ns,
38095             tipConfig,
38096             autoHide;
38097
38098         if (me.disabled) {
38099             return;
38100         }
38101
38102         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
38103         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
38104         // that smashed Ext.EventObject.
38105         me.targetXY = e.getXY();
38106
38107         if(!target || target.nodeType !== 1 || target == document || target == document.body){
38108             return;
38109         }
38110
38111         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
38112             me.clearTimer('hide');
38113             me.show();
38114             return;
38115         }
38116
38117         if (target) {
38118             Ext.Object.each(me.targets, function(key, value) {
38119                 var targetEl = Ext.fly(value.target);
38120                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
38121                     elTarget = targetEl.dom;
38122                     return false;
38123                 }
38124             });
38125             if (elTarget) {
38126                 me.activeTarget = me.targets[elTarget.id];
38127                 me.activeTarget.el = target;
38128                 me.anchor = me.activeTarget.anchor;
38129                 if (me.anchor) {
38130                     me.anchorTarget = target;
38131                 }
38132                 me.delayShow();
38133                 return;
38134             }
38135         }
38136
38137         elTarget = Ext.get(target);
38138         cfg = me.tagConfig;
38139         ns = cfg.namespace;
38140         tipConfig = me.getTipCfg(e);
38141
38142         if (tipConfig) {
38143
38144             // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
38145             // Change our target element to match that from which the tip text attribute was read.
38146             if (tipConfig.target) {
38147                 target = tipConfig.target;
38148                 elTarget = Ext.get(target);
38149             }
38150             autoHide = elTarget.getAttribute(ns + cfg.hide);
38151
38152             me.activeTarget = {
38153                 el: target,
38154                 text: tipConfig.text,
38155                 width: +elTarget.getAttribute(ns + cfg.width) || null,
38156                 autoHide: autoHide != "user" && autoHide !== 'false',
38157                 title: elTarget.getAttribute(ns + cfg.title),
38158                 cls: elTarget.getAttribute(ns + cfg.cls),
38159                 align: elTarget.getAttribute(ns + cfg.align)
38160
38161             };
38162             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
38163             if (me.anchor) {
38164                 me.anchorTarget = target;
38165             }
38166             me.delayShow();
38167         }
38168     },
38169
38170     // private
38171     onTargetOut : function(e){
38172         var me = this;
38173
38174         // If moving within the current target, and it does not have a new tip, ignore the mouseout
38175         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
38176             return;
38177         }
38178
38179         me.clearTimer('show');
38180         if (me.autoHide !== false) {
38181             me.delayHide();
38182         }
38183     },
38184
38185     // inherit docs
38186     showAt : function(xy){
38187         var me = this,
38188             target = me.activeTarget;
38189
38190         if (target) {
38191             if (!me.rendered) {
38192                 me.render(Ext.getBody());
38193                 me.activeTarget = target;
38194             }
38195             if (target.title) {
38196                 me.setTitle(target.title || '');
38197                 me.header.show();
38198             } else {
38199                 me.header.hide();
38200             }
38201             me.body.update(target.text);
38202             me.autoHide = target.autoHide;
38203             me.dismissDelay = target.dismissDelay || me.dismissDelay;
38204             if (me.lastCls) {
38205                 me.el.removeCls(me.lastCls);
38206                 delete me.lastCls;
38207             }
38208             if (target.cls) {
38209                 me.el.addCls(target.cls);
38210                 me.lastCls = target.cls;
38211             }
38212
38213             me.setWidth(target.width);
38214
38215             if (me.anchor) {
38216                 me.constrainPosition = false;
38217             } else if (target.align) { // TODO: this doesn't seem to work consistently
38218                 xy = me.el.getAlignToXY(target.el, target.align);
38219                 me.constrainPosition = false;
38220             }else{
38221                 me.constrainPosition = true;
38222             }
38223         }
38224         me.callParent([xy]);
38225     },
38226
38227     // inherit docs
38228     hide: function(){
38229         delete this.activeTarget;
38230         this.callParent();
38231     }
38232 });
38233
38234 /**
38235  * @class Ext.tip.QuickTipManager
38236  *
38237  * Provides attractive and customizable tooltips for any element. The QuickTips
38238  * singleton is used to configure and manage tooltips globally for multiple elements
38239  * in a generic manner.  To create individual tooltips with maximum customizability,
38240  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
38241  *
38242  * Quicktips can be configured via tag attributes directly in markup, or by
38243  * registering quick tips programmatically via the {@link #register} method.
38244  *
38245  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
38246  * {@link #getQuickTip}, and supports all the methods, and all the all the
38247  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
38248  * tooltips shown by the singleton.
38249  *
38250  * Below is the summary of the configuration properties which can be used.
38251  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
38252  *
38253  * ## QuickTips singleton configs (all are optional)
38254  *
38255  *  - `dismissDelay`
38256  *  - `hideDelay`
38257  *  - `maxWidth`
38258  *  - `minWidth`
38259  *  - `showDelay`
38260  *  - `trackMouse`
38261  *
38262  * ## Target element configs (optional unless otherwise noted)
38263  *
38264  *  - `autoHide`
38265  *  - `cls`
38266  *  - `dismissDelay` (overrides singleton value)
38267  *  - `target` (required)
38268  *  - `text` (required)
38269  *  - `title`
38270  *  - `width`
38271  *
38272  * Here is an example showing how some of these config options could be used:
38273  *
38274  *     @example
38275  *     // Init the singleton.  Any tag-based quick tips will start working.
38276  *     Ext.tip.QuickTipManager.init();
38277  *
38278  *     // Apply a set of config properties to the singleton
38279  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
38280  *         maxWidth: 200,
38281  *         minWidth: 100,
38282  *         showDelay: 50      // Show 50ms after entering target
38283  *     });
38284  *
38285  *     // Create a small panel to add a quick tip to
38286  *     Ext.create('Ext.container.Container', {
38287  *         id: 'quickTipContainer',
38288  *         width: 200,
38289  *         height: 150,
38290  *         style: {
38291  *             backgroundColor:'#000000'
38292  *         },
38293  *         renderTo: Ext.getBody()
38294  *     });
38295  *
38296  *
38297  *     // Manually register a quick tip for a specific element
38298  *     Ext.tip.QuickTipManager.register({
38299  *         target: 'quickTipContainer',
38300  *         title: 'My Tooltip',
38301  *         text: 'This tooltip was added in code',
38302  *         width: 100,
38303  *         dismissDelay: 10000 // Hide after 10 seconds hover
38304  *     });
38305  *
38306  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
38307  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
38308  * of supported attributes (optional unless otherwise noted):
38309  *
38310  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
38311  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
38312  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
38313  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
38314  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
38315  *
38316  * Here is an example of configuring an HTML element to display a tooltip from markup:
38317  *
38318  *     // Add a quick tip to an HTML button
38319  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
38320  *          data-qtip="This is a quick tip from markup!"></input>
38321  *
38322  * @singleton
38323  */
38324 Ext.define('Ext.tip.QuickTipManager', function() {
38325     var tip,
38326         disabled = false;
38327
38328     return {
38329         requires: ['Ext.tip.QuickTip'],
38330         singleton: true,
38331         alternateClassName: 'Ext.QuickTips',
38332
38333         /**
38334          * Initialize the global QuickTips instance and prepare any quick tips.
38335          * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
38336          * preload images. (Defaults to true)
38337          * @param {Object} config (optional) config object for the created QuickTip. By
38338          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
38339          * be changed by supplying an xtype property or a className property in this object.
38340          * All other properties on this object are configuration for the created component.
38341          */
38342         init : function (autoRender, config) {
38343             if (!tip) {
38344                 if (!Ext.isReady) {
38345                     Ext.onReady(function(){
38346                         Ext.tip.QuickTipManager.init(autoRender);
38347                     });
38348                     return;
38349                 }
38350
38351                 var tipConfig = Ext.apply({ disabled: disabled }, config),
38352                     className = tipConfig.className,
38353                     xtype = tipConfig.xtype;
38354
38355                 if (className) {
38356                     delete tipConfig.className;
38357                 } else if (xtype) {
38358                     className = 'widget.' + xtype;
38359                     delete tipConfig.xtype;
38360                 }
38361
38362                 if (autoRender !== false) {
38363                     tipConfig.renderTo = document.body;
38364
38365                     //<debug>
38366                     if (tipConfig.renderTo.tagName != 'BODY') { // e.g., == 'FRAMESET'
38367                         Ext.Error.raise({
38368                             sourceClass: 'Ext.tip.QuickTipManager',
38369                             sourceMethod: 'init',
38370                             msg: 'Cannot init QuickTipManager: no document body'
38371                         });
38372                     }
38373                     //</debug>
38374                 }
38375
38376                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
38377             }
38378         },
38379
38380         /**
38381          * Destroy the QuickTips instance.
38382          */
38383         destroy: function() {
38384             if (tip) {
38385                 var undef;
38386                 tip.destroy();
38387                 tip = undef;
38388             }
38389         },
38390
38391         // Protected method called by the dd classes
38392         ddDisable : function(){
38393             // don't disable it if we don't need to
38394             if(tip && !disabled){
38395                 tip.disable();
38396             }
38397         },
38398
38399         // Protected method called by the dd classes
38400         ddEnable : function(){
38401             // only enable it if it hasn't been disabled
38402             if(tip && !disabled){
38403                 tip.enable();
38404             }
38405         },
38406
38407         /**
38408          * Enable quick tips globally.
38409          */
38410         enable : function(){
38411             if(tip){
38412                 tip.enable();
38413             }
38414             disabled = false;
38415         },
38416
38417         /**
38418          * Disable quick tips globally.
38419          */
38420         disable : function(){
38421             if(tip){
38422                 tip.disable();
38423             }
38424             disabled = true;
38425         },
38426
38427         /**
38428          * Returns true if quick tips are enabled, else false.
38429          * @return {Boolean}
38430          */
38431         isEnabled : function(){
38432             return tip !== undefined && !tip.disabled;
38433         },
38434
38435         /**
38436          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
38437          * @return {Ext.tip.QuickTip}
38438          */
38439         getQuickTip : function(){
38440             return tip;
38441         },
38442
38443         /**
38444          * Configures a new quick tip instance and assigns it to a target element.  See
38445          * {@link Ext.tip.QuickTip#register} for details.
38446          * @param {Object} config The config object
38447          */
38448         register : function(){
38449             tip.register.apply(tip, arguments);
38450         },
38451
38452         /**
38453          * Removes any registered quick tip from the target element and destroys it.
38454          * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
38455          */
38456         unregister : function(){
38457             tip.unregister.apply(tip, arguments);
38458         },
38459
38460         /**
38461          * Alias of {@link #register}.
38462          * @param {Object} config The config object
38463          */
38464         tips : function(){
38465             tip.register.apply(tip, arguments);
38466         }
38467     };
38468 }());
38469 /**
38470  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
38471  * A typical Ext.app.Application might look like this:
38472  *
38473  *     Ext.application({
38474  *         name: 'MyApp',
38475  *         launch: function() {
38476  *             Ext.create('Ext.container.Viewport', {
38477  *                 items: {
38478  *                     html: 'My App'
38479  *                 }
38480  *             });
38481  *         }
38482  *     });
38483  *
38484  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
38485  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
38486  * of colliding global variables.
38487  *
38488  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
38489  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
38490  * the example above.
38491  *
38492  * # Telling Application about the rest of the app
38493  *
38494  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
38495  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
38496  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
38497  * Here's how we'd tell our Application about all these things:
38498  *
38499  *     Ext.application({
38500  *         name: 'Blog',
38501  *         models: ['Post', 'Comment'],
38502  *         controllers: ['Posts', 'Comments'],
38503  *
38504  *         launch: function() {
38505  *             ...
38506  *         }
38507  *     });
38508  *
38509  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
38510  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
38511  * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
38512  * in this case expecting the controllers to reside in `app/controller/Posts.js` and
38513  * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
38514  * automatically loaded. Here's how our Posts controller like be defined:
38515  *
38516  *     Ext.define('MyApp.controller.Posts', {
38517  *         extend: 'Ext.app.Controller',
38518  *         views: ['posts.List', 'posts.Edit'],
38519  *
38520  *         //the rest of the Controller here
38521  *     });
38522  *
38523  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
38524  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
38525  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
38526  * application using the Ext JS 4 SDK Tools.
38527  *
38528  * For more information about writing Ext JS 4 applications, please see the
38529  * [application architecture guide][mvc].
38530  *
38531  * [mvc]: #!/guide/application_architecture
38532  *
38533  * @docauthor Ed Spencer
38534  */
38535 Ext.define('Ext.app.Application', {
38536     extend: 'Ext.app.Controller',
38537
38538     requires: [
38539         'Ext.ModelManager',
38540         'Ext.data.Model',
38541         'Ext.data.StoreManager',
38542         'Ext.tip.QuickTipManager',
38543         'Ext.ComponentManager',
38544         'Ext.app.EventBus'
38545     ],
38546
38547     /**
38548      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
38549      * models and stores. Don't use spaces or special characters in the name.
38550      */
38551
38552     /**
38553      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
38554      * instance.
38555      */
38556     scope: undefined,
38557
38558     /**
38559      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
38560      */
38561     enableQuickTips: true,
38562
38563     /**
38564      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
38565      */
38566
38567     /**
38568      * @cfg {String} appFolder The path to the directory which contains all application's classes.
38569      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
38570      */
38571     appFolder: 'app',
38572
38573     /**
38574      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
38575      * before firing the launch function.
38576      */
38577     autoCreateViewport: false,
38578
38579     /**
38580      * Creates new Application.
38581      * @param {Object} [config] Config object.
38582      */
38583     constructor: function(config) {
38584         config = config || {};
38585         Ext.apply(this, config);
38586
38587         var requires = config.requires || [];
38588
38589         Ext.Loader.setPath(this.name, this.appFolder);
38590
38591         if (this.paths) {
38592             Ext.Object.each(this.paths, function(key, value) {
38593                 Ext.Loader.setPath(key, value);
38594             });
38595         }
38596
38597         this.callParent(arguments);
38598
38599         this.eventbus = Ext.create('Ext.app.EventBus');
38600
38601         var controllers = Ext.Array.from(this.controllers),
38602             ln = controllers && controllers.length,
38603             i, controller;
38604
38605         this.controllers = Ext.create('Ext.util.MixedCollection');
38606
38607         if (this.autoCreateViewport) {
38608             requires.push(this.getModuleClassName('Viewport', 'view'));
38609         }
38610
38611         for (i = 0; i < ln; i++) {
38612             requires.push(this.getModuleClassName(controllers[i], 'controller'));
38613         }
38614
38615         Ext.require(requires);
38616
38617         Ext.onReady(function() {
38618             for (i = 0; i < ln; i++) {
38619                 controller = this.getController(controllers[i]);
38620                 controller.init(this);
38621             }
38622
38623             this.onBeforeLaunch.call(this);
38624         }, this);
38625     },
38626
38627     control: function(selectors, listeners, controller) {
38628         this.eventbus.control(selectors, listeners, controller);
38629     },
38630
38631     /**
38632      * Called automatically when the page has completely loaded. This is an empty function that should be
38633      * overridden by each application that needs to take action on page load
38634      * @property launch
38635      * @type Function
38636      * @param {String} profile The detected {@link #profiles application profile}
38637      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
38638      * action immediately after running the launch function. Return false to prevent this behavior.
38639      */
38640     launch: Ext.emptyFn,
38641
38642     /**
38643      * @private
38644      */
38645     onBeforeLaunch: function() {
38646         if (this.enableQuickTips) {
38647             Ext.tip.QuickTipManager.init();
38648         }
38649
38650         if (this.autoCreateViewport) {
38651             this.getView('Viewport').create();
38652         }
38653
38654         this.launch.call(this.scope || this);
38655         this.launched = true;
38656         this.fireEvent('launch', this);
38657
38658         this.controllers.each(function(controller) {
38659             controller.onLaunch(this);
38660         }, this);
38661     },
38662
38663     getModuleClassName: function(name, type) {
38664         var namespace = Ext.Loader.getPrefix(name);
38665
38666         if (namespace.length > 0 && namespace !== name) {
38667             return name;
38668         }
38669
38670         return this.name + '.' + type + '.' + name;
38671     },
38672
38673     getController: function(name) {
38674         var controller = this.controllers.get(name);
38675
38676         if (!controller) {
38677             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
38678                 application: this,
38679                 id: name
38680             });
38681
38682             this.controllers.add(controller);
38683         }
38684
38685         return controller;
38686     },
38687
38688     getStore: function(name) {
38689         var store = Ext.StoreManager.get(name);
38690
38691         if (!store) {
38692             store = Ext.create(this.getModuleClassName(name, 'store'), {
38693                 storeId: name
38694             });
38695         }
38696
38697         return store;
38698     },
38699
38700     getModel: function(model) {
38701         model = this.getModuleClassName(model, 'model');
38702
38703         return Ext.ModelManager.getModel(model);
38704     },
38705
38706     getView: function(view) {
38707         view = this.getModuleClassName(view, 'view');
38708
38709         return Ext.ClassManager.get(view);
38710     }
38711 });
38712
38713 /**
38714  * @class Ext.chart.Callout
38715  * A mixin providing callout functionality for Ext.chart.series.Series.
38716  */
38717 Ext.define('Ext.chart.Callout', {
38718
38719     /* Begin Definitions */
38720
38721     /* End Definitions */
38722
38723     constructor: function(config) {
38724         if (config.callouts) {
38725             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
38726                 color: "#000",
38727                 font: "11px Helvetica, sans-serif"
38728             });
38729             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
38730             this.calloutsArray = [];
38731         }
38732     },
38733
38734     renderCallouts: function() {
38735         if (!this.callouts) {
38736             return;
38737         }
38738
38739         var me = this,
38740             items = me.items,
38741             animate = me.chart.animate,
38742             config = me.callouts,
38743             styles = config.styles,
38744             group = me.calloutsArray,
38745             store = me.chart.store,
38746             len = store.getCount(),
38747             ratio = items.length / len,
38748             previouslyPlacedCallouts = [],
38749             i,
38750             count,
38751             j,
38752             p;
38753             
38754         for (i = 0, count = 0; i < len; i++) {
38755             for (j = 0; j < ratio; j++) {
38756                 var item = items[count],
38757                     label = group[count],
38758                     storeItem = store.getAt(i),
38759                     display;
38760                 
38761                 display = config.filter(storeItem);
38762                 
38763                 if (!display && !label) {
38764                     count++;
38765                     continue;               
38766                 }
38767                 
38768                 if (!label) {
38769                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
38770                 }
38771                 for (p in label) {
38772                     if (label[p] && label[p].setAttributes) {
38773                         label[p].setAttributes(styles, true);
38774                     }
38775                 }
38776                 if (!display) {
38777                     for (p in label) {
38778                         if (label[p]) {
38779                             if (label[p].setAttributes) {
38780                                 label[p].setAttributes({
38781                                     hidden: true
38782                                 }, true);
38783                             } else if(label[p].setVisible) {
38784                                 label[p].setVisible(false);
38785                             }
38786                         }
38787                     }
38788                 }
38789                 config.renderer(label, storeItem);
38790                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
38791                                   j, count, previouslyPlacedCallouts);
38792                 previouslyPlacedCallouts.push(label);
38793                 count++;
38794             }
38795         }
38796         this.hideCallouts(count);
38797     },
38798
38799     onCreateCallout: function(storeItem, item, i, display) {
38800         var me = this,
38801             group = me.calloutsGroup,
38802             config = me.callouts,
38803             styles = config.styles,
38804             width = styles.width,
38805             height = styles.height,
38806             chart = me.chart,
38807             surface = chart.surface,
38808             calloutObj = {
38809                 //label: false,
38810                 //box: false,
38811                 lines: false
38812             };
38813
38814         calloutObj.lines = surface.add(Ext.apply({},
38815         {
38816             type: 'path',
38817             path: 'M0,0',
38818             stroke: me.getLegendColor() || '#555'
38819         },
38820         styles));
38821
38822         if (config.items) {
38823             calloutObj.panel = Ext.create('widget.panel', {
38824                 style: "position: absolute;",    
38825                 width: width,
38826                 height: height,
38827                 items: config.items,
38828                 renderTo: chart.el
38829             });
38830         }
38831
38832         return calloutObj;
38833     },
38834
38835     hideCallouts: function(index) {
38836         var calloutsArray = this.calloutsArray,
38837             len = calloutsArray.length,
38838             co,
38839             p;
38840         while (len-->index) {
38841             co = calloutsArray[len];
38842             for (p in co) {
38843                 if (co[p]) {
38844                     co[p].hide(true);
38845                 }
38846             }
38847         }
38848     }
38849 });
38850
38851 /**
38852  * @class Ext.draw.CompositeSprite
38853  * @extends Ext.util.MixedCollection
38854  *
38855  * A composite Sprite handles a group of sprites with common methods to a sprite
38856  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
38857  * added to the group.
38858  *
38859  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
38860  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
38861  *
38862  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
38863  * rendered:
38864  *
38865  *     var group = Ext.create('Ext.draw.CompositeSprite', {
38866  *         surface: drawComponent.surface
38867  *     });
38868  *                  
38869  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
38870  *  
38871  *     group.add(sprite1);
38872  *     group.add(sprite2);
38873  *     group.add(sprite3);
38874  *                  
38875  * And then apply common Sprite methods to them:
38876  *  
38877  *     group.setAttributes({
38878  *         fill: '#f00'
38879  *     }, true);
38880  */
38881 Ext.define('Ext.draw.CompositeSprite', {
38882
38883     /* Begin Definitions */
38884
38885     extend: 'Ext.util.MixedCollection',
38886     mixins: {
38887         animate: 'Ext.util.Animate'
38888     },
38889
38890     /* End Definitions */
38891     isCompositeSprite: true,
38892     constructor: function(config) {
38893         var me = this;
38894         
38895         config = config || {};
38896         Ext.apply(me, config);
38897
38898         me.addEvents(
38899             'mousedown',
38900             'mouseup',
38901             'mouseover',
38902             'mouseout',
38903             'click'
38904         );
38905         me.id = Ext.id(null, 'ext-sprite-group-');
38906         me.callParent();
38907     },
38908
38909     // @private
38910     onClick: function(e) {
38911         this.fireEvent('click', e);
38912     },
38913
38914     // @private
38915     onMouseUp: function(e) {
38916         this.fireEvent('mouseup', e);
38917     },
38918
38919     // @private
38920     onMouseDown: function(e) {
38921         this.fireEvent('mousedown', e);
38922     },
38923
38924     // @private
38925     onMouseOver: function(e) {
38926         this.fireEvent('mouseover', e);
38927     },
38928
38929     // @private
38930     onMouseOut: function(e) {
38931         this.fireEvent('mouseout', e);
38932     },
38933
38934     attachEvents: function(o) {
38935         var me = this;
38936         
38937         o.on({
38938             scope: me,
38939             mousedown: me.onMouseDown,
38940             mouseup: me.onMouseUp,
38941             mouseover: me.onMouseOver,
38942             mouseout: me.onMouseOut,
38943             click: me.onClick
38944         });
38945     },
38946
38947     // Inherit docs from MixedCollection
38948     add: function(key, o) {
38949         var result = this.callParent(arguments);
38950         this.attachEvents(result);
38951         return result;
38952     },
38953
38954     insert: function(index, key, o) {
38955         return this.callParent(arguments);
38956     },
38957
38958     // Inherit docs from MixedCollection
38959     remove: function(o) {
38960         var me = this;
38961         
38962         o.un({
38963             scope: me,
38964             mousedown: me.onMouseDown,
38965             mouseup: me.onMouseUp,
38966             mouseover: me.onMouseOver,
38967             mouseout: me.onMouseOut,
38968             click: me.onClick
38969         });
38970         return me.callParent(arguments);
38971     },
38972     
38973     /**
38974      * Returns the group bounding box.
38975      * Behaves like {@link Ext.draw.Sprite#getBBox} method.
38976      * @return {Object} an object with x, y, width, and height properties.
38977      */
38978     getBBox: function() {
38979         var i = 0,
38980             sprite,
38981             bb,
38982             items = this.items,
38983             len = this.length,
38984             infinity = Infinity,
38985             minX = infinity,
38986             maxHeight = -infinity,
38987             minY = infinity,
38988             maxWidth = -infinity,
38989             maxWidthBBox, maxHeightBBox;
38990         
38991         for (; i < len; i++) {
38992             sprite = items[i];
38993             if (sprite.el) {
38994                 bb = sprite.getBBox();
38995                 minX = Math.min(minX, bb.x);
38996                 minY = Math.min(minY, bb.y);
38997                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
38998                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
38999             }
39000         }
39001         
39002         return {
39003             x: minX,
39004             y: minY,
39005             height: maxHeight - minY,
39006             width: maxWidth - minX
39007         };
39008     },
39009
39010     /**
39011      * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
39012      * provides a description of the attributes that can be set with this method.
39013      * @param {Object} attrs Attributes to be changed on the sprite.
39014      * @param {Boolean} redraw Flag to immediatly draw the change.
39015      * @return {Ext.draw.CompositeSprite} this
39016      */
39017     setAttributes: function(attrs, redraw) {
39018         var i = 0,
39019             items = this.items,
39020             len = this.length;
39021             
39022         for (; i < len; i++) {
39023             items[i].setAttributes(attrs, redraw);
39024         }
39025         return this;
39026     },
39027
39028     /**
39029      * Hides all sprites. If the first parameter of the method is true
39030      * then a redraw will be forced for each sprite.
39031      * @param {Boolean} redraw Flag to immediatly draw the change.
39032      * @return {Ext.draw.CompositeSprite} this
39033      */
39034     hide: function(redraw) {
39035         var i = 0,
39036             items = this.items,
39037             len = this.length;
39038             
39039         for (; i < len; i++) {
39040             items[i].hide(redraw);
39041         }
39042         return this;
39043     },
39044
39045     /**
39046      * Shows all sprites. If the first parameter of the method is true
39047      * then a redraw will be forced for each sprite.
39048      * @param {Boolean} redraw Flag to immediatly draw the change.
39049      * @return {Ext.draw.CompositeSprite} this
39050      */
39051     show: function(redraw) {
39052         var i = 0,
39053             items = this.items,
39054             len = this.length;
39055             
39056         for (; i < len; i++) {
39057             items[i].show(redraw);
39058         }
39059         return this;
39060     },
39061
39062     redraw: function() {
39063         var me = this,
39064             i = 0,
39065             items = me.items,
39066             surface = me.getSurface(),
39067             len = me.length;
39068         
39069         if (surface) {
39070             for (; i < len; i++) {
39071                 surface.renderItem(items[i]);
39072             }
39073         }
39074         return me;
39075     },
39076
39077     setStyle: function(obj) {
39078         var i = 0,
39079             items = this.items,
39080             len = this.length,
39081             item, el;
39082             
39083         for (; i < len; i++) {
39084             item = items[i];
39085             el = item.el;
39086             if (el) {
39087                 el.setStyle(obj);
39088             }
39089         }
39090     },
39091
39092     addCls: function(obj) {
39093         var i = 0,
39094             items = this.items,
39095             surface = this.getSurface(),
39096             len = this.length;
39097         
39098         if (surface) {
39099             for (; i < len; i++) {
39100                 surface.addCls(items[i], obj);
39101             }
39102         }
39103     },
39104
39105     removeCls: function(obj) {
39106         var i = 0,
39107             items = this.items,
39108             surface = this.getSurface(),
39109             len = this.length;
39110         
39111         if (surface) {
39112             for (; i < len; i++) {
39113                 surface.removeCls(items[i], obj);
39114             }
39115         }
39116     },
39117     
39118     /**
39119      * Grab the surface from the items
39120      * @private
39121      * @return {Ext.draw.Surface} The surface, null if not found
39122      */
39123     getSurface: function(){
39124         var first = this.first();
39125         if (first) {
39126             return first.surface;
39127         }
39128         return null;
39129     },
39130     
39131     /**
39132      * Destroys the SpriteGroup
39133      */
39134     destroy: function(){
39135         var me = this,
39136             surface = me.getSurface(),
39137             item;
39138             
39139         if (surface) {
39140             while (me.getCount() > 0) {
39141                 item = me.first();
39142                 me.remove(item);
39143                 surface.remove(item);
39144             }
39145         }
39146         me.clearListeners();
39147     }
39148 });
39149
39150 /**
39151  * @class Ext.layout.component.Auto
39152  * @extends Ext.layout.component.Component
39153  * @private
39154  *
39155  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
39156  * render any child Elements when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured.</p>
39157  */
39158
39159 Ext.define('Ext.layout.component.Auto', {
39160
39161     /* Begin Definitions */
39162
39163     alias: 'layout.autocomponent',
39164
39165     extend: 'Ext.layout.component.Component',
39166
39167     /* End Definitions */
39168
39169     type: 'autocomponent',
39170
39171     onLayout : function(width, height) {
39172         this.setTargetSize(width, height);
39173     }
39174 });
39175 /**
39176  * @class Ext.chart.theme.Theme
39177  * 
39178  * Provides chart theming.
39179  * 
39180  * Used as mixins by Ext.chart.Chart.
39181  */
39182 Ext.define('Ext.chart.theme.Theme', {
39183
39184     /* Begin Definitions */
39185
39186     requires: ['Ext.draw.Color'],
39187
39188     /* End Definitions */
39189
39190     theme: 'Base',
39191     themeAttrs: false,
39192     
39193     initTheme: function(theme) {
39194         var me = this,
39195             themes = Ext.chart.theme,
39196             key, gradients;
39197         if (theme) {
39198             theme = theme.split(':');
39199             for (key in themes) {
39200                 if (key == theme[0]) {
39201                     gradients = theme[1] == 'gradients';
39202                     me.themeAttrs = new themes[key]({
39203                         useGradients: gradients
39204                     });
39205                     if (gradients) {
39206                         me.gradients = me.themeAttrs.gradients;
39207                     }
39208                     if (me.themeAttrs.background) {
39209                         me.background = me.themeAttrs.background;
39210                     }
39211                     return;
39212                 }
39213             }
39214             //<debug>
39215             Ext.Error.raise('No theme found named "' + theme + '"');
39216             //</debug>
39217         }
39218     }
39219 }, 
39220 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
39221 function() {
39222    /* Theme constructor: takes either a complex object with styles like:
39223   
39224    {
39225         axis: {
39226             fill: '#000',
39227             'stroke-width': 1
39228         },
39229         axisLabelTop: {
39230             fill: '#000',
39231             font: '11px Arial'
39232         },
39233         axisLabelLeft: {
39234             fill: '#000',
39235             font: '11px Arial'
39236         },
39237         axisLabelRight: {
39238             fill: '#000',
39239             font: '11px Arial'
39240         },
39241         axisLabelBottom: {
39242             fill: '#000',
39243             font: '11px Arial'
39244         },
39245         axisTitleTop: {
39246             fill: '#000',
39247             font: '11px Arial'
39248         },
39249         axisTitleLeft: {
39250             fill: '#000',
39251             font: '11px Arial'
39252         },
39253         axisTitleRight: {
39254             fill: '#000',
39255             font: '11px Arial'
39256         },
39257         axisTitleBottom: {
39258             fill: '#000',
39259             font: '11px Arial'
39260         },
39261         series: {
39262             'stroke-width': 1
39263         },
39264         seriesLabel: {
39265             font: '12px Arial',
39266             fill: '#333'
39267         },
39268         marker: {
39269             stroke: '#555',
39270             fill: '#000',
39271             radius: 3,
39272             size: 3
39273         },
39274         seriesThemes: [{
39275             fill: '#C6DBEF'
39276         }, {
39277             fill: '#9ECAE1'
39278         }, {
39279             fill: '#6BAED6'
39280         }, {
39281             fill: '#4292C6'
39282         }, {
39283             fill: '#2171B5'
39284         }, {
39285             fill: '#084594'
39286         }],
39287         markerThemes: [{
39288             fill: '#084594',
39289             type: 'circle' 
39290         }, {
39291             fill: '#2171B5',
39292             type: 'cross'
39293         }, {
39294             fill: '#4292C6',
39295             type: 'plus'
39296         }]
39297     }
39298   
39299   ...or also takes just an array of colors and creates the complex object:
39300   
39301   {
39302       colors: ['#aaa', '#bcd', '#eee']
39303   }
39304   
39305   ...or takes just a base color and makes a theme from it
39306   
39307   {
39308       baseColor: '#bce'
39309   }
39310   
39311   To create a new theme you may add it to the Themes object:
39312   
39313   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
39314       constructor: function(config) {
39315           Ext.chart.theme.call(this, config, {
39316               baseColor: '#mybasecolor'
39317           });
39318       }
39319   });
39320   
39321   //Proposal:
39322   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
39323   
39324   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
39325   
39326   {
39327       theme: 'mynewtheme'
39328   }
39329  */
39330
39331 (function() {
39332     Ext.chart.theme = function(config, base) {
39333         config = config || {};
39334         var i = 0, l, colors, color,
39335             seriesThemes, markerThemes,
39336             seriesTheme, markerTheme, 
39337             key, gradients = [],
39338             midColor, midL;
39339         
39340         if (config.baseColor) {
39341             midColor = Ext.draw.Color.fromString(config.baseColor);
39342             midL = midColor.getHSL()[2];
39343             if (midL < 0.15) {
39344                 midColor = midColor.getLighter(0.3);
39345             } else if (midL < 0.3) {
39346                 midColor = midColor.getLighter(0.15);
39347             } else if (midL > 0.85) {
39348                 midColor = midColor.getDarker(0.3);
39349             } else if (midL > 0.7) {
39350                 midColor = midColor.getDarker(0.15);
39351             }
39352             config.colors = [ midColor.getDarker(0.3).toString(),
39353                               midColor.getDarker(0.15).toString(),
39354                               midColor.toString(),
39355                               midColor.getLighter(0.15).toString(),
39356                               midColor.getLighter(0.3).toString()];
39357
39358             delete config.baseColor;
39359         }
39360         if (config.colors) {
39361             colors = config.colors.slice();
39362             markerThemes = base.markerThemes;
39363             seriesThemes = base.seriesThemes;
39364             l = colors.length;
39365             base.colors = colors;
39366             for (; i < l; i++) {
39367                 color = colors[i];
39368                 markerTheme = markerThemes[i] || {};
39369                 seriesTheme = seriesThemes[i] || {};
39370                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
39371                 markerThemes[i] = markerTheme;
39372                 seriesThemes[i] = seriesTheme;
39373             }
39374             base.markerThemes = markerThemes.slice(0, l);
39375             base.seriesThemes = seriesThemes.slice(0, l);
39376         //the user is configuring something in particular (either markers, series or pie slices)
39377         }
39378         for (key in base) {
39379             if (key in config) {
39380                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
39381                     Ext.apply(base[key], config[key]);
39382                 } else {
39383                     base[key] = config[key];
39384                 }
39385             }
39386         }
39387         if (config.useGradients) {
39388             colors = base.colors || (function () {
39389                 var ans = [];
39390                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
39391                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
39392                 }
39393                 return ans;
39394             })();
39395             for (i = 0, l = colors.length; i < l; i++) {
39396                 midColor = Ext.draw.Color.fromString(colors[i]);
39397                 if (midColor) {
39398                     color = midColor.getDarker(0.1).toString();
39399                     midColor = midColor.toString();
39400                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
39401                     gradients.push({
39402                         id: key,
39403                         angle: 45,
39404                         stops: {
39405                             0: {
39406                                 color: midColor.toString()
39407                             },
39408                             100: {
39409                                 color: color.toString()
39410                             }
39411                         }
39412                     });
39413                     colors[i] = 'url(#' + key + ')'; 
39414                 }
39415             }
39416             base.gradients = gradients;
39417             base.colors = colors;
39418         }
39419         /*
39420         base.axis = Ext.apply(base.axis || {}, config.axis || {});
39421         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
39422         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
39423         */
39424         Ext.apply(this, base);
39425     };
39426 })();
39427 });
39428
39429 /**
39430  * @class Ext.chart.Mask
39431  *
39432  * Defines a mask for a chart's series.
39433  * The 'chart' member must be set prior to rendering.
39434  *
39435  * A Mask can be used to select a certain region in a chart.
39436  * When enabled, the `select` event will be triggered when a
39437  * region is selected by the mask, allowing the user to perform
39438  * other tasks like zooming on that region, etc.
39439  *
39440  * In order to use the mask one has to set the Chart `mask` option to
39441  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
39442  * listener could be:
39443  *
39444         items: {
39445             xtype: 'chart',
39446             animate: true,
39447             store: store1,
39448             mask: 'horizontal',
39449             listeners: {
39450                 select: {
39451                     fn: function(me, selection) {
39452                         me.setZoom(selection);
39453                         me.mask.hide();
39454                     }
39455                 }
39456             },
39457
39458  * In this example we zoom the chart to that particular region. You can also get
39459  * a handle to a mask instance from the chart object. The `chart.mask` element is a
39460  * `Ext.Panel`.
39461  * 
39462  */
39463 Ext.define('Ext.chart.Mask', {
39464     require: ['Ext.chart.MaskLayer'],
39465     /**
39466      * Creates new Mask.
39467      * @param {Object} config (optional) Config object.
39468      */
39469     constructor: function(config) {
39470         var me = this;
39471
39472         me.addEvents('select');
39473
39474         if (config) {
39475             Ext.apply(me, config);
39476         }
39477         if (me.mask) {
39478             me.on('afterrender', function() {
39479                 //create a mask layer component
39480                 var comp = Ext.create('Ext.chart.MaskLayer', {
39481                     renderTo: me.el
39482                 });
39483                 comp.el.on({
39484                     'mousemove': function(e) {
39485                         me.onMouseMove(e);
39486                     },
39487                     'mouseup': function(e) {
39488                         me.resized(e);
39489                     }
39490                 });
39491                 //create a resize handler for the component
39492                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
39493                     el: comp.el,
39494                     handles: 'all',
39495                     pinned: true
39496                 });
39497                 resizeHandler.on({
39498                     'resize': function(e) {
39499                         me.resized(e);    
39500                     }    
39501                 });
39502                 comp.initDraggable();
39503                 me.maskType = me.mask;
39504                 me.mask = comp;
39505                 me.maskSprite = me.surface.add({
39506                     type: 'path',
39507                     path: ['M', 0, 0],
39508                     zIndex: 1001,
39509                     opacity: 0.7,
39510                     hidden: true,
39511                     stroke: '#444'
39512                 });
39513             }, me, { single: true });
39514         }
39515     },
39516     
39517     resized: function(e) {
39518         var me = this,
39519             bbox = me.bbox || me.chartBBox,
39520             x = bbox.x,
39521             y = bbox.y,
39522             width = bbox.width,
39523             height = bbox.height,
39524             box = me.mask.getBox(true),
39525             max = Math.max,
39526             min = Math.min,
39527             staticX = box.x - x,
39528             staticY = box.y - y;
39529         
39530         staticX = max(staticX, x);
39531         staticY = max(staticY, y);
39532         staticX = min(staticX, width);
39533         staticY = min(staticY, height);
39534         box.x = staticX;
39535         box.y = staticY;
39536         me.fireEvent('select', me, box);
39537     },
39538
39539     onMouseUp: function(e) {
39540         var me = this,
39541             bbox = me.bbox || me.chartBBox,
39542             sel = me.maskSelection;
39543         me.maskMouseDown = false;
39544         me.mouseDown = false;
39545         if (me.mouseMoved) {
39546             me.onMouseMove(e);
39547             me.mouseMoved = false;
39548             me.fireEvent('select', me, {
39549                 x: sel.x - bbox.x,
39550                 y: sel.y - bbox.y,
39551                 width: sel.width,
39552                 height: sel.height
39553             });
39554         }
39555     },
39556
39557     onMouseDown: function(e) {
39558         var me = this;
39559         me.mouseDown = true;
39560         me.mouseMoved = false;
39561         me.maskMouseDown = {
39562             x: e.getPageX() - me.el.getX(),
39563             y: e.getPageY() - me.el.getY()
39564         };
39565     },
39566
39567     onMouseMove: function(e) {
39568         var me = this,
39569             mask = me.maskType,
39570             bbox = me.bbox || me.chartBBox,
39571             x = bbox.x,
39572             y = bbox.y,
39573             math = Math,
39574             floor = math.floor,
39575             abs = math.abs,
39576             min = math.min,
39577             max = math.max,
39578             height = floor(y + bbox.height),
39579             width = floor(x + bbox.width),
39580             posX = e.getPageX(),
39581             posY = e.getPageY(),
39582             staticX = posX - me.el.getX(),
39583             staticY = posY - me.el.getY(),
39584             maskMouseDown = me.maskMouseDown,
39585             path;
39586         
39587         me.mouseMoved = me.mouseDown;
39588         staticX = max(staticX, x);
39589         staticY = max(staticY, y);
39590         staticX = min(staticX, width);
39591         staticY = min(staticY, height);
39592         if (maskMouseDown && me.mouseDown) {
39593             if (mask == 'horizontal') {
39594                 staticY = y;
39595                 maskMouseDown.y = height;
39596                 posY = me.el.getY() + bbox.height + me.insetPadding;
39597             }
39598             else if (mask == 'vertical') {
39599                 staticX = x;
39600                 maskMouseDown.x = width;
39601             }
39602             width = maskMouseDown.x - staticX;
39603             height = maskMouseDown.y - staticY;
39604             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
39605             me.maskSelection = {
39606                 x: width > 0 ? staticX : staticX + width,
39607                 y: height > 0 ? staticY : staticY + height,
39608                 width: abs(width),
39609                 height: abs(height)
39610             };
39611             me.mask.updateBox(me.maskSelection);
39612             me.mask.show();
39613             me.maskSprite.setAttributes({
39614                 hidden: true    
39615             }, true);
39616         }
39617         else {
39618             if (mask == 'horizontal') {
39619                 path = ['M', staticX, y, 'L', staticX, height];
39620             }
39621             else if (mask == 'vertical') {
39622                 path = ['M', x, staticY, 'L', width, staticY];
39623             }
39624             else {
39625                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
39626             }
39627             me.maskSprite.setAttributes({
39628                 path: path,
39629                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
39630                 'stroke-width': mask === true ? 1 : 3,
39631                 hidden: false
39632             }, true);
39633         }
39634     },
39635
39636     onMouseLeave: function(e) {
39637         var me = this;
39638         me.mouseMoved = false;
39639         me.mouseDown = false;
39640         me.maskMouseDown = false;
39641         me.mask.hide();
39642         me.maskSprite.hide(true);
39643     }
39644 });
39645     
39646 /**
39647  * @class Ext.chart.Navigation
39648  *
39649  * Handles panning and zooming capabilities.
39650  *
39651  * Used as mixin by Ext.chart.Chart.
39652  */
39653 Ext.define('Ext.chart.Navigation', {
39654
39655     constructor: function() {
39656         this.originalStore = this.store;
39657     },
39658
39659     /**
39660      * Zooms the chart to the specified selection range.
39661      * Can be used with a selection mask. For example:
39662      *
39663      *     items: {
39664      *         xtype: 'chart',
39665      *         animate: true,
39666      *         store: store1,
39667      *         mask: 'horizontal',
39668      *         listeners: {
39669      *             select: {
39670      *                 fn: function(me, selection) {
39671      *                     me.setZoom(selection);
39672      *                     me.mask.hide();
39673      *                 }
39674      *             }
39675      *         }
39676      *     }
39677      */
39678     setZoom: function(zoomConfig) {
39679         var me = this,
39680             axes = me.axes,
39681             bbox = me.chartBBox,
39682             xScale = 1 / bbox.width,
39683             yScale = 1 / bbox.height,
39684             zoomer = {
39685                 x : zoomConfig.x * xScale,
39686                 y : zoomConfig.y * yScale,
39687                 width : zoomConfig.width * xScale,
39688                 height : zoomConfig.height * yScale
39689             };
39690         axes.each(function(axis) {
39691             var ends = axis.calcEnds();
39692             if (axis.position == 'bottom' || axis.position == 'top') {
39693                 var from = (ends.to - ends.from) * zoomer.x + ends.from,
39694                     to = (ends.to - ends.from) * zoomer.width + from;
39695                 axis.minimum = from;
39696                 axis.maximum = to;
39697             } else {
39698                 var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
39699                     from = to - (ends.to - ends.from) * zoomer.height;
39700                 axis.minimum = from;
39701                 axis.maximum = to;
39702             }
39703         });
39704         me.redraw(false);
39705     },
39706
39707     /**
39708      * Restores the zoom to the original value. This can be used to reset
39709      * the previous zoom state set by `setZoom`. For example:
39710      *
39711      *     myChart.restoreZoom();
39712      */
39713     restoreZoom: function() {
39714         this.store = this.substore = this.originalStore;
39715         this.redraw(true);
39716     }
39717
39718 });
39719
39720 /**
39721  * @class Ext.chart.Shape
39722  * @ignore
39723  */
39724 Ext.define('Ext.chart.Shape', {
39725
39726     /* Begin Definitions */
39727
39728     singleton: true,
39729
39730     /* End Definitions */
39731
39732     circle: function (surface, opts) {
39733         return surface.add(Ext.apply({
39734             type: 'circle',
39735             x: opts.x,
39736             y: opts.y,
39737             stroke: null,
39738             radius: opts.radius
39739         }, opts));
39740     },
39741     line: function (surface, opts) {
39742         return surface.add(Ext.apply({
39743             type: 'rect',
39744             x: opts.x - opts.radius,
39745             y: opts.y - opts.radius,
39746             height: 2 * opts.radius,
39747             width: 2 * opts.radius / 5
39748         }, opts));
39749     },
39750     square: function (surface, opts) {
39751         return surface.add(Ext.applyIf({
39752             type: 'rect',
39753             x: opts.x - opts.radius,
39754             y: opts.y - opts.radius,
39755             height: 2 * opts.radius,
39756             width: 2 * opts.radius,
39757             radius: null
39758         }, opts));
39759     },
39760     triangle: function (surface, opts) {
39761         opts.radius *= 1.75;
39762         return surface.add(Ext.apply({
39763             type: 'path',
39764             stroke: null,
39765             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
39766         }, opts));
39767     },
39768     diamond: function (surface, opts) {
39769         var r = opts.radius;
39770         r *= 1.5;
39771         return surface.add(Ext.apply({
39772             type: 'path',
39773             stroke: null,
39774             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
39775         }, opts));
39776     },
39777     cross: function (surface, opts) {
39778         var r = opts.radius;
39779         r = r / 1.7;
39780         return surface.add(Ext.apply({
39781             type: 'path',
39782             stroke: null,
39783             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"])
39784         }, opts));
39785     },
39786     plus: function (surface, opts) {
39787         var r = opts.radius / 1.3;
39788         return surface.add(Ext.apply({
39789             type: 'path',
39790             stroke: null,
39791             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"])
39792         }, opts));
39793     },
39794     arrow: function (surface, opts) {
39795         var r = opts.radius;
39796         return surface.add(Ext.apply({
39797             type: 'path',
39798             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")
39799         }, opts));
39800     },
39801     drop: function (surface, x, y, text, size, angle) {
39802         size = size || 30;
39803         angle = angle || 0;
39804         surface.add({
39805             type: 'path',
39806             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'],
39807             fill: '#000',
39808             stroke: 'none',
39809             rotate: {
39810                 degrees: 22.5 - angle,
39811                 x: x,
39812                 y: y
39813             }
39814         });
39815         angle = (angle + 90) * Math.PI / 180;
39816         surface.add({
39817             type: 'text',
39818             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
39819             y: y + size * Math.cos(angle) + 5,
39820             text:  text,
39821             'font-size': size * 12 / 40,
39822             stroke: 'none',
39823             fill: '#fff'
39824         });
39825     }
39826 });
39827 /**
39828  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
39829  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
39830  * sprites to the canvas, initialize other graphic components, etc. One of the most used
39831  * methods for this class is the `add` method, to add Sprites to the surface.
39832  *
39833  * Most of the Surface methods are abstract and they have a concrete implementation
39834  * in VML or SVG engines.
39835  *
39836  * A Surface instance can be accessed as a property of a draw component. For example:
39837  *
39838  *     drawComponent.surface.add({
39839  *         type: 'circle',
39840  *         fill: '#ffc',
39841  *         radius: 100,
39842  *         x: 100,
39843  *         y: 100
39844  *     });
39845  *
39846  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
39847  * class documentation.
39848  *
39849  * # Listeners
39850  *
39851  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
39852  *
39853  * - mousedown
39854  * - mouseup
39855  * - mouseover
39856  * - mouseout
39857  * - mousemove
39858  * - mouseenter
39859  * - mouseleave
39860  * - click
39861  *
39862  * For example:
39863  *
39864  *     drawComponent.surface.on({
39865  *        'mousemove': function() {
39866  *             console.log('moving the mouse over the surface');
39867  *         }
39868  *     });
39869  *
39870  * # Example
39871  *
39872  *     var drawComponent = Ext.create('Ext.draw.Component', {
39873  *         width: 800,
39874  *         height: 600,
39875  *         renderTo: document.body
39876  *     }), surface = drawComponent.surface;
39877  *
39878  *     surface.add([{
39879  *         type: 'circle',
39880  *         radius: 10,
39881  *         fill: '#f00',
39882  *         x: 10,
39883  *         y: 10,
39884  *         group: 'circles'
39885  *     }, {
39886  *         type: 'circle',
39887  *         radius: 10,
39888  *         fill: '#0f0',
39889  *         x: 50,
39890  *         y: 50,
39891  *         group: 'circles'
39892  *     }, {
39893  *         type: 'circle',
39894  *         radius: 10,
39895  *         fill: '#00f',
39896  *         x: 100,
39897  *         y: 100,
39898  *         group: 'circles'
39899  *     }, {
39900  *         type: 'rect',
39901  *         width: 20,
39902  *         height: 20,
39903  *         fill: '#f00',
39904  *         x: 10,
39905  *         y: 10,
39906  *         group: 'rectangles'
39907  *     }, {
39908  *         type: 'rect',
39909  *         width: 20,
39910  *         height: 20,
39911  *         fill: '#0f0',
39912  *         x: 50,
39913  *         y: 50,
39914  *         group: 'rectangles'
39915  *     }, {
39916  *         type: 'rect',
39917  *         width: 20,
39918  *         height: 20,
39919  *         fill: '#00f',
39920  *         x: 100,
39921  *         y: 100,
39922  *         group: 'rectangles'
39923  *     }]);
39924  *
39925  *     // Get references to my groups
39926  *     circles = surface.getGroup('circles');
39927  *     rectangles = surface.getGroup('rectangles');
39928  *
39929  *     // Animate the circles down
39930  *     circles.animate({
39931  *         duration: 1000,
39932  *         to: {
39933  *             translate: {
39934  *                 y: 200
39935  *             }
39936  *         }
39937  *     });
39938  *
39939  *     // Animate the rectangles across
39940  *     rectangles.animate({
39941  *         duration: 1000,
39942  *         to: {
39943  *             translate: {
39944  *                 x: 200
39945  *             }
39946  *         }
39947  *     });
39948  */
39949 Ext.define('Ext.draw.Surface', {
39950
39951     /* Begin Definitions */
39952
39953     mixins: {
39954         observable: 'Ext.util.Observable'
39955     },
39956
39957     requires: ['Ext.draw.CompositeSprite'],
39958     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
39959
39960     separatorRe: /[, ]+/,
39961
39962     statics: {
39963         /**
39964          * Creates and returns a new concrete Surface instance appropriate for the current environment.
39965          * @param {Object} config Initial configuration for the Surface instance
39966          * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
39967          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
39968          * @return {Object} The created Surface or false.
39969          * @static
39970          */
39971         create: function(config, enginePriority) {
39972             enginePriority = enginePriority || ['Svg', 'Vml'];
39973
39974             var i = 0,
39975                 len = enginePriority.length,
39976                 surfaceClass;
39977
39978             for (; i < len; i++) {
39979                 if (Ext.supports[enginePriority[i]]) {
39980                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
39981                 }
39982             }
39983             return false;
39984         }
39985     },
39986
39987     /* End Definitions */
39988
39989     // @private
39990     availableAttrs: {
39991         blur: 0,
39992         "clip-rect": "0 0 1e9 1e9",
39993         cursor: "default",
39994         cx: 0,
39995         cy: 0,
39996         'dominant-baseline': 'auto',
39997         fill: "none",
39998         "fill-opacity": 1,
39999         font: '10px "Arial"',
40000         "font-family": '"Arial"',
40001         "font-size": "10",
40002         "font-style": "normal",
40003         "font-weight": 400,
40004         gradient: "",
40005         height: 0,
40006         hidden: false,
40007         href: "http://sencha.com/",
40008         opacity: 1,
40009         path: "M0,0",
40010         radius: 0,
40011         rx: 0,
40012         ry: 0,
40013         scale: "1 1",
40014         src: "",
40015         stroke: "#000",
40016         "stroke-dasharray": "",
40017         "stroke-linecap": "butt",
40018         "stroke-linejoin": "butt",
40019         "stroke-miterlimit": 0,
40020         "stroke-opacity": 1,
40021         "stroke-width": 1,
40022         target: "_blank",
40023         text: "",
40024         "text-anchor": "middle",
40025         title: "Ext Draw",
40026         width: 0,
40027         x: 0,
40028         y: 0,
40029         zIndex: 0
40030     },
40031
40032     /**
40033      * @cfg {Number} height
40034      * The height of this component in pixels (defaults to auto).
40035      */
40036     /**
40037      * @cfg {Number} width
40038      * The width of this component in pixels (defaults to auto).
40039      */
40040
40041     container: undefined,
40042     height: 352,
40043     width: 512,
40044     x: 0,
40045     y: 0,
40046
40047     /**
40048      * @private Flag indicating that the surface implementation requires sprites to be maintained
40049      * in order of their zIndex. Impls that don't require this can set it to false.
40050      */
40051     orderSpritesByZIndex: true,
40052
40053
40054     /**
40055      * Creates new Surface.
40056      * @param {Object} config (optional) Config object.
40057      */
40058     constructor: function(config) {
40059         var me = this;
40060         config = config || {};
40061         Ext.apply(me, config);
40062
40063         me.domRef = Ext.getDoc().dom;
40064
40065         me.customAttributes = {};
40066
40067         me.addEvents(
40068             'mousedown',
40069             'mouseup',
40070             'mouseover',
40071             'mouseout',
40072             'mousemove',
40073             'mouseenter',
40074             'mouseleave',
40075             'click'
40076         );
40077
40078         me.mixins.observable.constructor.call(me);
40079
40080         me.getId();
40081         me.initGradients();
40082         me.initItems();
40083         if (me.renderTo) {
40084             me.render(me.renderTo);
40085             delete me.renderTo;
40086         }
40087         me.initBackground(config.background);
40088     },
40089
40090     // @private called to initialize components in the surface
40091     // this is dependent on the underlying implementation.
40092     initSurface: Ext.emptyFn,
40093
40094     // @private called to setup the surface to render an item
40095     //this is dependent on the underlying implementation.
40096     renderItem: Ext.emptyFn,
40097
40098     // @private
40099     renderItems: Ext.emptyFn,
40100
40101     // @private
40102     setViewBox: function (x, y, width, height) {
40103         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
40104             this.viewBox = {x: x, y: y, width: width, height: height};
40105             this.applyViewBox();
40106         }
40107     },
40108
40109     /**
40110      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
40111      *
40112      * For example:
40113      *
40114      *     drawComponent.surface.addCls(sprite, 'x-visible');
40115      *
40116      * @param {Object} sprite The sprite to add the class to.
40117      * @param {String/String[]} className The CSS class to add, or an array of classes
40118      * @method
40119      */
40120     addCls: Ext.emptyFn,
40121
40122     /**
40123      * Removes one or more CSS classes from the element.
40124      *
40125      * For example:
40126      *
40127      *     drawComponent.surface.removeCls(sprite, 'x-visible');
40128      *
40129      * @param {Object} sprite The sprite to remove the class from.
40130      * @param {String/String[]} className The CSS class to remove, or an array of classes
40131      * @method
40132      */
40133     removeCls: Ext.emptyFn,
40134
40135     /**
40136      * Sets CSS style attributes to an element.
40137      *
40138      * For example:
40139      *
40140      *     drawComponent.surface.setStyle(sprite, {
40141      *         'cursor': 'pointer'
40142      *     });
40143      *
40144      * @param {Object} sprite The sprite to add, or an array of classes to
40145      * @param {Object} styles An Object with CSS styles.
40146      * @method
40147      */
40148     setStyle: Ext.emptyFn,
40149
40150     // @private
40151     initGradients: function() {
40152         var gradients = this.gradients;
40153         if (gradients) {
40154             Ext.each(gradients, this.addGradient, this);
40155         }
40156     },
40157
40158     // @private
40159     initItems: function() {
40160         var items = this.items;
40161         this.items = Ext.create('Ext.draw.CompositeSprite');
40162         this.groups = Ext.create('Ext.draw.CompositeSprite');
40163         if (items) {
40164             this.add(items);
40165         }
40166     },
40167
40168     // @private
40169     initBackground: function(config) {
40170         var me = this,
40171             width = me.width,
40172             height = me.height,
40173             gradientId, gradient, backgroundSprite;
40174         if (config) {
40175             if (config.gradient) {
40176                 gradient = config.gradient;
40177                 gradientId = gradient.id;
40178                 me.addGradient(gradient);
40179                 me.background = me.add({
40180                     type: 'rect',
40181                     x: 0,
40182                     y: 0,
40183                     width: width,
40184                     height: height,
40185                     fill: 'url(#' + gradientId + ')'
40186                 });
40187             } else if (config.fill) {
40188                 me.background = me.add({
40189                     type: 'rect',
40190                     x: 0,
40191                     y: 0,
40192                     width: width,
40193                     height: height,
40194                     fill: config.fill
40195                 });
40196             } else if (config.image) {
40197                 me.background = me.add({
40198                     type: 'image',
40199                     x: 0,
40200                     y: 0,
40201                     width: width,
40202                     height: height,
40203                     src: config.image
40204                 });
40205             }
40206         }
40207     },
40208
40209     /**
40210      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
40211      *
40212      * For example:
40213      *
40214      *     drawComponent.surface.setSize(500, 500);
40215      *
40216      * This method is generally called when also setting the size of the draw Component.
40217      *
40218      * @param {Number} w The new width of the canvas.
40219      * @param {Number} h The new height of the canvas.
40220      */
40221     setSize: function(w, h) {
40222         if (this.background) {
40223             this.background.setAttributes({
40224                 width: w,
40225                 height: h,
40226                 hidden: false
40227             }, true);
40228         }
40229         this.applyViewBox();
40230     },
40231
40232     // @private
40233     scrubAttrs: function(sprite) {
40234         var i,
40235             attrs = {},
40236             exclude = {},
40237             sattr = sprite.attr;
40238         for (i in sattr) {
40239             // Narrow down attributes to the main set
40240             if (this.translateAttrs.hasOwnProperty(i)) {
40241                 // Translated attr
40242                 attrs[this.translateAttrs[i]] = sattr[i];
40243                 exclude[this.translateAttrs[i]] = true;
40244             }
40245             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
40246                 // Passtrhough attr
40247                 attrs[i] = sattr[i];
40248             }
40249         }
40250         return attrs;
40251     },
40252
40253     // @private
40254     onClick: function(e) {
40255         this.processEvent('click', e);
40256     },
40257
40258     // @private
40259     onMouseUp: function(e) {
40260         this.processEvent('mouseup', e);
40261     },
40262
40263     // @private
40264     onMouseDown: function(e) {
40265         this.processEvent('mousedown', e);
40266     },
40267
40268     // @private
40269     onMouseOver: function(e) {
40270         this.processEvent('mouseover', e);
40271     },
40272
40273     // @private
40274     onMouseOut: function(e) {
40275         this.processEvent('mouseout', e);
40276     },
40277
40278     // @private
40279     onMouseMove: function(e) {
40280         this.fireEvent('mousemove', e);
40281     },
40282
40283     // @private
40284     onMouseEnter: Ext.emptyFn,
40285
40286     // @private
40287     onMouseLeave: Ext.emptyFn,
40288
40289     /**
40290      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
40291      * a gradient via this method will not take effect if the surface has already been rendered.
40292      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
40293      * than calling this method, especially if the surface is rendered immediately (e.g. due to
40294      * 'renderTo' in its config). For more information on how to create gradients in the Chart
40295      * configuration object please refer to {@link Ext.chart.Chart}.
40296      *
40297      * The gradient object to be passed into this method is composed by:
40298      *
40299      * - **id** - string - The unique name of the gradient.
40300      * - **angle** - number, optional - The angle of the gradient in degrees.
40301      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
40302      *
40303      * For example:
40304      *
40305      *    drawComponent.surface.addGradient({
40306      *        id: 'gradientId',
40307      *        angle: 45,
40308      *        stops: {
40309      *            0: {
40310      *                color: '#555'
40311      *            },
40312      *            100: {
40313      *                color: '#ddd'
40314      *            }
40315      *        }
40316      *    });
40317      *
40318      * @method
40319      */
40320     addGradient: Ext.emptyFn,
40321
40322     /**
40323      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
40324      * passed into this method.
40325      *
40326      * For example:
40327      *
40328      *     drawComponent.surface.add({
40329      *         type: 'circle',
40330      *         fill: '#ffc',
40331      *         radius: 100,
40332      *         x: 100,
40333      *         y: 100
40334      *     });
40335      *
40336      */
40337     add: function() {
40338         var args = Array.prototype.slice.call(arguments),
40339             sprite,
40340             index;
40341
40342         var hasMultipleArgs = args.length > 1;
40343         if (hasMultipleArgs || Ext.isArray(args[0])) {
40344             var items = hasMultipleArgs ? args : args[0],
40345                 results = [],
40346                 i, ln, item;
40347
40348             for (i = 0, ln = items.length; i < ln; i++) {
40349                 item = items[i];
40350                 item = this.add(item);
40351                 results.push(item);
40352             }
40353
40354             return results;
40355         }
40356         sprite = this.prepareItems(args[0], true)[0];
40357         this.insertByZIndex(sprite);
40358         this.onAdd(sprite);
40359         return sprite;
40360     },
40361
40362     /**
40363      * @private
40364      * Inserts a given sprite into the correct position in the items collection, according to
40365      * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
40366      * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
40367      * the sprites in the correct order for proper z-index stacking.
40368      * @param {Ext.draw.Sprite} sprite
40369      * @return {Number} the sprite's new index in the list
40370      */
40371     insertByZIndex: function(sprite) {
40372         var me = this,
40373             sprites = me.items.items,
40374             len = sprites.length,
40375             ceil = Math.ceil,
40376             zIndex = sprite.attr.zIndex,
40377             idx = len,
40378             high = idx - 1,
40379             low = 0,
40380             otherZIndex;
40381
40382         if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
40383             // Find the target index via a binary search for speed
40384             while (low <= high) {
40385                 idx = ceil((low + high) / 2);
40386                 otherZIndex = sprites[idx].attr.zIndex;
40387                 if (otherZIndex > zIndex) {
40388                     high = idx - 1;
40389                 }
40390                 else if (otherZIndex < zIndex) {
40391                     low = idx + 1;
40392                 }
40393                 else {
40394                     break;
40395                 }
40396             }
40397             // Step forward to the end of a sequence of the same or lower z-index
40398             while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
40399                 idx++;
40400             }
40401         }
40402
40403         me.items.insert(idx, sprite);
40404         return idx;
40405     },
40406
40407     onAdd: function(sprite) {
40408         var group = sprite.group,
40409             draggable = sprite.draggable,
40410             groups, ln, i;
40411         if (group) {
40412             groups = [].concat(group);
40413             ln = groups.length;
40414             for (i = 0; i < ln; i++) {
40415                 group = groups[i];
40416                 this.getGroup(group).add(sprite);
40417             }
40418             delete sprite.group;
40419         }
40420         if (draggable) {
40421             sprite.initDraggable();
40422         }
40423     },
40424
40425     /**
40426      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
40427      * You can also call the sprite own `remove` method.
40428      *
40429      * For example:
40430      *
40431      *     drawComponent.surface.remove(sprite);
40432      *     //or...
40433      *     sprite.remove();
40434      *
40435      * @param {Ext.draw.Sprite} sprite
40436      * @param {Boolean} destroySprite
40437      * @return {Number} the sprite's new index in the list
40438      */
40439     remove: function(sprite, destroySprite) {
40440         if (sprite) {
40441             this.items.remove(sprite);
40442             this.groups.each(function(item) {
40443                 item.remove(sprite);
40444             });
40445             sprite.onRemove();
40446             if (destroySprite === true) {
40447                 sprite.destroy();
40448             }
40449         }
40450     },
40451
40452     /**
40453      * Removes all sprites from the surface, optionally destroying the sprites in the process.
40454      *
40455      * For example:
40456      *
40457      *     drawComponent.surface.removeAll();
40458      *
40459      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
40460      * @return {Number} The sprite's new index in the list.
40461      */
40462     removeAll: function(destroySprites) {
40463         var items = this.items.items,
40464             ln = items.length,
40465             i;
40466         for (i = ln - 1; i > -1; i--) {
40467             this.remove(items[i], destroySprites);
40468         }
40469     },
40470
40471     onRemove: Ext.emptyFn,
40472
40473     onDestroy: Ext.emptyFn,
40474
40475     /**
40476      * @private Using the current viewBox property and the surface's width and height, calculate the
40477      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
40478      */
40479     applyViewBox: function() {
40480         var me = this,
40481             viewBox = me.viewBox,
40482             width = me.width,
40483             height = me.height,
40484             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
40485             relativeHeight, relativeWidth, size;
40486
40487         if (viewBox && (width || height)) {
40488             viewBoxX = viewBox.x;
40489             viewBoxY = viewBox.y;
40490             viewBoxWidth = viewBox.width;
40491             viewBoxHeight = viewBox.height;
40492             relativeHeight = height / viewBoxHeight;
40493             relativeWidth = width / viewBoxWidth;
40494
40495             if (viewBoxWidth * relativeHeight < width) {
40496                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
40497             }
40498             if (viewBoxHeight * relativeWidth < height) {
40499                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
40500             }
40501
40502             size = 1 / Math.min(viewBoxWidth, relativeHeight);
40503
40504             me.viewBoxShift = {
40505                 dx: -viewBoxX,
40506                 dy: -viewBoxY,
40507                 scale: size
40508             };
40509         }
40510     },
40511
40512     transformToViewBox: function (x, y) {
40513         if (this.viewBoxShift) {
40514             var me = this, shift = me.viewBoxShift;
40515             return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
40516         } else {
40517             return [x, y];
40518         }
40519     },
40520
40521     // @private
40522     applyTransformations: function(sprite) {
40523             sprite.bbox.transform = 0;
40524             this.transform(sprite);
40525
40526         var me = this,
40527             dirty = false,
40528             attr = sprite.attr;
40529
40530         if (attr.translation.x != null || attr.translation.y != null) {
40531             me.translate(sprite);
40532             dirty = true;
40533         }
40534         if (attr.scaling.x != null || attr.scaling.y != null) {
40535             me.scale(sprite);
40536             dirty = true;
40537         }
40538         if (attr.rotation.degrees != null) {
40539             me.rotate(sprite);
40540             dirty = true;
40541         }
40542         if (dirty) {
40543             sprite.bbox.transform = 0;
40544             this.transform(sprite);
40545             sprite.transformations = [];
40546         }
40547     },
40548
40549     // @private
40550     rotate: function (sprite) {
40551         var bbox,
40552             deg = sprite.attr.rotation.degrees,
40553             centerX = sprite.attr.rotation.x,
40554             centerY = sprite.attr.rotation.y;
40555         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
40556             bbox = this.getBBox(sprite);
40557             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
40558             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
40559         }
40560         sprite.transformations.push({
40561             type: "rotate",
40562             degrees: deg,
40563             x: centerX,
40564             y: centerY
40565         });
40566     },
40567
40568     // @private
40569     translate: function(sprite) {
40570         var x = sprite.attr.translation.x || 0,
40571             y = sprite.attr.translation.y || 0;
40572         sprite.transformations.push({
40573             type: "translate",
40574             x: x,
40575             y: y
40576         });
40577     },
40578
40579     // @private
40580     scale: function(sprite) {
40581         var bbox,
40582             x = sprite.attr.scaling.x || 1,
40583             y = sprite.attr.scaling.y || 1,
40584             centerX = sprite.attr.scaling.centerX,
40585             centerY = sprite.attr.scaling.centerY;
40586
40587         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
40588             bbox = this.getBBox(sprite);
40589             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
40590             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
40591         }
40592         sprite.transformations.push({
40593             type: "scale",
40594             x: x,
40595             y: y,
40596             centerX: centerX,
40597             centerY: centerY
40598         });
40599     },
40600
40601     // @private
40602     rectPath: function (x, y, w, h, r) {
40603         if (r) {
40604             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"]];
40605         }
40606         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
40607     },
40608
40609     // @private
40610     ellipsePath: function (x, y, rx, ry) {
40611         if (ry == null) {
40612             ry = rx;
40613         }
40614         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"]];
40615     },
40616
40617     // @private
40618     getPathpath: function (el) {
40619         return el.attr.path;
40620     },
40621
40622     // @private
40623     getPathcircle: function (el) {
40624         var a = el.attr;
40625         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
40626     },
40627
40628     // @private
40629     getPathellipse: function (el) {
40630         var a = el.attr;
40631         return this.ellipsePath(a.x, a.y,
40632                                 a.radiusX || (a.width / 2) || 0,
40633                                 a.radiusY || (a.height / 2) || 0);
40634     },
40635
40636     // @private
40637     getPathrect: function (el) {
40638         var a = el.attr;
40639         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
40640     },
40641
40642     // @private
40643     getPathimage: function (el) {
40644         var a = el.attr;
40645         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
40646     },
40647
40648     // @private
40649     getPathtext: function (el) {
40650         var bbox = this.getBBoxText(el);
40651         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
40652     },
40653
40654     createGroup: function(id) {
40655         var group = this.groups.get(id);
40656         if (!group) {
40657             group = Ext.create('Ext.draw.CompositeSprite', {
40658                 surface: this
40659             });
40660             group.id = id || Ext.id(null, 'ext-surface-group-');
40661             this.groups.add(group);
40662         }
40663         return group;
40664     },
40665
40666     /**
40667      * Returns a new group or an existent group associated with the current surface.
40668      * The group returned is a {@link Ext.draw.CompositeSprite} group.
40669      *
40670      * For example:
40671      *
40672      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
40673      *
40674      * @param {String} id The unique identifier of the group.
40675      * @return {Object} The {@link Ext.draw.CompositeSprite}.
40676      */
40677     getGroup: function(id) {
40678         if (typeof id == "string") {
40679             var group = this.groups.get(id);
40680             if (!group) {
40681                 group = this.createGroup(id);
40682             }
40683         } else {
40684             group = id;
40685         }
40686         return group;
40687     },
40688
40689     // @private
40690     prepareItems: function(items, applyDefaults) {
40691         items = [].concat(items);
40692         // Make sure defaults are applied and item is initialized
40693         var item, i, ln;
40694         for (i = 0, ln = items.length; i < ln; i++) {
40695             item = items[i];
40696             if (!(item instanceof Ext.draw.Sprite)) {
40697                 // Temporary, just take in configs...
40698                 item.surface = this;
40699                 items[i] = this.createItem(item);
40700             } else {
40701                 item.surface = this;
40702             }
40703         }
40704         return items;
40705     },
40706
40707     /**
40708      * Changes the text in the sprite element. The sprite must be a `text` sprite.
40709      * This method can also be called from {@link Ext.draw.Sprite}.
40710      *
40711      * For example:
40712      *
40713      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
40714      *
40715      * @param {Object} sprite The Sprite to change the text.
40716      * @param {String} text The new text to be set.
40717      * @method
40718      */
40719     setText: Ext.emptyFn,
40720
40721     //@private Creates an item and appends it to the surface. Called
40722     //as an internal method when calling `add`.
40723     createItem: Ext.emptyFn,
40724
40725     /**
40726      * Retrieves the id of this component.
40727      * Will autogenerate an id if one has not already been set.
40728      */
40729     getId: function() {
40730         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
40731     },
40732
40733     /**
40734      * Destroys the surface. This is done by removing all components from it and
40735      * also removing its reference to a DOM element.
40736      *
40737      * For example:
40738      *
40739      *      drawComponent.surface.destroy();
40740      */
40741     destroy: function() {
40742         delete this.domRef;
40743         this.removeAll();
40744     }
40745 });
40746 /**
40747  * @class Ext.layout.component.Draw
40748  * @extends Ext.layout.component.Component
40749  * @private
40750  *
40751  */
40752
40753 Ext.define('Ext.layout.component.Draw', {
40754
40755     /* Begin Definitions */
40756
40757     alias: 'layout.draw',
40758
40759     extend: 'Ext.layout.component.Auto',
40760
40761     /* End Definitions */
40762
40763     type: 'draw',
40764
40765     onLayout : function(width, height) {
40766         this.owner.surface.setSize(width, height);
40767         this.callParent(arguments);
40768     }
40769 });
40770 /**
40771  * @class Ext.draw.Component
40772  * @extends Ext.Component
40773  *
40774  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
40775  * manages and holds a `Surface` instance: an interface that has
40776  * an SVG or VML implementation depending on the browser capabilities and where
40777  * Sprites can be appended.
40778  *
40779  * One way to create a draw component is:
40780  *
40781  *     @example
40782  *     var drawComponent = Ext.create('Ext.draw.Component', {
40783  *         viewBox: false,
40784  *         items: [{
40785  *             type: 'circle',
40786  *             fill: '#79BB3F',
40787  *             radius: 100,
40788  *             x: 100,
40789  *             y: 100
40790  *         }]
40791  *     });
40792  *
40793  *     Ext.create('Ext.Window', {
40794  *         width: 215,
40795  *         height: 235,
40796  *         layout: 'fit',
40797  *         items: [drawComponent]
40798  *     }).show();
40799  *
40800  * In this case we created a draw component and added a sprite to it.
40801  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
40802  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
40803  * dimensions accordingly.
40804  *
40805  * You can also add sprites by using the surface's add method:
40806  *
40807  *     drawComponent.surface.add({
40808  *         type: 'circle',
40809  *         fill: '#79BB3F',
40810  *         radius: 100,
40811  *         x: 100,
40812  *         y: 100
40813  *     });
40814  *
40815  * For more information on Sprites, the core elements added to a draw component's surface,
40816  * refer to the Ext.draw.Sprite documentation.
40817  */
40818 Ext.define('Ext.draw.Component', {
40819
40820     /* Begin Definitions */
40821
40822     alias: 'widget.draw',
40823
40824     extend: 'Ext.Component',
40825
40826     requires: [
40827         'Ext.draw.Surface',
40828         'Ext.layout.component.Draw'
40829     ],
40830
40831     /* End Definitions */
40832
40833     /**
40834      * @cfg {String[]} enginePriority
40835      * Defines the priority order for which Surface implementation to use. The first
40836      * one supported by the current environment will be used.
40837      */
40838     enginePriority: ['Svg', 'Vml'],
40839
40840     baseCls: Ext.baseCSSPrefix + 'surface',
40841
40842     componentLayout: 'draw',
40843
40844     /**
40845      * @cfg {Boolean} viewBox
40846      * Turn on view box support which will scale and position items in the draw component to fit to the component while
40847      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
40848      */
40849     viewBox: true,
40850
40851     /**
40852      * @cfg {Boolean} autoSize
40853      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
40854      */
40855     autoSize: false,
40856
40857     /**
40858      * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
40859      * The gradients array is an array of objects with the following properties:
40860      *
40861      *  - `id` - string - The unique name of the gradient.
40862      *  - `angle` - number, optional - The angle of the gradient in degrees.
40863      *  - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
40864      *
40865      * ## Example
40866      *
40867      *     gradients: [{
40868      *         id: 'gradientId',
40869      *         angle: 45,
40870      *         stops: {
40871      *             0: {
40872      *                 color: '#555'
40873      *             },
40874      *             100: {
40875      *                 color: '#ddd'
40876      *             }
40877      *         }
40878      *     }, {
40879      *         id: 'gradientId2',
40880      *         angle: 0,
40881      *         stops: {
40882      *             0: {
40883      *                 color: '#590'
40884      *             },
40885      *             20: {
40886      *                 color: '#599'
40887      *             },
40888      *             100: {
40889      *                 color: '#ddd'
40890      *             }
40891      *         }
40892      *     }]
40893      *
40894      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
40895      *
40896      *     sprite.setAttributes({
40897      *         fill: 'url(#gradientId)'
40898      *     }, true);
40899      */
40900     initComponent: function() {
40901         this.callParent(arguments);
40902
40903         this.addEvents(
40904             'mousedown',
40905             'mouseup',
40906             'mousemove',
40907             'mouseenter',
40908             'mouseleave',
40909             'click'
40910         );
40911     },
40912
40913     /**
40914      * @private
40915      *
40916      * Create the Surface on initial render
40917      */
40918     onRender: function() {
40919         var me = this,
40920             viewBox = me.viewBox,
40921             autoSize = me.autoSize,
40922             bbox, items, width, height, x, y;
40923         me.callParent(arguments);
40924
40925         if (me.createSurface() !== false) {
40926             items = me.surface.items;
40927
40928             if (viewBox || autoSize) {
40929                 bbox = items.getBBox();
40930                 width = bbox.width;
40931                 height = bbox.height;
40932                 x = bbox.x;
40933                 y = bbox.y;
40934                 if (me.viewBox) {
40935                     me.surface.setViewBox(x, y, width, height);
40936                 }
40937                 else {
40938                     // AutoSized
40939                     me.autoSizeSurface();
40940                 }
40941             }
40942         }
40943     },
40944
40945     //@private
40946     autoSizeSurface: function() {
40947         var me = this,
40948             items = me.surface.items,
40949             bbox = items.getBBox(),
40950             width = bbox.width,
40951             height = bbox.height;
40952         items.setAttributes({
40953             translate: {
40954                 x: -bbox.x,
40955                 //Opera has a slight offset in the y axis.
40956                 y: -bbox.y + (+Ext.isOpera)
40957             }
40958         }, true);
40959         if (me.rendered) {
40960             me.setSize(width, height);
40961             me.surface.setSize(width, height);
40962         }
40963         else {
40964             me.surface.setSize(width, height);
40965         }
40966         me.el.setSize(width, height);
40967     },
40968
40969     /**
40970      * Create the Surface instance. Resolves the correct Surface implementation to
40971      * instantiate based on the 'enginePriority' config. Once the Surface instance is
40972      * created you can use the handle to that instance to add sprites. For example:
40973      *
40974      *     drawComponent.surface.add(sprite);
40975      */
40976     createSurface: function() {
40977         var surface = Ext.draw.Surface.create(Ext.apply({}, {
40978                 width: this.width,
40979                 height: this.height,
40980                 renderTo: this.el
40981             }, this.initialConfig));
40982         if (!surface) {
40983             // In case we cannot create a surface, return false so we can stop
40984             return false;
40985         }
40986         this.surface = surface;
40987
40988
40989         function refire(eventName) {
40990             return function(e) {
40991                 this.fireEvent(eventName, e);
40992             };
40993         }
40994
40995         surface.on({
40996             scope: this,
40997             mouseup: refire('mouseup'),
40998             mousedown: refire('mousedown'),
40999             mousemove: refire('mousemove'),
41000             mouseenter: refire('mouseenter'),
41001             mouseleave: refire('mouseleave'),
41002             click: refire('click')
41003         });
41004     },
41005
41006
41007     /**
41008      * @private
41009      *
41010      * Clean up the Surface instance on component destruction
41011      */
41012     onDestroy: function() {
41013         var surface = this.surface;
41014         if (surface) {
41015             surface.destroy();
41016         }
41017         this.callParent(arguments);
41018     }
41019
41020 });
41021
41022 /**
41023  * @class Ext.chart.LegendItem
41024  * @extends Ext.draw.CompositeSprite
41025  * A single item of a legend (marker plus label)
41026  */
41027 Ext.define('Ext.chart.LegendItem', {
41028
41029     /* Begin Definitions */
41030
41031     extend: 'Ext.draw.CompositeSprite',
41032
41033     requires: ['Ext.chart.Shape'],
41034
41035     /* End Definitions */
41036
41037     // Position of the item, relative to the upper-left corner of the legend box
41038     x: 0,
41039     y: 0,
41040     zIndex: 500,
41041
41042     constructor: function(config) {
41043         this.callParent(arguments);
41044         this.createLegend(config);
41045     },
41046
41047     /**
41048      * Creates all the individual sprites for this legend item
41049      */
41050     createLegend: function(config) {
41051         var me = this,
41052             index = config.yFieldIndex,
41053             series = me.series,
41054             seriesType = series.type,
41055             idx = me.yFieldIndex,
41056             legend = me.legend,
41057             surface = me.surface,
41058             refX = legend.x + me.x,
41059             refY = legend.y + me.y,
41060             bbox, z = me.zIndex,
41061             markerConfig, label, mask,
41062             radius, toggle = false,
41063             seriesStyle = Ext.apply(series.seriesStyle, series.style);
41064
41065         function getSeriesProp(name) {
41066             var val = series[name];
41067             return (Ext.isArray(val) ? val[idx] : val);
41068         }
41069         
41070         label = me.add('label', surface.add({
41071             type: 'text',
41072             x: 20,
41073             y: 0,
41074             zIndex: z || 0,
41075             font: legend.labelFont,
41076             text: getSeriesProp('title') || getSeriesProp('yField')
41077         }));
41078
41079         // Line series - display as short line with optional marker in the middle
41080         if (seriesType === 'line' || seriesType === 'scatter') {
41081             if(seriesType === 'line') {
41082                 me.add('line', surface.add({
41083                     type: 'path',
41084                     path: 'M0.5,0.5L16.5,0.5',
41085                     zIndex: z,
41086                     "stroke-width": series.lineWidth,
41087                     "stroke-linejoin": "round",
41088                     "stroke-dasharray": series.dash,
41089                     stroke: seriesStyle.stroke || '#000',
41090                     style: {
41091                         cursor: 'pointer'
41092                     }
41093                 }));
41094             }
41095             if (series.showMarkers || seriesType === 'scatter') {
41096                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
41097                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
41098                     fill: markerConfig.fill,
41099                     x: 8.5,
41100                     y: 0.5,
41101                     zIndex: z,
41102                     radius: markerConfig.radius || markerConfig.size,
41103                     style: {
41104                         cursor: 'pointer'
41105                     }
41106                 }));
41107             }
41108         }
41109         // All other series types - display as filled box
41110         else {
41111             me.add('box', surface.add({
41112                 type: 'rect',
41113                 zIndex: z,
41114                 x: 0,
41115                 y: 0,
41116                 width: 12,
41117                 height: 12,
41118                 fill: series.getLegendColor(index),
41119                 style: {
41120                     cursor: 'pointer'
41121                 }
41122             }));
41123         }
41124         
41125         me.setAttributes({
41126             hidden: false
41127         }, true);
41128         
41129         bbox = me.getBBox();
41130         
41131         mask = me.add('mask', surface.add({
41132             type: 'rect',
41133             x: bbox.x,
41134             y: bbox.y,
41135             width: bbox.width || 20,
41136             height: bbox.height || 20,
41137             zIndex: (z || 0) + 1000,
41138             fill: '#f00',
41139             opacity: 0,
41140             style: {
41141                 'cursor': 'pointer'
41142             }
41143         }));
41144
41145         //add toggle listener
41146         me.on('mouseover', function() {
41147             label.setStyle({
41148                 'font-weight': 'bold'
41149             });
41150             mask.setStyle({
41151                 'cursor': 'pointer'
41152             });
41153             series._index = index;
41154             series.highlightItem();
41155         }, me);
41156
41157         me.on('mouseout', function() {
41158             label.setStyle({
41159                 'font-weight': 'normal'
41160             });
41161             series._index = index;
41162             series.unHighlightItem();
41163         }, me);
41164         
41165         if (!series.visibleInLegend(index)) {
41166             toggle = true;
41167             label.setAttributes({
41168                opacity: 0.5
41169             }, true);
41170         }
41171
41172         me.on('mousedown', function() {
41173             if (!toggle) {
41174                 series.hideAll();
41175                 label.setAttributes({
41176                     opacity: 0.5
41177                 }, true);
41178             } else {
41179                 series.showAll();
41180                 label.setAttributes({
41181                     opacity: 1
41182                 }, true);
41183             }
41184             toggle = !toggle;
41185         }, me);
41186         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
41187     },
41188
41189     /**
41190      * Update the positions of all this item's sprites to match the root position
41191      * of the legend box.
41192      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
41193      *                 as the reference point for the relative positioning. Defaults to the Legend.
41194      */
41195     updatePosition: function(relativeTo) {
41196         var me = this,
41197             items = me.items,
41198             ln = items.length,
41199             i = 0,
41200             item;
41201         if (!relativeTo) {
41202             relativeTo = me.legend;
41203         }
41204         for (; i < ln; i++) {
41205             item = items[i];
41206             switch (item.type) {
41207                 case 'text':
41208                     item.setAttributes({
41209                         x: 20 + relativeTo.x + me.x,
41210                         y: relativeTo.y + me.y
41211                     }, true);
41212                     break;
41213                 case 'rect':
41214                     item.setAttributes({
41215                         translate: {
41216                             x: relativeTo.x + me.x,
41217                             y: relativeTo.y + me.y - 6
41218                         }
41219                     }, true);
41220                     break;
41221                 default:
41222                     item.setAttributes({
41223                         translate: {
41224                             x: relativeTo.x + me.x,
41225                             y: relativeTo.y + me.y
41226                         }
41227                     }, true);
41228             }
41229         }
41230     }
41231 });
41232
41233 /**
41234  * @class Ext.chart.Legend
41235  *
41236  * Defines a legend for a chart's series.
41237  * The 'chart' member must be set prior to rendering.
41238  * The legend class displays a list of legend items each of them related with a
41239  * series being rendered. In order to render the legend item of the proper series
41240  * the series configuration object must have `showInSeries` set to true.
41241  *
41242  * The legend configuration object accepts a `position` as parameter.
41243  * The `position` parameter can be `left`, `right`
41244  * `top` or `bottom`. For example:
41245  *
41246  *     legend: {
41247  *         position: 'right'
41248  *     },
41249  *
41250  * ## Example
41251  *
41252  *     @example
41253  *     var store = Ext.create('Ext.data.JsonStore', {
41254  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
41255  *         data: [
41256  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
41257  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
41258  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
41259  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
41260  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
41261  *         ]
41262  *     });
41263  *
41264  *     Ext.create('Ext.chart.Chart', {
41265  *         renderTo: Ext.getBody(),
41266  *         width: 500,
41267  *         height: 300,
41268  *         animate: true,
41269  *         store: store,
41270  *         shadow: true,
41271  *         theme: 'Category1',
41272  *         legend: {
41273  *             position: 'top'
41274  *         },
41275  *         axes: [
41276  *             {
41277  *                 type: 'Numeric',
41278  *                 grid: true,
41279  *                 position: 'left',
41280  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
41281  *                 title: 'Sample Values',
41282  *                 grid: {
41283  *                     odd: {
41284  *                         opacity: 1,
41285  *                         fill: '#ddd',
41286  *                         stroke: '#bbb',
41287  *                         'stroke-width': 1
41288  *                     }
41289  *                 },
41290  *                 minimum: 0,
41291  *                 adjustMinimumByMajorUnit: 0
41292  *             },
41293  *             {
41294  *                 type: 'Category',
41295  *                 position: 'bottom',
41296  *                 fields: ['name'],
41297  *                 title: 'Sample Metrics',
41298  *                 grid: true,
41299  *                 label: {
41300  *                     rotate: {
41301  *                         degrees: 315
41302  *                     }
41303  *                 }
41304  *             }
41305  *         ],
41306  *         series: [{
41307  *             type: 'area',
41308  *             highlight: false,
41309  *             axis: 'left',
41310  *             xField: 'name',
41311  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
41312  *             style: {
41313  *                 opacity: 0.93
41314  *             }
41315  *         }]
41316  *     });
41317  */
41318 Ext.define('Ext.chart.Legend', {
41319
41320     /* Begin Definitions */
41321
41322     requires: ['Ext.chart.LegendItem'],
41323
41324     /* End Definitions */
41325
41326     /**
41327      * @cfg {Boolean} visible
41328      * Whether or not the legend should be displayed.
41329      */
41330     visible: true,
41331
41332     /**
41333      * @cfg {String} position
41334      * The position of the legend in relation to the chart. One of: "top",
41335      * "bottom", "left", "right", or "float". If set to "float", then the legend
41336      * box will be positioned at the point denoted by the x and y parameters.
41337      */
41338     position: 'bottom',
41339
41340     /**
41341      * @cfg {Number} x
41342      * X-position of the legend box. Used directly if position is set to "float", otherwise
41343      * it will be calculated dynamically.
41344      */
41345     x: 0,
41346
41347     /**
41348      * @cfg {Number} y
41349      * Y-position of the legend box. Used directly if position is set to "float", otherwise
41350      * it will be calculated dynamically.
41351      */
41352     y: 0,
41353
41354     /**
41355      * @cfg {String} labelFont
41356      * Font to be used for the legend labels, eg '12px Helvetica'
41357      */
41358     labelFont: '12px Helvetica, sans-serif',
41359
41360     /**
41361      * @cfg {String} boxStroke
41362      * Style of the stroke for the legend box
41363      */
41364     boxStroke: '#000',
41365
41366     /**
41367      * @cfg {String} boxStrokeWidth
41368      * Width of the stroke for the legend box
41369      */
41370     boxStrokeWidth: 1,
41371
41372     /**
41373      * @cfg {String} boxFill
41374      * Fill style for the legend box
41375      */
41376     boxFill: '#FFF',
41377
41378     /**
41379      * @cfg {Number} itemSpacing
41380      * Amount of space between legend items
41381      */
41382     itemSpacing: 10,
41383
41384     /**
41385      * @cfg {Number} padding
41386      * Amount of padding between the legend box's border and its items
41387      */
41388     padding: 5,
41389
41390     // @private
41391     width: 0,
41392     // @private
41393     height: 0,
41394
41395     /**
41396      * @cfg {Number} boxZIndex
41397      * Sets the z-index for the legend. Defaults to 100.
41398      */
41399     boxZIndex: 100,
41400
41401     /**
41402      * Creates new Legend.
41403      * @param {Object} config  (optional) Config object.
41404      */
41405     constructor: function(config) {
41406         var me = this;
41407         if (config) {
41408             Ext.apply(me, config);
41409         }
41410         me.items = [];
41411         /**
41412          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
41413          * @type {Boolean}
41414          */
41415         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
41416
41417         // cache these here since they may get modified later on
41418         me.origX = me.x;
41419         me.origY = me.y;
41420     },
41421
41422     /**
41423      * @private Create all the sprites for the legend
41424      */
41425     create: function() {
41426         var me = this;
41427         me.createBox();
41428         me.createItems();
41429         if (!me.created && me.isDisplayed()) {
41430             me.created = true;
41431
41432             // Listen for changes to series titles to trigger regeneration of the legend
41433             me.chart.series.each(function(series) {
41434                 series.on('titlechange', function() {
41435                     me.create();
41436                     me.updatePosition();
41437                 });
41438             });
41439         }
41440     },
41441
41442     /**
41443      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
41444      * and also the 'showInLegend' config for each of the series.
41445      */
41446     isDisplayed: function() {
41447         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
41448     },
41449
41450     /**
41451      * @private Create the series markers and labels
41452      */
41453     createItems: function() {
41454         var me = this,
41455             chart = me.chart,
41456             surface = chart.surface,
41457             items = me.items,
41458             padding = me.padding,
41459             itemSpacing = me.itemSpacing,
41460             spacingOffset = 2,
41461             maxWidth = 0,
41462             maxHeight = 0,
41463             totalWidth = 0,
41464             totalHeight = 0,
41465             vertical = me.isVertical,
41466             math = Math,
41467             mfloor = math.floor,
41468             mmax = math.max,
41469             index = 0,
41470             i = 0,
41471             len = items ? items.length : 0,
41472             x, y, spacing, item, bbox, height, width;
41473
41474         //remove all legend items
41475         if (len) {
41476             for (; i < len; i++) {
41477                 items[i].destroy();
41478             }
41479         }
41480         //empty array
41481         items.length = [];
41482         // Create all the item labels, collecting their dimensions and positioning each one
41483         // properly in relation to the previous item
41484         chart.series.each(function(series, i) {
41485             if (series.showInLegend) {
41486                 Ext.each([].concat(series.yField), function(field, j) {
41487                     item = Ext.create('Ext.chart.LegendItem', {
41488                         legend: this,
41489                         series: series,
41490                         surface: chart.surface,
41491                         yFieldIndex: j
41492                     });
41493                     bbox = item.getBBox();
41494
41495                     //always measure from x=0, since not all markers go all the way to the left
41496                     width = bbox.width;
41497                     height = bbox.height;
41498
41499                     if (i + j === 0) {
41500                         spacing = vertical ? padding + height / 2 : padding;
41501                     }
41502                     else {
41503                         spacing = itemSpacing / (vertical ? 2 : 1);
41504                     }
41505                     // Set the item's position relative to the legend box
41506                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
41507                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
41508
41509                     // Collect cumulative dimensions
41510                     totalWidth += width + spacing;
41511                     totalHeight += height + spacing;
41512                     maxWidth = mmax(maxWidth, width);
41513                     maxHeight = mmax(maxHeight, height);
41514
41515                     items.push(item);
41516                 }, this);
41517             }
41518         }, me);
41519
41520         // Store the collected dimensions for later
41521         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
41522         if (vertical && items.length === 1) {
41523             spacingOffset = 1;
41524         }
41525         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
41526         me.itemHeight = maxHeight;
41527     },
41528
41529     /**
41530      * @private Get the bounds for the legend's outer box
41531      */
41532     getBBox: function() {
41533         var me = this;
41534         return {
41535             x: Math.round(me.x) - me.boxStrokeWidth / 2,
41536             y: Math.round(me.y) - me.boxStrokeWidth / 2,
41537             width: me.width,
41538             height: me.height
41539         };
41540     },
41541
41542     /**
41543      * @private Create the box around the legend items
41544      */
41545     createBox: function() {
41546         var me = this,
41547             box;
41548
41549         if (me.boxSprite) {
41550             me.boxSprite.destroy();
41551         }
41552         
41553         box = me.boxSprite = me.chart.surface.add(Ext.apply({
41554             type: 'rect',
41555             stroke: me.boxStroke,
41556             "stroke-width": me.boxStrokeWidth,
41557             fill: me.boxFill,
41558             zIndex: me.boxZIndex
41559         }, me.getBBox()));
41560
41561         box.redraw();
41562     },
41563
41564     /**
41565      * @private Update the position of all the legend's sprites to match its current x/y values
41566      */
41567     updatePosition: function() {
41568         var me = this,
41569             x, y,
41570             legendWidth = me.width,
41571             legendHeight = me.height,
41572             padding = me.padding,
41573             chart = me.chart,
41574             chartBBox = chart.chartBBox,
41575             insets = chart.insetPadding,
41576             chartWidth = chartBBox.width - (insets * 2),
41577             chartHeight = chartBBox.height - (insets * 2),
41578             chartX = chartBBox.x + insets,
41579             chartY = chartBBox.y + insets,
41580             surface = chart.surface,
41581             mfloor = Math.floor;
41582
41583         if (me.isDisplayed()) {
41584             // Find the position based on the dimensions
41585             switch(me.position) {
41586                 case "left":
41587                     x = insets;
41588                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
41589                     break;
41590                 case "right":
41591                     x = mfloor(surface.width - legendWidth) - insets;
41592                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
41593                     break;
41594                 case "top":
41595                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
41596                     y = insets;
41597                     break;
41598                 case "bottom":
41599                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
41600                     y = mfloor(surface.height - legendHeight) - insets;
41601                     break;
41602                 default:
41603                     x = mfloor(me.origX) + insets;
41604                     y = mfloor(me.origY) + insets;
41605             }
41606             me.x = x;
41607             me.y = y;
41608
41609             // Update the position of each item
41610             Ext.each(me.items, function(item) {
41611                 item.updatePosition();
41612             });
41613             // Update the position of the outer box
41614             me.boxSprite.setAttributes(me.getBBox(), true);
41615         }
41616     }
41617 });
41618
41619 /**
41620  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
41621  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
41622  * updates its display whenever data in the Store changes. In addition, the look and feel
41623  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
41624  * 
41625  * ## Creating a Simple Chart
41626  * 
41627  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
41628  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
41629  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
41630  * 
41631  * ### 1. Creating a Store
41632  * 
41633  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
41634  * data that will be displayed in the Chart. For example the data for a chart that displays
41635  * a weather forecast could be represented as a series of "WeatherPoint" data points with
41636  * two fields - "temperature", and "date":
41637  * 
41638  *     Ext.define('WeatherPoint', {
41639  *         extend: 'Ext.data.Model',
41640  *         fields: ['temperature', 'date']
41641  *     });
41642  * 
41643  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
41644  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
41645  * 
41646  *     var store = Ext.create('Ext.data.Store', {
41647  *         model: 'WeatherPoint',
41648  *         data: [
41649  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
41650  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
41651  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
41652  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
41653  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
41654  *         ]
41655  *     });
41656  *    
41657  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
41658  * 
41659  * ### 2. Creating the Chart object
41660  * 
41661  * Now that a Store has been created it can be used in a Chart:
41662  * 
41663  *     Ext.create('Ext.chart.Chart', {
41664  *        renderTo: Ext.getBody(),
41665  *        width: 400,
41666  *        height: 300,
41667  *        store: store
41668  *     });
41669  *    
41670  * That's all it takes to create a Chart instance that is backed by a Store.
41671  * However, if the above code is run in a browser, a blank screen will be displayed.
41672  * This is because the two pieces that are responsible for the visual display,
41673  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
41674  * 
41675  * ### 3. Configuring the Axes
41676  * 
41677  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
41678  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
41679  * 
41680  *     Ext.create('Ext.chart.Chart', {
41681  *         ...
41682  *         axes: [
41683  *             {
41684  *                 title: 'Temperature',
41685  *                 type: 'Numeric',
41686  *                 position: 'left',
41687  *                 fields: ['temperature'],
41688  *                 minimum: 0,
41689  *                 maximum: 100
41690  *             },
41691  *             {
41692  *                 title: 'Time',
41693  *                 type: 'Time',
41694  *                 position: 'bottom',
41695  *                 fields: ['date'],
41696  *                 dateFormat: 'ga'
41697  *             }
41698  *         ]
41699  *     });
41700  *    
41701  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
41702  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
41703  * defined above. The minimum value for this axis is "0", and the maximum is "100".
41704  * 
41705  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
41706  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
41707  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
41708  * configuration tells the Time Axis how to format it's labels.
41709  * 
41710  * Here's what the Chart looks like now that it has its Axes configured:
41711  * 
41712  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
41713  * 
41714  * ### 4. Configuring the Series
41715  * 
41716  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
41717  * Series are responsible for the visual representation of the data points contained in the Store.
41718  * This example only has one Series:
41719  * 
41720  *     Ext.create('Ext.chart.Chart', {
41721  *         ...
41722  *         axes: [
41723  *             ...
41724  *         ],
41725  *         series: [
41726  *             {
41727  *                 type: 'line',
41728  *                 xField: 'date',
41729  *                 yField: 'temperature'
41730  *             }
41731  *         ]
41732  *     });
41733  *     
41734  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
41735  * from the "WeatherPoint" Models in the Store to plot its data points:
41736  * 
41737  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
41738  * 
41739  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
41740  * 
41741  * ## Themes
41742  * 
41743  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
41744  * 
41745  *     Ext.create('Ext.chart.Chart', {
41746  *         ...
41747  *         theme: 'Green',
41748  *         ...
41749  *     });
41750  * 
41751  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
41752  * 
41753  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
41754  * 
41755  */
41756 Ext.define('Ext.chart.Chart', {
41757
41758     /* Begin Definitions */
41759
41760     alias: 'widget.chart',
41761
41762     extend: 'Ext.draw.Component',
41763     
41764     mixins: {
41765         themeManager: 'Ext.chart.theme.Theme',
41766         mask: 'Ext.chart.Mask',
41767         navigation: 'Ext.chart.Navigation'
41768     },
41769
41770     requires: [
41771         'Ext.util.MixedCollection',
41772         'Ext.data.StoreManager',
41773         'Ext.chart.Legend',
41774         'Ext.util.DelayedTask'
41775     ],
41776
41777     /* End Definitions */
41778
41779     // @private
41780     viewBox: false,
41781
41782     /**
41783      * @cfg {String} theme
41784      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
41785      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
41786      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
41787      * is 'Base'.
41788      */
41789
41790     /**
41791      * @cfg {Boolean/Object} animate
41792      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
41793      * object to be used for default chart animations. Defaults to false.
41794      */
41795     animate: false,
41796
41797     /**
41798      * @cfg {Boolean/Object} legend
41799      * True for the default legend display or a legend config object. Defaults to false.
41800      */
41801     legend: false,
41802
41803     /**
41804      * @cfg {Number} insetPadding
41805      * The amount of inset padding in pixels for the chart. Defaults to 10.
41806      */
41807     insetPadding: 10,
41808
41809     /**
41810      * @cfg {String[]} enginePriority
41811      * Defines the priority order for which Surface implementation to use. The first one supported by the current
41812      * environment will be used. Defaults to `['Svg', 'Vml']`.
41813      */
41814     enginePriority: ['Svg', 'Vml'],
41815
41816     /**
41817      * @cfg {Object/Boolean} background
41818      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
41819      * background. For example, if `background` were to be a color we could set the object as
41820      *
41821      *     background: {
41822      *         //color string
41823      *         fill: '#ccc'
41824      *     }
41825      *
41826      * You can specify an image by using:
41827      *
41828      *     background: {
41829      *         image: 'http://path.to.image/'
41830      *     }
41831      *
41832      * Also you can specify a gradient by using the gradient object syntax:
41833      *
41834      *     background: {
41835      *         gradient: {
41836      *             id: 'gradientId',
41837      *             angle: 45,
41838      *             stops: {
41839      *                 0: {
41840      *                     color: '#555'
41841      *                 }
41842      *                 100: {
41843      *                     color: '#ddd'
41844      *                 }
41845      *             }
41846      *         }
41847      *     }
41848      */
41849     background: false,
41850
41851     /**
41852      * @cfg {Object[]} gradients
41853      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
41854      * array of objects with the following properties:
41855      *
41856      * - **id** - string - The unique name of the gradient.
41857      * - **angle** - number, optional - The angle of the gradient in degrees.
41858      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
41859      *
41860      * For example:
41861      *
41862      *     gradients: [{
41863      *         id: 'gradientId',
41864      *         angle: 45,
41865      *         stops: {
41866      *             0: {
41867      *                 color: '#555'
41868      *             },
41869      *             100: {
41870      *                 color: '#ddd'
41871      *             }
41872      *         }
41873      *     }, {
41874      *         id: 'gradientId2',
41875      *         angle: 0,
41876      *         stops: {
41877      *             0: {
41878      *                 color: '#590'
41879      *             },
41880      *             20: {
41881      *                 color: '#599'
41882      *             },
41883      *             100: {
41884      *                 color: '#ddd'
41885      *             }
41886      *         }
41887      *     }]
41888      *
41889      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
41890      *
41891      *     sprite.setAttributes({
41892      *         fill: 'url(#gradientId)'
41893      *     }, true);
41894      */
41895
41896     /**
41897      * @cfg {Ext.data.Store} store
41898      * The store that supplies data to this chart.
41899      */
41900
41901     /**
41902      * @cfg {Ext.chart.series.Series[]} series
41903      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
41904      * 
41905      *     series: [{
41906      *         type: 'column',
41907      *         axis: 'left',
41908      *         listeners: {
41909      *             'afterrender': function() {
41910      *                 console('afterrender');
41911      *             }
41912      *         },
41913      *         xField: 'category',
41914      *         yField: 'data1'
41915      *     }]
41916      */
41917
41918     /**
41919      * @cfg {Ext.chart.axis.Axis[]} axes
41920      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
41921      * 
41922      *     axes: [{
41923      *         type: 'Numeric',
41924      *         position: 'left',
41925      *         fields: ['data1'],
41926      *         title: 'Number of Hits',
41927      *         minimum: 0,
41928      *         //one minor tick between two major ticks
41929      *         minorTickSteps: 1
41930      *     }, {
41931      *         type: 'Category',
41932      *         position: 'bottom',
41933      *         fields: ['name'],
41934      *         title: 'Month of the Year'
41935      *     }]
41936      */
41937
41938     constructor: function(config) {
41939         var me = this,
41940             defaultAnim;
41941             
41942         config = Ext.apply({}, config);
41943         me.initTheme(config.theme || me.theme);
41944         if (me.gradients) {
41945             Ext.apply(config, { gradients: me.gradients });
41946         }
41947         if (me.background) {
41948             Ext.apply(config, { background: me.background });
41949         }
41950         if (config.animate) {
41951             defaultAnim = {
41952                 easing: 'ease',
41953                 duration: 500
41954             };
41955             if (Ext.isObject(config.animate)) {
41956                 config.animate = Ext.applyIf(config.animate, defaultAnim);
41957             }
41958             else {
41959                 config.animate = defaultAnim;
41960             }
41961         }
41962         me.mixins.mask.constructor.call(me, config);
41963         me.mixins.navigation.constructor.call(me, config);
41964         me.callParent([config]);
41965     },
41966     
41967     getChartStore: function(){
41968         return this.substore || this.store;    
41969     },
41970
41971     initComponent: function() {
41972         var me = this,
41973             axes,
41974             series;
41975         me.callParent();
41976         me.addEvents(
41977             'itemmousedown',
41978             'itemmouseup',
41979             'itemmouseover',
41980             'itemmouseout',
41981             'itemclick',
41982             'itemdoubleclick',
41983             'itemdragstart',
41984             'itemdrag',
41985             'itemdragend',
41986             /**
41987              * @event beforerefresh
41988              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
41989              * {@link #refresh} action will be cancelled.
41990              * @param {Ext.chart.Chart} this
41991              */
41992             'beforerefresh',
41993             /**
41994              * @event refresh
41995              * Fires after the chart data has been refreshed.
41996              * @param {Ext.chart.Chart} this
41997              */
41998             'refresh'
41999         );
42000         Ext.applyIf(me, {
42001             zoom: {
42002                 width: 1,
42003                 height: 1,
42004                 x: 0,
42005                 y: 0
42006             }
42007         });
42008         me.maxGutter = [0, 0];
42009         me.store = Ext.data.StoreManager.lookup(me.store);
42010         axes = me.axes;
42011         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
42012         if (axes) {
42013             me.axes.addAll(axes);
42014         }
42015         series = me.series;
42016         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
42017         if (series) {
42018             me.series.addAll(series);
42019         }
42020         if (me.legend !== false) {
42021             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
42022         }
42023
42024         me.on({
42025             mousemove: me.onMouseMove,
42026             mouseleave: me.onMouseLeave,
42027             mousedown: me.onMouseDown,
42028             mouseup: me.onMouseUp,
42029             scope: me
42030         });
42031     },
42032
42033     // @private overrides the component method to set the correct dimensions to the chart.
42034     afterComponentLayout: function(width, height) {
42035         var me = this;
42036         if (Ext.isNumber(width) && Ext.isNumber(height)) {
42037             me.curWidth = width;
42038             me.curHeight = height;
42039             me.redraw(true);
42040         }
42041         this.callParent(arguments);
42042     },
42043
42044     /**
42045      * Redraws the chart. If animations are set this will animate the chart too. 
42046      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
42047      */
42048     redraw: function(resize) {
42049         var me = this,
42050             chartBBox = me.chartBBox = {
42051                 x: 0,
42052                 y: 0,
42053                 height: me.curHeight,
42054                 width: me.curWidth
42055             },
42056             legend = me.legend;
42057         me.surface.setSize(chartBBox.width, chartBBox.height);
42058         // Instantiate Series and Axes
42059         me.series.each(me.initializeSeries, me);
42060         me.axes.each(me.initializeAxis, me);
42061         //process all views (aggregated data etc) on stores
42062         //before rendering.
42063         me.axes.each(function(axis) {
42064             axis.processView();
42065         });
42066         me.axes.each(function(axis) {
42067             axis.drawAxis(true);
42068         });
42069
42070         // Create legend if not already created
42071         if (legend !== false) {
42072             legend.create();
42073         }
42074
42075         // Place axes properly, including influence from each other
42076         me.alignAxes();
42077
42078         // Reposition legend based on new axis alignment
42079         if (me.legend !== false) {
42080             legend.updatePosition();
42081         }
42082
42083         // Find the max gutter
42084         me.getMaxGutter();
42085
42086         // Draw axes and series
42087         me.resizing = !!resize;
42088
42089         me.axes.each(me.drawAxis, me);
42090         me.series.each(me.drawCharts, me);
42091         me.resizing = false;
42092     },
42093
42094     // @private set the store after rendering the chart.
42095     afterRender: function() {
42096         var ref,
42097             me = this;
42098         this.callParent();
42099
42100         if (me.categoryNames) {
42101             me.setCategoryNames(me.categoryNames);
42102         }
42103
42104         if (me.tipRenderer) {
42105             ref = me.getFunctionRef(me.tipRenderer);
42106             me.setTipRenderer(ref.fn, ref.scope);
42107         }
42108         me.bindStore(me.store, true);
42109         me.refresh();
42110     },
42111
42112     // @private get x and y position of the mouse cursor.
42113     getEventXY: function(e) {
42114         var me = this,
42115             box = this.surface.getRegion(),
42116             pageXY = e.getXY(),
42117             x = pageXY[0] - box.left,
42118             y = pageXY[1] - box.top;
42119         return [x, y];
42120     },
42121
42122     // @private wrap the mouse down position to delegate the event to the series.
42123     onClick: function(e) {
42124         var me = this,
42125             position = me.getEventXY(e),
42126             item;
42127
42128         // Ask each series if it has an item corresponding to (not necessarily exactly
42129         // on top of) the current mouse coords. Fire itemclick event.
42130         me.series.each(function(series) {
42131             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
42132                 if (series.getItemForPoint) {
42133                     item = series.getItemForPoint(position[0], position[1]);
42134                     if (item) {
42135                         series.fireEvent('itemclick', item);
42136                     }
42137                 }
42138             }
42139         }, me);
42140     },
42141
42142     // @private wrap the mouse down position to delegate the event to the series.
42143     onMouseDown: function(e) {
42144         var me = this,
42145             position = me.getEventXY(e),
42146             item;
42147
42148         if (me.mask) {
42149             me.mixins.mask.onMouseDown.call(me, e);
42150         }
42151         // Ask each series if it has an item corresponding to (not necessarily exactly
42152         // on top of) the current mouse coords. Fire mousedown event.
42153         me.series.each(function(series) {
42154             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
42155                 if (series.getItemForPoint) {
42156                     item = series.getItemForPoint(position[0], position[1]);
42157                     if (item) {
42158                         series.fireEvent('itemmousedown', item);
42159                     }
42160                 }
42161             }
42162         }, me);
42163     },
42164
42165     // @private wrap the mouse up event to delegate it to the series.
42166     onMouseUp: function(e) {
42167         var me = this,
42168             position = me.getEventXY(e),
42169             item;
42170
42171         if (me.mask) {
42172             me.mixins.mask.onMouseUp.call(me, e);
42173         }
42174         // Ask each series if it has an item corresponding to (not necessarily exactly
42175         // on top of) the current mouse coords. Fire mousedown event.
42176         me.series.each(function(series) {
42177             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
42178                 if (series.getItemForPoint) {
42179                     item = series.getItemForPoint(position[0], position[1]);
42180                     if (item) {
42181                         series.fireEvent('itemmouseup', item);
42182                     }
42183                 }
42184             }
42185         }, me);
42186     },
42187
42188     // @private wrap the mouse move event so it can be delegated to the series.
42189     onMouseMove: function(e) {
42190         var me = this,
42191             position = me.getEventXY(e),
42192             item, last, storeItem, storeField;
42193
42194         if (me.mask) {
42195             me.mixins.mask.onMouseMove.call(me, e);
42196         }
42197         // Ask each series if it has an item corresponding to (not necessarily exactly
42198         // on top of) the current mouse coords. Fire itemmouseover/out events.
42199         me.series.each(function(series) {
42200             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
42201                 if (series.getItemForPoint) {
42202                     item = series.getItemForPoint(position[0], position[1]);
42203                     last = series._lastItemForPoint;
42204                     storeItem = series._lastStoreItem;
42205                     storeField = series._lastStoreField;
42206
42207
42208                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
42209                         if (last) {
42210                             series.fireEvent('itemmouseout', last);
42211                             delete series._lastItemForPoint;
42212                             delete series._lastStoreField;
42213                             delete series._lastStoreItem;
42214                         }
42215                         if (item) {
42216                             series.fireEvent('itemmouseover', item);
42217                             series._lastItemForPoint = item;
42218                             series._lastStoreItem = item.storeItem;
42219                             series._lastStoreField = item.storeField;
42220                         }
42221                     }
42222                 }
42223             } else {
42224                 last = series._lastItemForPoint;
42225                 if (last) {
42226                     series.fireEvent('itemmouseout', last);
42227                     delete series._lastItemForPoint;
42228                     delete series._lastStoreField;
42229                     delete series._lastStoreItem;
42230                 }
42231             }
42232         }, me);
42233     },
42234
42235     // @private handle mouse leave event.
42236     onMouseLeave: function(e) {
42237         var me = this;
42238         if (me.mask) {
42239             me.mixins.mask.onMouseLeave.call(me, e);
42240         }
42241         me.series.each(function(series) {
42242             delete series._lastItemForPoint;
42243         });
42244     },
42245
42246     // @private buffered refresh for when we update the store
42247     delayRefresh: function() {
42248         var me = this;
42249         if (!me.refreshTask) {
42250             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
42251         }
42252         me.refreshTask.delay(me.refreshBuffer);
42253     },
42254
42255     // @private
42256     refresh: function() {
42257         var me = this;
42258         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
42259             if (me.fireEvent('beforerefresh', me) !== false) {
42260                 me.redraw();
42261                 me.fireEvent('refresh', me);
42262             }
42263         }
42264     },
42265
42266     /**
42267      * Changes the data store bound to this chart and refreshes it.
42268      * @param {Ext.data.Store} store The store to bind to this chart
42269      */
42270     bindStore: function(store, initial) {
42271         var me = this;
42272         if (!initial && me.store) {
42273             if (store !== me.store && me.store.autoDestroy) {
42274                 me.store.destroyStore();
42275             }
42276             else {
42277                 me.store.un('datachanged', me.refresh, me);
42278                 me.store.un('add', me.delayRefresh, me);
42279                 me.store.un('remove', me.delayRefresh, me);
42280                 me.store.un('update', me.delayRefresh, me);
42281                 me.store.un('clear', me.refresh, me);
42282             }
42283         }
42284         if (store) {
42285             store = Ext.data.StoreManager.lookup(store);
42286             store.on({
42287                 scope: me,
42288                 datachanged: me.refresh,
42289                 add: me.delayRefresh,
42290                 remove: me.delayRefresh,
42291                 update: me.delayRefresh,
42292                 clear: me.refresh
42293             });
42294         }
42295         me.store = store;
42296         if (store && !initial) {
42297             me.refresh();
42298         }
42299     },
42300
42301     // @private Create Axis
42302     initializeAxis: function(axis) {
42303         var me = this,
42304             chartBBox = me.chartBBox,
42305             w = chartBBox.width,
42306             h = chartBBox.height,
42307             x = chartBBox.x,
42308             y = chartBBox.y,
42309             themeAttrs = me.themeAttrs,
42310             config = {
42311                 chart: me
42312             };
42313         if (themeAttrs) {
42314             config.axisStyle = Ext.apply({}, themeAttrs.axis);
42315             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
42316             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
42317             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
42318             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
42319             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
42320             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
42321             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
42322             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
42323         }
42324         switch (axis.position) {
42325             case 'top':
42326                 Ext.apply(config, {
42327                     length: w,
42328                     width: h,
42329                     x: x,
42330                     y: y
42331                 });
42332             break;
42333             case 'bottom':
42334                 Ext.apply(config, {
42335                     length: w,
42336                     width: h,
42337                     x: x,
42338                     y: h
42339                 });
42340             break;
42341             case 'left':
42342                 Ext.apply(config, {
42343                     length: h,
42344                     width: w,
42345                     x: x,
42346                     y: h
42347                 });
42348             break;
42349             case 'right':
42350                 Ext.apply(config, {
42351                     length: h,
42352                     width: w,
42353                     x: w,
42354                     y: h
42355                 });
42356             break;
42357         }
42358         if (!axis.chart) {
42359             Ext.apply(config, axis);
42360             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
42361         }
42362         else {
42363             Ext.apply(axis, config);
42364         }
42365     },
42366
42367
42368     /**
42369      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
42370      * for the space taken up on each side by the axes and legend.
42371      */
42372     alignAxes: function() {
42373         var me = this,
42374             axes = me.axes,
42375             legend = me.legend,
42376             edges = ['top', 'right', 'bottom', 'left'],
42377             chartBBox,
42378             insetPadding = me.insetPadding,
42379             insets = {
42380                 top: insetPadding,
42381                 right: insetPadding,
42382                 bottom: insetPadding,
42383                 left: insetPadding
42384             };
42385
42386         function getAxis(edge) {
42387             var i = axes.findIndex('position', edge);
42388             return (i < 0) ? null : axes.getAt(i);
42389         }
42390
42391         // Find the space needed by axes and legend as a positive inset from each edge
42392         Ext.each(edges, function(edge) {
42393             var isVertical = (edge === 'left' || edge === 'right'),
42394                 axis = getAxis(edge),
42395                 bbox;
42396
42397             // Add legend size if it's on this edge
42398             if (legend !== false) {
42399                 if (legend.position === edge) {
42400                     bbox = legend.getBBox();
42401                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
42402                 }
42403             }
42404
42405             // Add axis size if there's one on this edge only if it has been
42406             //drawn before.
42407             if (axis && axis.bbox) {
42408                 bbox = axis.bbox;
42409                 insets[edge] += (isVertical ? bbox.width : bbox.height);
42410             }
42411         });
42412         // Build the chart bbox based on the collected inset values
42413         chartBBox = {
42414             x: insets.left,
42415             y: insets.top,
42416             width: me.curWidth - insets.left - insets.right,
42417             height: me.curHeight - insets.top - insets.bottom
42418         };
42419         me.chartBBox = chartBBox;
42420
42421         // Go back through each axis and set its length and position based on the
42422         // corresponding edge of the chartBBox
42423         axes.each(function(axis) {
42424             var pos = axis.position,
42425                 isVertical = (pos === 'left' || pos === 'right');
42426
42427             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
42428             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
42429             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
42430             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
42431         });
42432     },
42433
42434     // @private initialize the series.
42435     initializeSeries: function(series, idx) {
42436         var me = this,
42437             themeAttrs = me.themeAttrs,
42438             seriesObj, markerObj, seriesThemes, st,
42439             markerThemes, colorArrayStyle = [],
42440             i = 0, l,
42441             config = {
42442                 chart: me,
42443                 seriesId: series.seriesId
42444             };
42445         if (themeAttrs) {
42446             seriesThemes = themeAttrs.seriesThemes;
42447             markerThemes = themeAttrs.markerThemes;
42448             seriesObj = Ext.apply({}, themeAttrs.series);
42449             markerObj = Ext.apply({}, themeAttrs.marker);
42450             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
42451             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
42452             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
42453             if (themeAttrs.colors) {
42454                 config.colorArrayStyle = themeAttrs.colors;
42455             } else {
42456                 colorArrayStyle = [];
42457                 for (l = seriesThemes.length; i < l; i++) {
42458                     st = seriesThemes[i];
42459                     if (st.fill || st.stroke) {
42460                         colorArrayStyle.push(st.fill || st.stroke);
42461                     }
42462                 }
42463                 if (colorArrayStyle.length) {
42464                     config.colorArrayStyle = colorArrayStyle;
42465                 }
42466             }
42467             config.seriesIdx = idx;
42468         }
42469         if (series instanceof Ext.chart.series.Series) {
42470             Ext.apply(series, config);
42471         } else {
42472             Ext.applyIf(config, series);
42473             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
42474         }
42475         if (series.initialize) {
42476             series.initialize();
42477         }
42478     },
42479
42480     // @private
42481     getMaxGutter: function() {
42482         var me = this,
42483             maxGutter = [0, 0];
42484         me.series.each(function(s) {
42485             var gutter = s.getGutters && s.getGutters() || [0, 0];
42486             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
42487             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
42488         });
42489         me.maxGutter = maxGutter;
42490     },
42491
42492     // @private draw axis.
42493     drawAxis: function(axis) {
42494         axis.drawAxis();
42495     },
42496
42497     // @private draw series.
42498     drawCharts: function(series) {
42499         series.triggerafterrender = false;
42500         series.drawSeries();
42501         if (!this.animate) {
42502             series.fireEvent('afterrender');
42503         }
42504     },
42505
42506     // @private remove gently.
42507     destroy: function() {
42508         Ext.destroy(this.surface);
42509         this.bindStore(null);
42510         this.callParent(arguments);
42511     }
42512 });
42513
42514 /**
42515  * @class Ext.chart.Highlight
42516  * A mixin providing highlight functionality for Ext.chart.series.Series.
42517  */
42518 Ext.define('Ext.chart.Highlight', {
42519
42520     /* Begin Definitions */
42521
42522     requires: ['Ext.fx.Anim'],
42523
42524     /* End Definitions */
42525
42526     /**
42527      * Highlight the given series item.
42528      * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
42529      * or just use default styles per series by setting highlight = true.
42530      */
42531     highlight: false,
42532
42533     highlightCfg : null,
42534
42535     constructor: function(config) {
42536         if (config.highlight) {
42537             if (config.highlight !== true) { //is an object
42538                 this.highlightCfg = Ext.apply({}, config.highlight);
42539             }
42540             else {
42541                 this.highlightCfg = {
42542                     fill: '#fdd',
42543                     radius: 20,
42544                     lineWidth: 5,
42545                     stroke: '#f55'
42546                 };
42547             }
42548         }
42549     },
42550
42551     /**
42552      * Highlight the given series item.
42553      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
42554      */
42555     highlightItem: function(item) {
42556         if (!item) {
42557             return;
42558         }
42559         
42560         var me = this,
42561             sprite = item.sprite,
42562             opts = me.highlightCfg,
42563             surface = me.chart.surface,
42564             animate = me.chart.animate,
42565             p, from, to, pi;
42566
42567         if (!me.highlight || !sprite || sprite._highlighted) {
42568             return;
42569         }
42570         if (sprite._anim) {
42571             sprite._anim.paused = true;
42572         }
42573         sprite._highlighted = true;
42574         if (!sprite._defaults) {
42575             sprite._defaults = Ext.apply({}, sprite.attr);
42576             from = {};
42577             to = {};
42578             for (p in opts) {
42579                 if (! (p in sprite._defaults)) {
42580                     sprite._defaults[p] = surface.availableAttrs[p];
42581                 }
42582                 from[p] = sprite._defaults[p];
42583                 to[p] = opts[p];
42584                 if (Ext.isObject(opts[p])) {
42585                     from[p] = {};
42586                     to[p] = {};
42587                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
42588                     Ext.apply(from[p], sprite._defaults[p]);
42589                     for (pi in sprite._defaults[p]) {
42590                         if (! (pi in opts[p])) {
42591                             to[p][pi] = from[p][pi];
42592                         } else {
42593                             to[p][pi] = opts[p][pi];
42594                         }
42595                     }
42596                     for (pi in opts[p]) {
42597                         if (! (pi in to[p])) {
42598                             to[p][pi] = opts[p][pi];
42599                         }
42600                     }
42601                 }
42602             }
42603             sprite._from = from;
42604             sprite._to = to;
42605             sprite._endStyle = to;
42606         }
42607         if (animate) {
42608             sprite._anim = Ext.create('Ext.fx.Anim', {
42609                 target: sprite,
42610                 from: sprite._from,
42611                 to: sprite._to,
42612                 duration: 150
42613             });
42614         } else {
42615             sprite.setAttributes(sprite._to, true);
42616         }
42617     },
42618
42619     /**
42620      * Un-highlight any existing highlights
42621      */
42622     unHighlightItem: function() {
42623         if (!this.highlight || !this.items) {
42624             return;
42625         }
42626
42627         var me = this,
42628             items = me.items,
42629             len = items.length,
42630             opts = me.highlightCfg,
42631             animate = me.chart.animate,
42632             i = 0,
42633             obj, p, sprite;
42634
42635         for (; i < len; i++) {
42636             if (!items[i]) {
42637                 continue;
42638             }
42639             sprite = items[i].sprite;
42640             if (sprite && sprite._highlighted) {
42641                 if (sprite._anim) {
42642                     sprite._anim.paused = true;
42643                 }
42644                 obj = {};
42645                 for (p in opts) {
42646                     if (Ext.isObject(sprite._defaults[p])) {
42647                         obj[p] = {};
42648                         Ext.apply(obj[p], sprite._defaults[p]);
42649                     }
42650                     else {
42651                         obj[p] = sprite._defaults[p];
42652                     }
42653                 }
42654                 if (animate) {
42655                     //sprite._to = obj;
42656                     sprite._endStyle = obj;
42657                     sprite._anim = Ext.create('Ext.fx.Anim', {
42658                         target: sprite,
42659                         to: obj,
42660                         duration: 150
42661                     });
42662                 }
42663                 else {
42664                     sprite.setAttributes(obj, true);
42665                 }
42666                 delete sprite._highlighted;
42667                 //delete sprite._defaults;
42668             }
42669         }
42670     },
42671
42672     cleanHighlights: function() {
42673         if (!this.highlight) {
42674             return;
42675         }
42676
42677         var group = this.group,
42678             markerGroup = this.markerGroup,
42679             i = 0,
42680             l;
42681         for (l = group.getCount(); i < l; i++) {
42682             delete group.getAt(i)._defaults;
42683         }
42684         if (markerGroup) {
42685             for (l = markerGroup.getCount(); i < l; i++) {
42686                 delete markerGroup.getAt(i)._defaults;
42687             }
42688         }
42689     }
42690 });
42691 /**
42692  * @class Ext.chart.Label
42693  *
42694  * Labels is a mixin to the Series class. Labels methods are implemented
42695  * in each of the Series (Pie, Bar, etc) for label creation and placement.
42696  *
42697  * The methods implemented by the Series are:
42698  *
42699  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
42700  *   The arguments of the method are:
42701  *   - *`storeItem`* The element of the store that is related to the label sprite.
42702  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
42703  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
42704  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
42705  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
42706  *
42707  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
42708  *    The arguments of the method are:
42709  *    - *`label`* The sprite label.</li>
42710  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
42711  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
42712  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
42713  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
42714  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
42715  *    - *`animate`* A boolean value to set or unset animations for the labels.
42716  */
42717 Ext.define('Ext.chart.Label', {
42718
42719     /* Begin Definitions */
42720
42721     requires: ['Ext.draw.Color'],
42722
42723     /* End Definitions */
42724
42725     /**
42726      * @cfg {Object} label
42727      * Object with the following properties:
42728      *
42729      * - **display** : String
42730      *
42731      *   Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
42732      *   "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
42733      *   Default value: 'none'.
42734      *
42735      * - **color** : String
42736      *
42737      *   The color of the label text.
42738      *   Default value: '#000' (black).
42739      *
42740      * - **contrast** : Boolean
42741      *
42742      *   True to render the label in contrasting color with the backround.
42743      *   Default value: false.
42744      *
42745      * - **field** : String
42746      *
42747      *   The name of the field to be displayed in the label.
42748      *   Default value: 'name'.
42749      *
42750      * - **minMargin** : Number
42751      *
42752      *   Specifies the minimum distance from a label to the origin of the visualization.
42753      *   This parameter is useful when using PieSeries width variable pie slice lengths.
42754      *   Default value: 50.
42755      *
42756      * - **font** : String
42757      *
42758      *   The font used for the labels.
42759      *   Default value: "11px Helvetica, sans-serif".
42760      *
42761      * - **orientation** : String
42762      *
42763      *   Either "horizontal" or "vertical".
42764      *   Dafault value: "horizontal".
42765      *
42766      * - **renderer** : Function
42767      *
42768      *   Optional function for formatting the label into a displayable value.
42769      *   Default value: function(v) { return v; }
42770      */
42771
42772     //@private a regex to parse url type colors.
42773     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
42774
42775     //@private the mixin constructor. Used internally by Series.
42776     constructor: function(config) {
42777         var me = this;
42778         me.label = Ext.applyIf(me.label || {},
42779         {
42780             display: "none",
42781             color: "#000",
42782             field: "name",
42783             minMargin: 50,
42784             font: "11px Helvetica, sans-serif",
42785             orientation: "horizontal",
42786             renderer: function(v) {
42787                 return v;
42788             }
42789         });
42790
42791         if (me.label.display !== 'none') {
42792             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
42793         }
42794     },
42795
42796     //@private a method to render all labels in the labelGroup
42797     renderLabels: function() {
42798         var me = this,
42799             chart = me.chart,
42800             gradients = chart.gradients,
42801             items = me.items,
42802             animate = chart.animate,
42803             config = me.label,
42804             display = config.display,
42805             color = config.color,
42806             field = [].concat(config.field),
42807             group = me.labelsGroup,
42808             groupLength = (group || 0) && group.length,
42809             store = me.chart.store,
42810             len = store.getCount(),
42811             itemLength = (items || 0) && items.length,
42812             ratio = itemLength / len,
42813             gradientsCount = (gradients || 0) && gradients.length,
42814             Color = Ext.draw.Color,
42815             hides = [],
42816             gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
42817             storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
42818
42819         if (display == 'none') {
42820             return;
42821         }
42822         // no items displayed, hide all labels
42823         if(itemLength == 0){
42824             while(groupLength--)
42825                 hides.push(groupLength);
42826         }else{
42827             for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
42828                 index = 0;
42829                 for (j = 0; j < ratio; j++) {
42830                     item = items[count];
42831                     label = group.getAt(groupIndex);
42832                     storeItem = store.getAt(i);
42833                     //check the excludes
42834                     while(this.__excludes && this.__excludes[index] && ratio > 1) {
42835                         if(field[j]){
42836                             hides.push(groupIndex);
42837                         }
42838                         index++;
42839
42840                     }
42841
42842                     if (!item && label) {
42843                         label.hide(true);
42844                         groupIndex++;
42845                     }
42846
42847                     if (item && field[j]) {
42848                         if (!label) {
42849                             label = me.onCreateLabel(storeItem, item, i, display, j, index);
42850                         }
42851                         me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
42852                         groupIndex++;
42853
42854                         //set contrast
42855                         if (config.contrast && item.sprite) {
42856                             sprite = item.sprite;
42857                             //set the color string to the color to be set.
42858                             if (sprite._endStyle) {
42859                                 colorString = sprite._endStyle.fill;
42860                             }
42861                             else if (sprite._to) {
42862                                 colorString = sprite._to.fill;
42863                             }
42864                             else {
42865                                 colorString = sprite.attr.fill;
42866                             }
42867                             colorString = colorString || sprite.attr.fill;
42868
42869                             spriteColor = Color.fromString(colorString);
42870                             //color wasn't parsed property maybe because it's a gradient id
42871                             if (colorString && !spriteColor) {
42872                                 colorString = colorString.match(me.colorStringRe)[1];
42873                                 for (k = 0; k < gradientsCount; k++) {
42874                                     gradient = gradients[k];
42875                                     if (gradient.id == colorString) {
42876                                         //avg color stops
42877                                         colorStop = 0; colorStopTotal = 0;
42878                                         for (colorStopIndex in gradient.stops) {
42879                                             colorStop++;
42880                                             colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
42881                                         }
42882                                         spriteBrightness = (colorStopTotal / colorStop) / 255;
42883                                         break;
42884                                     }
42885                                 }
42886                             }
42887                             else {
42888                                 spriteBrightness = spriteColor.getGrayscale() / 255;
42889                             }
42890                             if (label.isOutside) {
42891                                 spriteBrightness = 1;
42892                             }
42893                             labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
42894                             labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
42895                             label.setAttributes({
42896                                 fill: String(Color.fromHSL.apply({}, labelColor))
42897                             }, true);
42898                         }
42899
42900                     }
42901                     count++;
42902                     index++;
42903                 }
42904             }
42905         }
42906         me.hideLabels(hides);
42907     },
42908     hideLabels: function(hides){
42909         var labelsGroup = this.labelsGroup,
42910             hlen = hides.length;
42911         while(hlen--)
42912             labelsGroup.getAt(hides[hlen]).hide(true);
42913     }
42914 });
42915 Ext.define('Ext.chart.MaskLayer', {
42916     extend: 'Ext.Component',
42917     
42918     constructor: function(config) {
42919         config = Ext.apply(config || {}, {
42920             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
42921         });
42922         this.callParent([config]);    
42923     },
42924     
42925     initComponent: function() {
42926         var me = this;
42927         me.callParent(arguments);
42928         me.addEvents(
42929             'mousedown',
42930             'mouseup',
42931             'mousemove',
42932             'mouseenter',
42933             'mouseleave'
42934         );
42935     },
42936
42937     initDraggable: function() {
42938         this.callParent(arguments);
42939         this.dd.onStart = function (e) {
42940             var me = this,
42941                 comp = me.comp;
42942     
42943             // Cache the start [X, Y] array
42944             this.startPosition = comp.getPosition(true);
42945     
42946             // If client Component has a ghost method to show a lightweight version of itself
42947             // then use that as a drag proxy unless configured to liveDrag.
42948             if (comp.ghost && !comp.liveDrag) {
42949                  me.proxy = comp.ghost();
42950                  me.dragTarget = me.proxy.header.el;
42951             }
42952     
42953             // Set the constrainTo Region before we start dragging.
42954             if (me.constrain || me.constrainDelegate) {
42955                 me.constrainTo = me.calculateConstrainRegion();
42956             }
42957         };
42958     }
42959 });
42960 /**
42961  * @class Ext.chart.TipSurface
42962  * @ignore
42963  */
42964 Ext.define('Ext.chart.TipSurface', {
42965
42966     /* Begin Definitions */
42967
42968     extend: 'Ext.draw.Component',
42969
42970     /* End Definitions */
42971
42972     spriteArray: false,
42973     renderFirst: true,
42974
42975     constructor: function(config) {
42976         this.callParent([config]);
42977         if (config.sprites) {
42978             this.spriteArray = [].concat(config.sprites);
42979             delete config.sprites;
42980         }
42981     },
42982
42983     onRender: function() {
42984         var me = this,
42985             i = 0,
42986             l = 0,
42987             sp,
42988             sprites;
42989             this.callParent(arguments);
42990         sprites = me.spriteArray;
42991         if (me.renderFirst && sprites) {
42992             me.renderFirst = false;
42993             for (l = sprites.length; i < l; i++) {
42994                 sp = me.surface.add(sprites[i]);
42995                 sp.setAttributes({
42996                     hidden: false
42997                 },
42998                 true);
42999             }
43000         }
43001     }
43002 });
43003
43004 /**
43005  * @class Ext.chart.Tip
43006  * Provides tips for Ext.chart.series.Series.
43007  */
43008 Ext.define('Ext.chart.Tip', {
43009
43010     /* Begin Definitions */
43011
43012     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
43013
43014     /* End Definitions */
43015
43016     constructor: function(config) {
43017         var me = this,
43018             surface,
43019             sprites,
43020             tipSurface;
43021         if (config.tips) {
43022             me.tipTimeout = null;
43023             me.tipConfig = Ext.apply({}, config.tips, {
43024                 renderer: Ext.emptyFn,
43025                 constrainPosition: false
43026             });
43027             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
43028             me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
43029             me.chart.surface.on('mouseleave', function() {
43030                 me.hideTip();
43031             });
43032             if (me.tipConfig.surface) {
43033                 //initialize a surface
43034                 surface = me.tipConfig.surface;
43035                 sprites = surface.sprites;
43036                 tipSurface = Ext.create('Ext.chart.TipSurface', {
43037                     id: 'tipSurfaceComponent',
43038                     sprites: sprites
43039                 });
43040                 if (surface.width && surface.height) {
43041                     tipSurface.setSize(surface.width, surface.height);
43042                 }
43043                 me.tooltip.add(tipSurface);
43044                 me.spriteTip = tipSurface;
43045             }
43046         }
43047     },
43048
43049     showTip: function(item) {
43050         var me = this;
43051         if (!me.tooltip) {
43052             return;
43053         }
43054         clearTimeout(me.tipTimeout);
43055         var tooltip = me.tooltip,
43056             spriteTip = me.spriteTip,
43057             tipConfig = me.tipConfig,
43058             trackMouse = tooltip.trackMouse,
43059             sprite, surface, surfaceExt, pos, x, y;
43060         if (!trackMouse) {
43061             tooltip.trackMouse = true;
43062             sprite = item.sprite;
43063             surface = sprite.surface;
43064             surfaceExt = Ext.get(surface.getId());
43065             if (surfaceExt) {
43066                 pos = surfaceExt.getXY();
43067                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
43068                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
43069                 tooltip.targetXY = [x, y];
43070             }
43071         }
43072         if (spriteTip) {
43073             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
43074         } else {
43075             tipConfig.renderer.call(tooltip, item.storeItem, item);
43076         }
43077         tooltip.show();
43078         tooltip.trackMouse = trackMouse;
43079     },
43080
43081     hideTip: function(item) {
43082         var tooltip = this.tooltip;
43083         if (!tooltip) {
43084             return;
43085         }
43086         clearTimeout(this.tipTimeout);
43087         this.tipTimeout = setTimeout(function() {
43088             tooltip.hide();
43089         }, 0);
43090     }
43091 });
43092 /**
43093  * @class Ext.chart.axis.Abstract
43094  * Base class for all axis classes.
43095  * @private
43096  */
43097 Ext.define('Ext.chart.axis.Abstract', {
43098
43099     /* Begin Definitions */
43100
43101     requires: ['Ext.chart.Chart'],
43102
43103     /* End Definitions */
43104
43105     /**
43106      * Creates new Axis.
43107      * @param {Object} config (optional) Config options.
43108      */
43109     constructor: function(config) {
43110         config = config || {};
43111
43112         var me = this,
43113             pos = config.position || 'left';
43114
43115         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
43116         //axisLabel(Top|Bottom|Right|Left)Style
43117         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
43118         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
43119         Ext.apply(me, config);
43120         me.fields = [].concat(me.fields);
43121         this.callParent();
43122         me.labels = [];
43123         me.getId();
43124         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
43125     },
43126
43127     alignment: null,
43128     grid: false,
43129     steps: 10,
43130     x: 0,
43131     y: 0,
43132     minValue: 0,
43133     maxValue: 0,
43134
43135     getId: function() {
43136         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
43137     },
43138
43139     /*
43140       Called to process a view i.e to make aggregation and filtering over
43141       a store creating a substore to be used to render the axis. Since many axes
43142       may do different things on the data and we want the final result of all these
43143       operations to be rendered we need to call processView on all axes before drawing
43144       them.
43145     */
43146     processView: Ext.emptyFn,
43147
43148     drawAxis: Ext.emptyFn,
43149     addDisplayAndLabels: Ext.emptyFn
43150 });
43151
43152 /**
43153  * @class Ext.chart.axis.Axis
43154  * @extends Ext.chart.axis.Abstract
43155  *
43156  * Defines axis for charts. The axis position, type, style can be configured.
43157  * The axes are defined in an axes array of configuration objects where the type,
43158  * field, grid and other configuration options can be set. To know more about how
43159  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
43160  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
43161  *
43162  *     axes: [{
43163  *         type: 'Numeric',
43164  *         grid: true,
43165  *         position: 'left',
43166  *         fields: ['data1', 'data2', 'data3'],
43167  *         title: 'Number of Hits',
43168  *         grid: {
43169  *             odd: {
43170  *                 opacity: 1,
43171  *                 fill: '#ddd',
43172  *                 stroke: '#bbb',
43173  *                 'stroke-width': 1
43174  *             }
43175  *         },
43176  *         minimum: 0
43177  *     }, {
43178  *         type: 'Category',
43179  *         position: 'bottom',
43180  *         fields: ['name'],
43181  *         title: 'Month of the Year',
43182  *         grid: true,
43183  *         label: {
43184  *             rotate: {
43185  *                 degrees: 315
43186  *             }
43187  *         }
43188  *     }]
43189  *
43190  * 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
43191  * 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.
43192  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
43193  * category axis the labels will be rotated so they can fit the space better.
43194  */
43195 Ext.define('Ext.chart.axis.Axis', {
43196
43197     /* Begin Definitions */
43198
43199     extend: 'Ext.chart.axis.Abstract',
43200
43201     alternateClassName: 'Ext.chart.Axis',
43202
43203     requires: ['Ext.draw.Draw'],
43204
43205     /* End Definitions */
43206
43207     /**
43208      * @cfg {Boolean/Object} grid
43209      * The grid configuration enables you to set a background grid for an axis.
43210      * If set to *true* on a vertical axis, vertical lines will be drawn.
43211      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
43212      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
43213      *
43214      * You can set specific options for the grid configuration for odd and/or even lines/rows.
43215      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
43216      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
43217      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
43218      *
43219      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
43220      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
43221      *
43222      * For example:
43223      *
43224      *     axes: [{
43225      *         type: 'Numeric',
43226      *         grid: true,
43227      *         position: 'left',
43228      *         fields: ['data1', 'data2', 'data3'],
43229      *         title: 'Number of Hits',
43230      *         grid: {
43231      *             odd: {
43232      *                 opacity: 1,
43233      *                 fill: '#ddd',
43234      *                 stroke: '#bbb',
43235      *                 'stroke-width': 1
43236      *             }
43237      *         }
43238      *     }, {
43239      *         type: 'Category',
43240      *         position: 'bottom',
43241      *         fields: ['name'],
43242      *         title: 'Month of the Year',
43243      *         grid: true
43244      *     }]
43245      *
43246      */
43247
43248     /**
43249      * @cfg {Number} majorTickSteps
43250      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
43251      */
43252
43253     /**
43254      * @cfg {Number} minorTickSteps
43255      * The number of small ticks between two major ticks. Default is zero.
43256      */
43257
43258     /**
43259      * @cfg {String} title
43260      * The title for the Axis
43261      */
43262
43263     //@private force min/max values from store
43264     forceMinMax: false,
43265
43266     /**
43267      * @cfg {Number} dashSize
43268      * The size of the dash marker. Default's 3.
43269      */
43270     dashSize: 3,
43271
43272     /**
43273      * @cfg {String} position
43274      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
43275      */
43276     position: 'bottom',
43277
43278     // @private
43279     skipFirst: false,
43280
43281     /**
43282      * @cfg {Number} length
43283      * Offset axis position. Default's 0.
43284      */
43285     length: 0,
43286
43287     /**
43288      * @cfg {Number} width
43289      * Offset axis width. Default's 0.
43290      */
43291     width: 0,
43292
43293     majorTickSteps: false,
43294
43295     // @private
43296     applyData: Ext.emptyFn,
43297
43298     getRange: function () {
43299         var me = this,
43300             store = me.chart.getChartStore(),
43301             fields = me.fields,
43302             ln = fields.length,
43303             math = Math,
43304             mmax = math.max,
43305             mmin = math.min,
43306             aggregate = false,
43307             min = isNaN(me.minimum) ? Infinity : me.minimum,
43308             max = isNaN(me.maximum) ? -Infinity : me.maximum,
43309             total = 0, i, l, value, values, rec,
43310             excludes = [],
43311             series = me.chart.series.items;
43312
43313         //if one series is stacked I have to aggregate the values
43314         //for the scale.
43315         // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
43316         // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
43317         // Assuming only stack on y-axis.
43318         // CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since 
43319         // it was constraining the minmax calculation to y-axis stacked
43320         // visualizations.
43321         for (i = 0, l = series.length; !aggregate && i < l; i++) {
43322             aggregate = aggregate || series[i].stacked;
43323             excludes = series[i].__excludes || excludes;
43324         }
43325         store.each(function(record) {
43326             if (aggregate) {
43327                 if (!isFinite(min)) {
43328                     min = 0;
43329                 }
43330                 for (values = [0, 0], i = 0; i < ln; i++) {
43331                     if (excludes[i]) {
43332                         continue;
43333                     }
43334                     rec = record.get(fields[i]);
43335                     values[+(rec > 0)] += math.abs(rec);
43336                 }
43337                 max = mmax(max, -values[0], +values[1]);
43338                 min = mmin(min, -values[0], +values[1]);
43339             }
43340             else {
43341                 for (i = 0; i < ln; i++) {
43342                     if (excludes[i]) {
43343                         continue;
43344                     }
43345                     value = record.get(fields[i]);
43346                     max = mmax(max, +value);
43347                     min = mmin(min, +value);
43348                 }
43349             }
43350         });
43351         if (!isFinite(max)) {
43352             max = me.prevMax || 0;
43353         }
43354         if (!isFinite(min)) {
43355             min = me.prevMin || 0;
43356         }
43357         //normalize min max for snapEnds.
43358         if (min != max && (max != Math.floor(max))) {
43359             max = Math.floor(max) + 1;
43360         }
43361
43362         if (!isNaN(me.minimum)) {
43363             min = me.minimum;
43364         }
43365         
43366         if (!isNaN(me.maximum)) {
43367             max = me.maximum;
43368         }
43369
43370         return {min: min, max: max};
43371     },
43372
43373     // @private creates a structure with start, end and step points.
43374     calcEnds: function() {
43375         var me = this,
43376             fields = me.fields,
43377             range = me.getRange(),
43378             min = range.min,
43379             max = range.max,
43380             outfrom, outto, out;
43381
43382         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
43383         outfrom = out.from;
43384         outto = out.to;
43385         if (me.forceMinMax) {
43386             if (!isNaN(max)) {
43387                 out.to = max;
43388             }
43389             if (!isNaN(min)) {
43390                 out.from = min;
43391             }
43392         }
43393         if (!isNaN(me.maximum)) {
43394             //TODO(nico) users are responsible for their own minimum/maximum values set.
43395             //Clipping should be added to remove lines in the chart which are below the axis.
43396             out.to = me.maximum;
43397         }
43398         if (!isNaN(me.minimum)) {
43399             //TODO(nico) users are responsible for their own minimum/maximum values set.
43400             //Clipping should be added to remove lines in the chart which are below the axis.
43401             out.from = me.minimum;
43402         }
43403
43404         //Adjust after adjusting minimum and maximum
43405         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
43406
43407         if (me.adjustMaximumByMajorUnit) {
43408             out.to += out.step;
43409         }
43410         if (me.adjustMinimumByMajorUnit) {
43411             out.from -= out.step;
43412         }
43413         me.prevMin = min == max? 0 : min;
43414         me.prevMax = max;
43415         return out;
43416     },
43417
43418     /**
43419      * Renders the axis into the screen and updates its position.
43420      */
43421     drawAxis: function (init) {
43422         var me = this,
43423             i, j,
43424             x = me.x,
43425             y = me.y,
43426             gutterX = me.chart.maxGutter[0],
43427             gutterY = me.chart.maxGutter[1],
43428             dashSize = me.dashSize,
43429             subDashesX = me.minorTickSteps || 0,
43430             subDashesY = me.minorTickSteps || 0,
43431             length = me.length,
43432             position = me.position,
43433             inflections = [],
43434             calcLabels = false,
43435             stepCalcs = me.applyData(),
43436             step = stepCalcs.step,
43437             steps = stepCalcs.steps,
43438             from = stepCalcs.from,
43439             to = stepCalcs.to,
43440             trueLength,
43441             currentX,
43442             currentY,
43443             path,
43444             prev,
43445             dashesX,
43446             dashesY,
43447             delta;
43448
43449         //If no steps are specified
43450         //then don't draw the axis. This generally happens
43451         //when an empty store.
43452         if (me.hidden || isNaN(step) || (from == to)) {
43453             return;
43454         }
43455
43456         me.from = stepCalcs.from;
43457         me.to = stepCalcs.to;
43458         if (position == 'left' || position == 'right') {
43459             currentX = Math.floor(x) + 0.5;
43460             path = ["M", currentX, y, "l", 0, -length];
43461             trueLength = length - (gutterY * 2);
43462         }
43463         else {
43464             currentY = Math.floor(y) + 0.5;
43465             path = ["M", x, currentY, "l", length, 0];
43466             trueLength = length - (gutterX * 2);
43467         }
43468
43469         delta = trueLength / (steps || 1);
43470         dashesX = Math.max(subDashesX +1, 0);
43471         dashesY = Math.max(subDashesY +1, 0);
43472         if (me.type == 'Numeric' || me.type == 'Time') {
43473             calcLabels = true;
43474             me.labels = [stepCalcs.from];
43475         }
43476         if (position == 'right' || position == 'left') {
43477             currentY = y - gutterY;
43478             currentX = x - ((position == 'left') * dashSize * 2);
43479             while (currentY >= y - gutterY - trueLength) {
43480                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
43481                 if (currentY != y - gutterY) {
43482                     for (i = 1; i < dashesY; i++) {
43483                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
43484                     }
43485                 }
43486                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
43487                 currentY -= delta;
43488                 if (calcLabels) {
43489                     me.labels.push(me.labels[me.labels.length -1] + step);
43490                 }
43491                 if (delta === 0) {
43492                     break;
43493                 }
43494             }
43495             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
43496                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
43497                 for (i = 1; i < dashesY; i++) {
43498                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
43499                 }
43500                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
43501                 if (calcLabels) {
43502                     me.labels.push(me.labels[me.labels.length -1] + step);
43503                 }
43504             }
43505         } else {
43506             currentX = x + gutterX;
43507             currentY = y - ((position == 'top') * dashSize * 2);
43508             while (currentX <= x + gutterX + trueLength) {
43509                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
43510                 if (currentX != x + gutterX) {
43511                     for (i = 1; i < dashesX; i++) {
43512                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
43513                     }
43514                 }
43515                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
43516                 currentX += delta;
43517                 if (calcLabels) {
43518                     me.labels.push(me.labels[me.labels.length -1] + step);
43519                 }
43520                 if (delta === 0) {
43521                     break;
43522                 }
43523             }
43524             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
43525                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
43526                 for (i = 1; i < dashesX; i++) {
43527                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
43528                 }
43529                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
43530                 if (calcLabels) {
43531                     me.labels.push(me.labels[me.labels.length -1] + step);
43532                 }
43533             }
43534         }
43535         if (!me.axis) {
43536             me.axis = me.chart.surface.add(Ext.apply({
43537                 type: 'path',
43538                 path: path
43539             }, me.axisStyle));
43540         }
43541         me.axis.setAttributes({
43542             path: path
43543         }, true);
43544         me.inflections = inflections;
43545         if (!init && me.grid) {
43546             me.drawGrid();
43547         }
43548         me.axisBBox = me.axis.getBBox();
43549         me.drawLabel();
43550     },
43551
43552     /**
43553      * Renders an horizontal and/or vertical grid into the Surface.
43554      */
43555     drawGrid: function() {
43556         var me = this,
43557             surface = me.chart.surface,
43558             grid = me.grid,
43559             odd = grid.odd,
43560             even = grid.even,
43561             inflections = me.inflections,
43562             ln = inflections.length - ((odd || even)? 0 : 1),
43563             position = me.position,
43564             gutter = me.chart.maxGutter,
43565             width = me.width - 2,
43566             vert = false,
43567             point, prevPoint,
43568             i = 1,
43569             path = [], styles, lineWidth, dlineWidth,
43570             oddPath = [], evenPath = [];
43571
43572         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
43573             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
43574             i = 0;
43575             ln++;
43576         }
43577         for (; i < ln; i++) {
43578             point = inflections[i];
43579             prevPoint = inflections[i - 1];
43580             if (odd || even) {
43581                 path = (i % 2)? oddPath : evenPath;
43582                 styles = ((i % 2)? odd : even) || {};
43583                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
43584                 dlineWidth = 2 * lineWidth;
43585                 if (position == 'left') {
43586                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
43587                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
43588                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
43589                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
43590                 }
43591                 else if (position == 'right') {
43592                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
43593                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
43594                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
43595                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
43596                 }
43597                 else if (position == 'top') {
43598                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
43599                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
43600                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
43601                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
43602                 }
43603                 else {
43604                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
43605                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
43606                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
43607                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
43608                 }
43609             } else {
43610                 if (position == 'left') {
43611                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
43612                 }
43613                 else if (position == 'right') {
43614                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
43615                 }
43616                 else if (position == 'top') {
43617                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
43618                 }
43619                 else {
43620                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
43621                 }
43622             }
43623         }
43624         if (odd || even) {
43625             if (oddPath.length) {
43626                 if (!me.gridOdd && oddPath.length) {
43627                     me.gridOdd = surface.add({
43628                         type: 'path',
43629                         path: oddPath
43630                     });
43631                 }
43632                 me.gridOdd.setAttributes(Ext.apply({
43633                     path: oddPath,
43634                     hidden: false
43635                 }, odd || {}), true);
43636             }
43637             if (evenPath.length) {
43638                 if (!me.gridEven) {
43639                     me.gridEven = surface.add({
43640                         type: 'path',
43641                         path: evenPath
43642                     });
43643                 }
43644                 me.gridEven.setAttributes(Ext.apply({
43645                     path: evenPath,
43646                     hidden: false
43647                 }, even || {}), true);
43648             }
43649         }
43650         else {
43651             if (path.length) {
43652                 if (!me.gridLines) {
43653                     me.gridLines = me.chart.surface.add({
43654                         type: 'path',
43655                         path: path,
43656                         "stroke-width": me.lineWidth || 1,
43657                         stroke: me.gridColor || '#ccc'
43658                     });
43659                 }
43660                 me.gridLines.setAttributes({
43661                     hidden: false,
43662                     path: path
43663                 }, true);
43664             }
43665             else if (me.gridLines) {
43666                 me.gridLines.hide(true);
43667             }
43668         }
43669     },
43670
43671     //@private
43672     getOrCreateLabel: function(i, text) {
43673         var me = this,
43674             labelGroup = me.labelGroup,
43675             textLabel = labelGroup.getAt(i),
43676             surface = me.chart.surface;
43677         if (textLabel) {
43678             if (text != textLabel.attr.text) {
43679                 textLabel.setAttributes(Ext.apply({
43680                     text: text
43681                 }, me.label), true);
43682                 textLabel._bbox = textLabel.getBBox();
43683             }
43684         }
43685         else {
43686             textLabel = surface.add(Ext.apply({
43687                 group: labelGroup,
43688                 type: 'text',
43689                 x: 0,
43690                 y: 0,
43691                 text: text
43692             }, me.label));
43693             surface.renderItem(textLabel);
43694             textLabel._bbox = textLabel.getBBox();
43695         }
43696         //get untransformed bounding box
43697         if (me.label.rotation) {
43698             textLabel.setAttributes({
43699                 rotation: {
43700                     degrees: 0
43701                 }
43702             }, true);
43703             textLabel._ubbox = textLabel.getBBox();
43704             textLabel.setAttributes(me.label, true);
43705         } else {
43706             textLabel._ubbox = textLabel._bbox;
43707         }
43708         return textLabel;
43709     },
43710
43711     rect2pointArray: function(sprite) {
43712         var surface = this.chart.surface,
43713             rect = surface.getBBox(sprite, true),
43714             p1 = [rect.x, rect.y],
43715             p1p = p1.slice(),
43716             p2 = [rect.x + rect.width, rect.y],
43717             p2p = p2.slice(),
43718             p3 = [rect.x + rect.width, rect.y + rect.height],
43719             p3p = p3.slice(),
43720             p4 = [rect.x, rect.y + rect.height],
43721             p4p = p4.slice(),
43722             matrix = sprite.matrix;
43723         //transform the points
43724         p1[0] = matrix.x.apply(matrix, p1p);
43725         p1[1] = matrix.y.apply(matrix, p1p);
43726
43727         p2[0] = matrix.x.apply(matrix, p2p);
43728         p2[1] = matrix.y.apply(matrix, p2p);
43729
43730         p3[0] = matrix.x.apply(matrix, p3p);
43731         p3[1] = matrix.y.apply(matrix, p3p);
43732
43733         p4[0] = matrix.x.apply(matrix, p4p);
43734         p4[1] = matrix.y.apply(matrix, p4p);
43735         return [p1, p2, p3, p4];
43736     },
43737
43738     intersect: function(l1, l2) {
43739         var r1 = this.rect2pointArray(l1),
43740             r2 = this.rect2pointArray(l2);
43741         return !!Ext.draw.Draw.intersect(r1, r2).length;
43742     },
43743
43744     drawHorizontalLabels: function() {
43745        var  me = this,
43746             labelConf = me.label,
43747             floor = Math.floor,
43748             max = Math.max,
43749             axes = me.chart.axes,
43750             position = me.position,
43751             inflections = me.inflections,
43752             ln = inflections.length,
43753             labels = me.labels,
43754             labelGroup = me.labelGroup,
43755             maxHeight = 0,
43756             ratio,
43757             gutterY = me.chart.maxGutter[1],
43758             ubbox, bbox, point, prevX, prevLabel,
43759             projectedWidth = 0,
43760             textLabel, attr, textRight, text,
43761             label, last, x, y, i, firstLabel;
43762
43763         last = ln - 1;
43764         //get a reference to the first text label dimensions
43765         point = inflections[0];
43766         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
43767         ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
43768
43769         for (i = 0; i < ln; i++) {
43770             point = inflections[i];
43771             text = me.label.renderer(labels[i]);
43772             textLabel = me.getOrCreateLabel(i, text);
43773             bbox = textLabel._bbox;
43774             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
43775             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
43776             if (me.chart.maxGutter[0] == 0) {
43777                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
43778                     x = point[0];
43779                 }
43780                 else if (i == last && axes.findIndex('position', 'right') == -1) {
43781                     x = point[0] - bbox.width;
43782                 }
43783             }
43784             if (position == 'top') {
43785                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
43786             }
43787             else {
43788                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
43789             }
43790
43791             textLabel.setAttributes({
43792                 hidden: false,
43793                 x: x,
43794                 y: y
43795             }, true);
43796
43797             // Skip label if there isn't available minimum space
43798             if (i != 0 && (me.intersect(textLabel, prevLabel)
43799                 || me.intersect(textLabel, firstLabel))) {
43800                 textLabel.hide(true);
43801                 continue;
43802             }
43803
43804             prevLabel = textLabel;
43805         }
43806
43807         return maxHeight;
43808     },
43809
43810     drawVerticalLabels: function() {
43811         var me = this,
43812             inflections = me.inflections,
43813             position = me.position,
43814             ln = inflections.length,
43815             labels = me.labels,
43816             maxWidth = 0,
43817             max = Math.max,
43818             floor = Math.floor,
43819             ceil = Math.ceil,
43820             axes = me.chart.axes,
43821             gutterY = me.chart.maxGutter[1],
43822             ubbox, bbox, point, prevLabel,
43823             projectedWidth = 0,
43824             textLabel, attr, textRight, text,
43825             label, last, x, y, i;
43826
43827         last = ln;
43828         for (i = 0; i < last; i++) {
43829             point = inflections[i];
43830             text = me.label.renderer(labels[i]);
43831             textLabel = me.getOrCreateLabel(i, text);
43832             bbox = textLabel._bbox;
43833
43834             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
43835             y = point[1];
43836             if (gutterY < bbox.height / 2) {
43837                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
43838                     y = me.y - me.length + ceil(bbox.height / 2);
43839                 }
43840                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
43841                     y = me.y - floor(bbox.height / 2);
43842                 }
43843             }
43844             if (position == 'left') {
43845                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
43846             }
43847             else {
43848                 x = point[0] + me.dashSize + me.label.padding + 2;
43849             }
43850             textLabel.setAttributes(Ext.apply({
43851                 hidden: false,
43852                 x: x,
43853                 y: y
43854             }, me.label), true);
43855             // Skip label if there isn't available minimum space
43856             if (i != 0 && me.intersect(textLabel, prevLabel)) {
43857                 textLabel.hide(true);
43858                 continue;
43859             }
43860             prevLabel = textLabel;
43861         }
43862
43863         return maxWidth;
43864     },
43865
43866     /**
43867      * Renders the labels in the axes.
43868      */
43869     drawLabel: function() {
43870         var me = this,
43871             position = me.position,
43872             labelGroup = me.labelGroup,
43873             inflections = me.inflections,
43874             maxWidth = 0,
43875             maxHeight = 0,
43876             ln, i;
43877
43878         if (position == 'left' || position == 'right') {
43879             maxWidth = me.drawVerticalLabels();
43880         } else {
43881             maxHeight = me.drawHorizontalLabels();
43882         }
43883
43884         // Hide unused bars
43885         ln = labelGroup.getCount();
43886         i = inflections.length;
43887         for (; i < ln; i++) {
43888             labelGroup.getAt(i).hide(true);
43889         }
43890
43891         me.bbox = {};
43892         Ext.apply(me.bbox, me.axisBBox);
43893         me.bbox.height = maxHeight;
43894         me.bbox.width = maxWidth;
43895         if (Ext.isString(me.title)) {
43896             me.drawTitle(maxWidth, maxHeight);
43897         }
43898     },
43899
43900     // @private creates the elipsis for the text.
43901     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
43902         var bbox,
43903             x;
43904
43905         if (desiredWidth < minWidth) {
43906             sprite.hide(true);
43907             return false;
43908         }
43909         while (text.length > 4) {
43910             text = text.substr(0, text.length - 4) + "...";
43911             sprite.setAttributes({
43912                 text: text
43913             }, true);
43914             bbox = sprite.getBBox();
43915             if (bbox.width < desiredWidth) {
43916                 if (typeof center == 'number') {
43917                     sprite.setAttributes({
43918                         x: Math.floor(center - (bbox.width / 2))
43919                     }, true);
43920                 }
43921                 break;
43922             }
43923         }
43924         return true;
43925     },
43926
43927     /**
43928      * Updates the {@link #title} of this axis.
43929      * @param {String} title
43930      */
43931     setTitle: function(title) {
43932         this.title = title;
43933         this.drawLabel();
43934     },
43935
43936     // @private draws the title for the axis.
43937     drawTitle: function(maxWidth, maxHeight) {
43938         var me = this,
43939             position = me.position,
43940             surface = me.chart.surface,
43941             displaySprite = me.displaySprite,
43942             title = me.title,
43943             rotate = (position == 'left' || position == 'right'),
43944             x = me.x,
43945             y = me.y,
43946             base, bbox, pad;
43947
43948         if (displaySprite) {
43949             displaySprite.setAttributes({text: title}, true);
43950         } else {
43951             base = {
43952                 type: 'text',
43953                 x: 0,
43954                 y: 0,
43955                 text: title
43956             };
43957             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
43958             surface.renderItem(displaySprite);
43959         }
43960         bbox = displaySprite.getBBox();
43961         pad = me.dashSize + me.label.padding;
43962
43963         if (rotate) {
43964             y -= ((me.length / 2) - (bbox.height / 2));
43965             if (position == 'left') {
43966                 x -= (maxWidth + pad + (bbox.width / 2));
43967             }
43968             else {
43969                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
43970             }
43971             me.bbox.width += bbox.width + 10;
43972         }
43973         else {
43974             x += (me.length / 2) - (bbox.width * 0.5);
43975             if (position == 'top') {
43976                 y -= (maxHeight + pad + (bbox.height * 0.3));
43977             }
43978             else {
43979                 y += (maxHeight + pad + (bbox.height * 0.8));
43980             }
43981             me.bbox.height += bbox.height + 10;
43982         }
43983         displaySprite.setAttributes({
43984             translate: {
43985                 x: x,
43986                 y: y
43987             }
43988         }, true);
43989     }
43990 });
43991
43992 /**
43993  * @class Ext.chart.axis.Category
43994  * @extends Ext.chart.axis.Axis
43995  *
43996  * A type of axis that displays items in categories. This axis is generally used to
43997  * display categorical information like names of items, month names, quarters, etc.
43998  * but no quantitative values. For that other type of information `Number`
43999  * axis are more suitable.
44000  *
44001  * As with other axis you can set the position of the axis and its title. For example:
44002  *
44003  *     @example
44004  *     var store = Ext.create('Ext.data.JsonStore', {
44005  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
44006  *         data: [
44007  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
44008  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
44009  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
44010  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
44011  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
44012  *         ]
44013  *     });
44014  *
44015  *     Ext.create('Ext.chart.Chart', {
44016  *         renderTo: Ext.getBody(),
44017  *         width: 500,
44018  *         height: 300,
44019  *         store: store,
44020  *         axes: [{
44021  *             type: 'Numeric',
44022  *             grid: true,
44023  *             position: 'left',
44024  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
44025  *             title: 'Sample Values',
44026  *             grid: {
44027  *                 odd: {
44028  *                     opacity: 1,
44029  *                     fill: '#ddd',
44030  *                     stroke: '#bbb',
44031  *                     'stroke-width': 1
44032  *                 }
44033  *             },
44034  *             minimum: 0,
44035  *             adjustMinimumByMajorUnit: 0
44036  *         }, {
44037  *             type: 'Category',
44038  *             position: 'bottom',
44039  *             fields: ['name'],
44040  *             title: 'Sample Metrics',
44041  *             grid: true,
44042  *             label: {
44043  *                 rotate: {
44044  *                     degrees: 315
44045  *                 }
44046  *             }
44047  *         }],
44048  *         series: [{
44049  *             type: 'area',
44050  *             highlight: false,
44051  *             axis: 'left',
44052  *             xField: 'name',
44053  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
44054  *             style: {
44055  *                 opacity: 0.93
44056  *             }
44057  *         }]
44058  *     });
44059  *
44060  * In this example with set the category axis to the bottom of the surface, bound the axis to
44061  * the `name` property and set as title _Month of the Year_.
44062  */
44063 Ext.define('Ext.chart.axis.Category', {
44064
44065     /* Begin Definitions */
44066
44067     extend: 'Ext.chart.axis.Axis',
44068
44069     alternateClassName: 'Ext.chart.CategoryAxis',
44070
44071     alias: 'axis.category',
44072
44073     /* End Definitions */
44074
44075     /**
44076      * A list of category names to display along this axis.
44077      * @property {String} categoryNames
44078      */
44079     categoryNames: null,
44080
44081     /**
44082      * Indicates whether or not to calculate the number of categories (ticks and
44083      * labels) when there is not enough room to display all labels on the axis.
44084      * If set to true, the axis will determine the number of categories to plot.
44085      * If not, all categories will be plotted.
44086      *
44087      * @property calculateCategoryCount
44088      * @type Boolean
44089      */
44090     calculateCategoryCount: false,
44091
44092     // @private creates an array of labels to be used when rendering.
44093     setLabels: function() {
44094         var store = this.chart.store,
44095             fields = this.fields,
44096             ln = fields.length,
44097             i;
44098
44099         this.labels = [];
44100         store.each(function(record) {
44101             for (i = 0; i < ln; i++) {
44102                 this.labels.push(record.get(fields[i]));
44103             }
44104         }, this);
44105     },
44106
44107     // @private calculates labels positions and marker positions for rendering.
44108     applyData: function() {
44109         this.callParent();
44110         this.setLabels();
44111         var count = this.chart.store.getCount();
44112         return {
44113             from: 0,
44114             to: count,
44115             power: 1,
44116             step: 1,
44117             steps: count - 1
44118         };
44119     }
44120 });
44121
44122 /**
44123  * @class Ext.chart.axis.Gauge
44124  * @extends Ext.chart.axis.Abstract
44125  *
44126  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
44127  * displays numeric data from an interval defined by the `minimum`, `maximum` and
44128  * `step` configuration properties. The placement of the numeric data can be changed
44129  * by altering the `margin` option that is set to `10` by default.
44130  *
44131  * A possible configuration for this axis would look like:
44132  *
44133  *     axes: [{
44134  *         type: 'gauge',
44135  *         position: 'gauge',
44136  *         minimum: 0,
44137  *         maximum: 100,
44138  *         steps: 10,
44139  *         margin: 7
44140  *     }],
44141  */
44142 Ext.define('Ext.chart.axis.Gauge', {
44143
44144     /* Begin Definitions */
44145
44146     extend: 'Ext.chart.axis.Abstract',
44147
44148     /* End Definitions */
44149
44150     /**
44151      * @cfg {Number} minimum (required)
44152      * The minimum value of the interval to be displayed in the axis.
44153      */
44154
44155     /**
44156      * @cfg {Number} maximum (required)
44157      * The maximum value of the interval to be displayed in the axis.
44158      */
44159
44160     /**
44161      * @cfg {Number} steps (required)
44162      * The number of steps and tick marks to add to the interval.
44163      */
44164
44165     /**
44166      * @cfg {Number} [margin=10]
44167      * The offset positioning of the tick marks and labels in pixels.
44168      */
44169
44170     /**
44171      * @cfg {String} title
44172      * The title for the Axis.
44173      */
44174
44175     position: 'gauge',
44176
44177     alias: 'axis.gauge',
44178
44179     drawAxis: function(init) {
44180         var chart = this.chart,
44181             surface = chart.surface,
44182             bbox = chart.chartBBox,
44183             centerX = bbox.x + (bbox.width / 2),
44184             centerY = bbox.y + bbox.height,
44185             margin = this.margin || 10,
44186             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
44187             sprites = [], sprite,
44188             steps = this.steps,
44189             i, pi = Math.PI,
44190             cos = Math.cos,
44191             sin = Math.sin;
44192
44193         if (this.sprites && !chart.resizing) {
44194             this.drawLabel();
44195             return;
44196         }
44197
44198         if (this.margin >= 0) {
44199             if (!this.sprites) {
44200                 //draw circles
44201                 for (i = 0; i <= steps; i++) {
44202                     sprite = surface.add({
44203                         type: 'path',
44204                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
44205                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
44206                                     'L', centerX + rho * cos(i / steps * pi - pi),
44207                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
44208                         stroke: '#ccc'
44209                     });
44210                     sprite.setAttributes({
44211                         hidden: false
44212                     }, true);
44213                     sprites.push(sprite);
44214                 }
44215             } else {
44216                 sprites = this.sprites;
44217                 //draw circles
44218                 for (i = 0; i <= steps; i++) {
44219                     sprites[i].setAttributes({
44220                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
44221                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
44222                                'L', centerX + rho * cos(i / steps * pi - pi),
44223                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
44224                         stroke: '#ccc'
44225                     }, true);
44226                 }
44227             }
44228         }
44229         this.sprites = sprites;
44230         this.drawLabel();
44231         if (this.title) {
44232             this.drawTitle();
44233         }
44234     },
44235
44236     drawTitle: function() {
44237         var me = this,
44238             chart = me.chart,
44239             surface = chart.surface,
44240             bbox = chart.chartBBox,
44241             labelSprite = me.titleSprite,
44242             labelBBox;
44243
44244         if (!labelSprite) {
44245             me.titleSprite = labelSprite = surface.add({
44246                 type: 'text',
44247                 zIndex: 2
44248             });
44249         }
44250         labelSprite.setAttributes(Ext.apply({
44251             text: me.title
44252         }, me.label || {}), true);
44253         labelBBox = labelSprite.getBBox();
44254         labelSprite.setAttributes({
44255             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
44256             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
44257         }, true);
44258     },
44259
44260     /**
44261      * Updates the {@link #title} of this axis.
44262      * @param {String} title
44263      */
44264     setTitle: function(title) {
44265         this.title = title;
44266         this.drawTitle();
44267     },
44268
44269     drawLabel: function() {
44270         var chart = this.chart,
44271             surface = chart.surface,
44272             bbox = chart.chartBBox,
44273             centerX = bbox.x + (bbox.width / 2),
44274             centerY = bbox.y + bbox.height,
44275             margin = this.margin || 10,
44276             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
44277             round = Math.round,
44278             labelArray = [], label,
44279             maxValue = this.maximum || 0,
44280             steps = this.steps, i = 0,
44281             adjY,
44282             pi = Math.PI,
44283             cos = Math.cos,
44284             sin = Math.sin,
44285             labelConf = this.label,
44286             renderer = labelConf.renderer || function(v) { return v; };
44287
44288         if (!this.labelArray) {
44289             //draw scale
44290             for (i = 0; i <= steps; i++) {
44291                 // TODO Adjust for height of text / 2 instead
44292                 adjY = (i === 0 || i === steps) ? 7 : 0;
44293                 label = surface.add({
44294                     type: 'text',
44295                     text: renderer(round(i / steps * maxValue)),
44296                     x: centerX + rho * cos(i / steps * pi - pi),
44297                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
44298                     'text-anchor': 'middle',
44299                     'stroke-width': 0.2,
44300                     zIndex: 10,
44301                     stroke: '#333'
44302                 });
44303                 label.setAttributes({
44304                     hidden: false
44305                 }, true);
44306                 labelArray.push(label);
44307             }
44308         }
44309         else {
44310             labelArray = this.labelArray;
44311             //draw values
44312             for (i = 0; i <= steps; i++) {
44313                 // TODO Adjust for height of text / 2 instead
44314                 adjY = (i === 0 || i === steps) ? 7 : 0;
44315                 labelArray[i].setAttributes({
44316                     text: renderer(round(i / steps * maxValue)),
44317                     x: centerX + rho * cos(i / steps * pi - pi),
44318                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
44319                 }, true);
44320             }
44321         }
44322         this.labelArray = labelArray;
44323     }
44324 });
44325 /**
44326  * @class Ext.chart.axis.Numeric
44327  * @extends Ext.chart.axis.Axis
44328  *
44329  * An axis to handle numeric values. This axis is used for quantitative data as
44330  * opposed to the category axis. You can set mininum and maximum values to the
44331  * axis so that the values are bound to that. If no values are set, then the
44332  * scale will auto-adjust to the values.
44333  *
44334  *     @example
44335  *     var store = Ext.create('Ext.data.JsonStore', {
44336  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
44337  *          data: [
44338  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
44339  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
44340  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
44341  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
44342  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
44343  *          ]
44344  *     });
44345  *
44346  *     Ext.create('Ext.chart.Chart', {
44347  *         renderTo: Ext.getBody(),
44348  *         width: 500,
44349  *         height: 300,
44350  *         store: store,
44351  *         axes: [{
44352  *             type: 'Numeric',
44353  *             grid: true,
44354  *             position: 'left',
44355  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
44356  *             title: 'Sample Values',
44357  *             grid: {
44358  *                 odd: {
44359  *                     opacity: 1,
44360  *                     fill: '#ddd',
44361  *                     stroke: '#bbb',
44362  *                     'stroke-width': 1
44363  *                 }
44364  *             },
44365  *             minimum: 0,
44366  *             adjustMinimumByMajorUnit: 0
44367  *         }, {
44368  *             type: 'Category',
44369  *             position: 'bottom',
44370  *             fields: ['name'],
44371  *             title: 'Sample Metrics',
44372  *             grid: true,
44373  *             label: {
44374  *                 rotate: {
44375  *                     degrees: 315
44376  *                 }
44377  *             }
44378  *         }],
44379  *         series: [{
44380  *             type: 'area',
44381  *             highlight: false,
44382  *             axis: 'left',
44383  *             xField: 'name',
44384  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
44385  *             style: {
44386  *                 opacity: 0.93
44387  *             }
44388  *         }]
44389  *     });
44390  *
44391  * In this example we create an axis of Numeric type. We set a minimum value so that
44392  * even if all series have values greater than zero, the grid starts at zero. We bind
44393  * the axis onto the left part of the surface by setting `position` to `left`.
44394  * We bind three different store fields to this axis by setting `fields` to an array.
44395  * We set the title of the axis to _Number of Hits_ by using the `title` property.
44396  * We use a `grid` configuration to set odd background rows to a certain style and even rows
44397  * to be transparent/ignored.
44398  */
44399 Ext.define('Ext.chart.axis.Numeric', {
44400
44401     /* Begin Definitions */
44402
44403     extend: 'Ext.chart.axis.Axis',
44404
44405     alternateClassName: 'Ext.chart.NumericAxis',
44406
44407     /* End Definitions */
44408
44409     type: 'numeric',
44410
44411     alias: 'axis.numeric',
44412
44413     constructor: function(config) {
44414         var me = this,
44415             hasLabel = !!(config.label && config.label.renderer),
44416             label;
44417
44418         me.callParent([config]);
44419         label = me.label;
44420         if (me.roundToDecimal === false) {
44421             return;
44422         }
44423         if (!hasLabel) {
44424             label.renderer = function(v) {
44425                 return me.roundToDecimal(v, me.decimals);
44426             };
44427         }
44428     },
44429
44430     roundToDecimal: function(v, dec) {
44431         var val = Math.pow(10, dec || 0);
44432         return Math.floor(v * val) / val;
44433     },
44434
44435     /**
44436      * The minimum value drawn by the axis. If not set explicitly, the axis
44437      * minimum will be calculated automatically.
44438      *
44439      * @property {Number} minimum
44440      */
44441     minimum: NaN,
44442
44443     /**
44444      * The maximum value drawn by the axis. If not set explicitly, the axis
44445      * maximum will be calculated automatically.
44446      *
44447      * @property {Number} maximum
44448      */
44449     maximum: NaN,
44450
44451     /**
44452      * The number of decimals to round the value to.
44453      *
44454      * @property {Number} decimals
44455      */
44456     decimals: 2,
44457
44458     /**
44459      * The scaling algorithm to use on this axis. May be "linear" or
44460      * "logarithmic".  Currently only linear scale is implemented.
44461      *
44462      * @property {String} scale
44463      * @private
44464      */
44465     scale: "linear",
44466
44467     /**
44468      * Indicates the position of the axis relative to the chart
44469      *
44470      * @property {String} position
44471      */
44472     position: 'left',
44473
44474     /**
44475      * Indicates whether to extend maximum beyond data's maximum to the nearest
44476      * majorUnit.
44477      *
44478      * @property {Boolean} adjustMaximumByMajorUnit
44479      */
44480     adjustMaximumByMajorUnit: false,
44481
44482     /**
44483      * Indicates whether to extend the minimum beyond data's minimum to the
44484      * nearest majorUnit.
44485      *
44486      * @property {Boolean} adjustMinimumByMajorUnit
44487      */
44488     adjustMinimumByMajorUnit: false,
44489
44490     // @private apply data.
44491     applyData: function() {
44492         this.callParent();
44493         return this.calcEnds();
44494     }
44495 });
44496
44497 /**
44498  * @class Ext.chart.axis.Radial
44499  * @extends Ext.chart.axis.Abstract
44500  * @ignore
44501  */
44502 Ext.define('Ext.chart.axis.Radial', {
44503
44504     /* Begin Definitions */
44505
44506     extend: 'Ext.chart.axis.Abstract',
44507
44508     /* End Definitions */
44509
44510     position: 'radial',
44511
44512     alias: 'axis.radial',
44513
44514     drawAxis: function(init) {
44515         var chart = this.chart,
44516             surface = chart.surface,
44517             bbox = chart.chartBBox,
44518             store = chart.store,
44519             l = store.getCount(),
44520             centerX = bbox.x + (bbox.width / 2),
44521             centerY = bbox.y + (bbox.height / 2),
44522             rho = Math.min(bbox.width, bbox.height) /2,
44523             sprites = [], sprite,
44524             steps = this.steps,
44525             i, j, pi2 = Math.PI * 2,
44526             cos = Math.cos, sin = Math.sin;
44527
44528         if (this.sprites && !chart.resizing) {
44529             this.drawLabel();
44530             return;
44531         }
44532
44533         if (!this.sprites) {
44534             //draw circles
44535             for (i = 1; i <= steps; i++) {
44536                 sprite = surface.add({
44537                     type: 'circle',
44538                     x: centerX,
44539                     y: centerY,
44540                     radius: Math.max(rho * i / steps, 0),
44541                     stroke: '#ccc'
44542                 });
44543                 sprite.setAttributes({
44544                     hidden: false
44545                 }, true);
44546                 sprites.push(sprite);
44547             }
44548             //draw lines
44549             store.each(function(rec, i) {
44550                 sprite = surface.add({
44551                     type: 'path',
44552                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
44553                     stroke: '#ccc'
44554                 });
44555                 sprite.setAttributes({
44556                     hidden: false
44557                 }, true);
44558                 sprites.push(sprite);
44559             });
44560         } else {
44561             sprites = this.sprites;
44562             //draw circles
44563             for (i = 0; i < steps; i++) {
44564                 sprites[i].setAttributes({
44565                     x: centerX,
44566                     y: centerY,
44567                     radius: Math.max(rho * (i + 1) / steps, 0),
44568                     stroke: '#ccc'
44569                 }, true);
44570             }
44571             //draw lines
44572             store.each(function(rec, j) {
44573                 sprites[i + j].setAttributes({
44574                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
44575                     stroke: '#ccc'
44576                 }, true);
44577             });
44578         }
44579         this.sprites = sprites;
44580
44581         this.drawLabel();
44582     },
44583
44584     drawLabel: function() {
44585         var chart = this.chart,
44586             surface = chart.surface,
44587             bbox = chart.chartBBox,
44588             store = chart.store,
44589             centerX = bbox.x + (bbox.width / 2),
44590             centerY = bbox.y + (bbox.height / 2),
44591             rho = Math.min(bbox.width, bbox.height) /2,
44592             max = Math.max, round = Math.round,
44593             labelArray = [], label,
44594             fields = [], nfields,
44595             categories = [], xField,
44596             aggregate = !this.maximum,
44597             maxValue = this.maximum || 0,
44598             steps = this.steps, i = 0, j, dx, dy,
44599             pi2 = Math.PI * 2,
44600             cos = Math.cos, sin = Math.sin,
44601             display = this.label.display,
44602             draw = display !== 'none',
44603             margin = 10;
44604
44605         if (!draw) {
44606             return;
44607         }
44608
44609         //get all rendered fields
44610         chart.series.each(function(series) {
44611             fields.push(series.yField);
44612             xField = series.xField;
44613         });
44614         
44615         //get maxValue to interpolate
44616         store.each(function(record, i) {
44617             if (aggregate) {
44618                 for (i = 0, nfields = fields.length; i < nfields; i++) {
44619                     maxValue = max(+record.get(fields[i]), maxValue);
44620                 }
44621             }
44622             categories.push(record.get(xField));
44623         });
44624         if (!this.labelArray) {
44625             if (display != 'categories') {
44626                 //draw scale
44627                 for (i = 1; i <= steps; i++) {
44628                     label = surface.add({
44629                         type: 'text',
44630                         text: round(i / steps * maxValue),
44631                         x: centerX,
44632                         y: centerY - rho * i / steps,
44633                         'text-anchor': 'middle',
44634                         'stroke-width': 0.1,
44635                         stroke: '#333'
44636                     });
44637                     label.setAttributes({
44638                         hidden: false
44639                     }, true);
44640                     labelArray.push(label);
44641                 }
44642             }
44643             if (display != 'scale') {
44644                 //draw text
44645                 for (j = 0, steps = categories.length; j < steps; j++) {
44646                     dx = cos(j / steps * pi2) * (rho + margin);
44647                     dy = sin(j / steps * pi2) * (rho + margin);
44648                     label = surface.add({
44649                         type: 'text',
44650                         text: categories[j],
44651                         x: centerX + dx,
44652                         y: centerY + dy,
44653                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
44654                     });
44655                     label.setAttributes({
44656                         hidden: false
44657                     }, true);
44658                     labelArray.push(label);
44659                 }
44660             }
44661         }
44662         else {
44663             labelArray = this.labelArray;
44664             if (display != 'categories') {
44665                 //draw values
44666                 for (i = 0; i < steps; i++) {
44667                     labelArray[i].setAttributes({
44668                         text: round((i + 1) / steps * maxValue),
44669                         x: centerX,
44670                         y: centerY - rho * (i + 1) / steps,
44671                         'text-anchor': 'middle',
44672                         'stroke-width': 0.1,
44673                         stroke: '#333'
44674                     }, true);
44675                 }
44676             }
44677             if (display != 'scale') {
44678                 //draw text
44679                 for (j = 0, steps = categories.length; j < steps; j++) {
44680                     dx = cos(j / steps * pi2) * (rho + margin);
44681                     dy = sin(j / steps * pi2) * (rho + margin);
44682                     if (labelArray[i + j]) {
44683                         labelArray[i + j].setAttributes({
44684                             type: 'text',
44685                             text: categories[j],
44686                             x: centerX + dx,
44687                             y: centerY + dy,
44688                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
44689                         }, true);
44690                     }
44691                 }
44692             }
44693         }
44694         this.labelArray = labelArray;
44695     }
44696 });
44697 /**
44698  * @author Ed Spencer
44699  *
44700  * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
44701  * but offers a set of methods used by both of those subclasses.
44702  * 
44703  * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
44704  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
44705  * AbstractStore is and is not.
44706  * 
44707  * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
44708  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
44709  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
44710  * 
44711  * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
44712  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
44713  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
44714  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
44715  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
44716  * 
44717  * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
44718  * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
44719  * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
44720  * {@link Ext.data.Store#filter filter} methods.
44721  */
44722 Ext.define('Ext.data.AbstractStore', {
44723     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
44724     
44725     mixins: {
44726         observable: 'Ext.util.Observable',
44727         sortable: 'Ext.util.Sortable'
44728     },
44729     
44730     statics: {
44731         create: function(store){
44732             if (!store.isStore) {
44733                 if (!store.type) {
44734                     store.type = 'store';
44735                 }
44736                 store = Ext.createByAlias('store.' + store.type, store);
44737             }
44738             return store;
44739         }    
44740     },
44741     
44742     remoteSort  : false,
44743     remoteFilter: false,
44744
44745     /**
44746      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
44747      * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
44748      * see {@link #setProxy} for details.
44749      */
44750
44751     /**
44752      * @cfg {Boolean/Object} autoLoad
44753      * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
44754      * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
44755      * Defaults to false.
44756      */
44757     autoLoad: false,
44758
44759     /**
44760      * @cfg {Boolean} autoSync
44761      * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
44762      */
44763     autoSync: false,
44764
44765     /**
44766      * @property {String} batchUpdateMode
44767      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
44768      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
44769      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
44770      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
44771      */
44772     batchUpdateMode: 'operation',
44773
44774     /**
44775      * @property {Boolean} filterOnLoad
44776      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
44777      * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
44778      */
44779     filterOnLoad: true,
44780
44781     /**
44782      * @property {Boolean} sortOnLoad
44783      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
44784      * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
44785      */
44786     sortOnLoad: true,
44787
44788     /**
44789      * @property {Boolean} implicitModel
44790      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
44791      * constructor instead of a model constructor or name.
44792      * @private
44793      */
44794     implicitModel: false,
44795
44796     /**
44797      * @property {String} defaultProxyType
44798      * The string type of the Proxy to create if none is specified. This defaults to creating a
44799      * {@link Ext.data.proxy.Memory memory proxy}.
44800      */
44801     defaultProxyType: 'memory',
44802
44803     /**
44804      * @property {Boolean} isDestroyed
44805      * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
44806      * as it will not function correctly any more.
44807      */
44808     isDestroyed: false,
44809
44810     isStore: true,
44811
44812     /**
44813      * @cfg {String} storeId
44814      * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
44815      * making it easy to reuse elsewhere. Defaults to undefined.
44816      */
44817     
44818     /**
44819      * @cfg {Object[]} fields
44820      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
44821      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
44822      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
44823      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
44824      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
44825      * config.
44826      */
44827
44828     /**
44829      * @cfg {String} model
44830      * Name of the {@link Ext.data.Model Model} associated with this store.
44831      * The string is used as an argument for {@link Ext.ModelManager#getModel}.
44832      */
44833
44834     sortRoot: 'data',
44835     
44836     //documented above
44837     constructor: function(config) {
44838         var me = this,
44839             filters;
44840         
44841         me.addEvents(
44842             /**
44843              * @event add
44844              * Fired when a Model instance has been added to this Store
44845              * @param {Ext.data.Store} store The store
44846              * @param {Ext.data.Model[]} records The Model instances that were added
44847              * @param {Number} index The index at which the instances were inserted
44848              */
44849             'add',
44850
44851             /**
44852              * @event remove
44853              * Fired when a Model instance has been removed from this Store
44854              * @param {Ext.data.Store} store The Store object
44855              * @param {Ext.data.Model} record The record that was removed
44856              * @param {Number} index The index of the record that was removed
44857              */
44858             'remove',
44859             
44860             /**
44861              * @event update
44862              * Fires when a Model instance has been updated
44863              * @param {Ext.data.Store} this
44864              * @param {Ext.data.Model} record The Model instance that was updated
44865              * @param {String} operation The update operation being performed. Value may be one of:
44866              *
44867              *     Ext.data.Model.EDIT
44868              *     Ext.data.Model.REJECT
44869              *     Ext.data.Model.COMMIT
44870              */
44871             'update',
44872
44873             /**
44874              * @event datachanged
44875              * Fires whenever the records in the Store have changed in some way - this could include adding or removing
44876              * records, or updating the data in existing records
44877              * @param {Ext.data.Store} this The data store
44878              */
44879             'datachanged',
44880
44881             /**
44882              * @event beforeload
44883              * Fires before a request is made for a new data object. If the beforeload handler returns false the load
44884              * action will be canceled.
44885              * @param {Ext.data.Store} store This Store
44886              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
44887              * load the Store
44888              */
44889             'beforeload',
44890
44891             /**
44892              * @event load
44893              * Fires whenever the store reads data from a remote data source.
44894              * @param {Ext.data.Store} this
44895              * @param {Ext.data.Model[]} records An array of records
44896              * @param {Boolean} successful True if the operation was successful.
44897              */
44898             'load',
44899             
44900             /**
44901              * @event write
44902              * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
44903              * @param {Ext.data.Store} store This Store
44904              * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
44905              * the write
44906              */
44907             'write',
44908
44909             /**
44910              * @event beforesync
44911              * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
44912              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
44913              */
44914             'beforesync',
44915             /**
44916              * @event clear
44917              * Fired after the {@link #removeAll} method is called.
44918              * @param {Ext.data.Store} this
44919              */
44920             'clear'
44921         );
44922         
44923         Ext.apply(me, config);
44924         // don't use *config* anymore from here on... use *me* instead...
44925
44926         /**
44927          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
44928          * at which point this is cleared.
44929          * @private
44930          * @property {Ext.data.Model[]} removed
44931          */
44932         me.removed = [];
44933
44934         me.mixins.observable.constructor.apply(me, arguments);
44935         me.model = Ext.ModelManager.getModel(me.model);
44936
44937         /**
44938          * @property {Object} modelDefaults
44939          * @private
44940          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
44941          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
44942          * for examples. This should not need to be used by application developers.
44943          */
44944         Ext.applyIf(me, {
44945             modelDefaults: {}
44946         });
44947
44948         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
44949         if (!me.model && me.fields) {
44950             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
44951                 extend: 'Ext.data.Model',
44952                 fields: me.fields,
44953                 proxy: me.proxy || me.defaultProxyType
44954             });
44955
44956             delete me.fields;
44957
44958             me.implicitModel = true;
44959         }
44960         
44961         // <debug>
44962         if (!me.model) {
44963             if (Ext.isDefined(Ext.global.console)) {
44964                 Ext.global.console.warn('Store defined with no model. You may have mistyped the model name.');
44965             }
44966         }
44967         // </debug>
44968
44969         //ensures that the Proxy is instantiated correctly
44970         me.setProxy(me.proxy || me.model.getProxy());
44971
44972         if (me.id && !me.storeId) {
44973             me.storeId = me.id;
44974             delete me.id;
44975         }
44976
44977         if (me.storeId) {
44978             Ext.data.StoreManager.register(me);
44979         }
44980         
44981         me.mixins.sortable.initSortable.call(me);        
44982         
44983         /**
44984          * @property {Ext.util.MixedCollection} filters
44985          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
44986          */
44987         filters = me.decodeFilters(me.filters);
44988         me.filters = Ext.create('Ext.util.MixedCollection');
44989         me.filters.addAll(filters);
44990     },
44991
44992     /**
44993      * Sets the Store's Proxy by string, config object or Proxy instance
44994      * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
44995      * or an Ext.data.proxy.Proxy instance
44996      * @return {Ext.data.proxy.Proxy} The attached Proxy object
44997      */
44998     setProxy: function(proxy) {
44999         var me = this;
45000         
45001         if (proxy instanceof Ext.data.proxy.Proxy) {
45002             proxy.setModel(me.model);
45003         } else {
45004             if (Ext.isString(proxy)) {
45005                 proxy = {
45006                     type: proxy    
45007                 };
45008             }
45009             Ext.applyIf(proxy, {
45010                 model: me.model
45011             });
45012             
45013             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
45014         }
45015         
45016         me.proxy = proxy;
45017         
45018         return me.proxy;
45019     },
45020
45021     /**
45022      * Returns the proxy currently attached to this proxy instance
45023      * @return {Ext.data.proxy.Proxy} The Proxy instance
45024      */
45025     getProxy: function() {
45026         return this.proxy;
45027     },
45028
45029     //saves any phantom records
45030     create: function(data, options) {
45031         var me = this,
45032             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
45033             operation;
45034         
45035         options = options || {};
45036
45037         Ext.applyIf(options, {
45038             action : 'create',
45039             records: [instance]
45040         });
45041
45042         operation = Ext.create('Ext.data.Operation', options);
45043
45044         me.proxy.create(operation, me.onProxyWrite, me);
45045         
45046         return instance;
45047     },
45048
45049     read: function() {
45050         return this.load.apply(this, arguments);
45051     },
45052
45053     onProxyRead: Ext.emptyFn,
45054
45055     update: function(options) {
45056         var me = this,
45057             operation;
45058         options = options || {};
45059
45060         Ext.applyIf(options, {
45061             action : 'update',
45062             records: me.getUpdatedRecords()
45063         });
45064
45065         operation = Ext.create('Ext.data.Operation', options);
45066
45067         return me.proxy.update(operation, me.onProxyWrite, me);
45068     },
45069
45070     /**
45071      * @private
45072      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
45073      * the updates provided by the Proxy
45074      */
45075     onProxyWrite: function(operation) {
45076         var me = this,
45077             success = operation.wasSuccessful(),
45078             records = operation.getRecords();
45079
45080         switch (operation.action) {
45081             case 'create':
45082                 me.onCreateRecords(records, operation, success);
45083                 break;
45084             case 'update':
45085                 me.onUpdateRecords(records, operation, success);
45086                 break;
45087             case 'destroy':
45088                 me.onDestroyRecords(records, operation, success);
45089                 break;
45090         }
45091
45092         if (success) {
45093             me.fireEvent('write', me, operation);
45094             me.fireEvent('datachanged', me);
45095         }
45096         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
45097         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
45098     },
45099
45100
45101     //tells the attached proxy to destroy the given records
45102     destroy: function(options) {
45103         var me = this,
45104             operation;
45105             
45106         options = options || {};
45107
45108         Ext.applyIf(options, {
45109             action : 'destroy',
45110             records: me.getRemovedRecords()
45111         });
45112
45113         operation = Ext.create('Ext.data.Operation', options);
45114
45115         return me.proxy.destroy(operation, me.onProxyWrite, me);
45116     },
45117
45118     /**
45119      * @private
45120      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
45121      * to onProxyWrite.
45122      */
45123     onBatchOperationComplete: function(batch, operation) {
45124         return this.onProxyWrite(operation);
45125     },
45126
45127     /**
45128      * @private
45129      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
45130      * and updates the Store's internal data MixedCollection.
45131      */
45132     onBatchComplete: function(batch, operation) {
45133         var me = this,
45134             operations = batch.operations,
45135             length = operations.length,
45136             i;
45137
45138         me.suspendEvents();
45139
45140         for (i = 0; i < length; i++) {
45141             me.onProxyWrite(operations[i]);
45142         }
45143
45144         me.resumeEvents();
45145
45146         me.fireEvent('datachanged', me);
45147     },
45148
45149     onBatchException: function(batch, operation) {
45150         // //decide what to do... could continue with the next operation
45151         // batch.start();
45152         //
45153         // //or retry the last operation
45154         // batch.retry();
45155     },
45156
45157     /**
45158      * @private
45159      * Filter function for new records.
45160      */
45161     filterNew: function(item) {
45162         // only want phantom records that are valid
45163         return item.phantom === true && item.isValid();
45164     },
45165
45166     /**
45167      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
45168      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
45169      * @return {Ext.data.Model[]} The Model instances
45170      */
45171     getNewRecords: function() {
45172         return [];
45173     },
45174
45175     /**
45176      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
45177      * @return {Ext.data.Model[]} The updated Model instances
45178      */
45179     getUpdatedRecords: function() {
45180         return [];
45181     },
45182
45183     /**
45184      * @private
45185      * Filter function for updated records.
45186      */
45187     filterUpdated: function(item) {
45188         // only want dirty records, not phantoms that are valid
45189         return item.dirty === true && item.phantom !== true && item.isValid();
45190     },
45191
45192     /**
45193      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
45194      * @return {Ext.data.Model[]} The removed Model instances
45195      */
45196     getRemovedRecords: function() {
45197         return this.removed;
45198     },
45199
45200     filter: function(filters, value) {
45201
45202     },
45203
45204     /**
45205      * @private
45206      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
45207      * @param {Object[]} filters The filters array
45208      * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
45209      */
45210     decodeFilters: function(filters) {
45211         if (!Ext.isArray(filters)) {
45212             if (filters === undefined) {
45213                 filters = [];
45214             } else {
45215                 filters = [filters];
45216             }
45217         }
45218
45219         var length = filters.length,
45220             Filter = Ext.util.Filter,
45221             config, i;
45222
45223         for (i = 0; i < length; i++) {
45224             config = filters[i];
45225
45226             if (!(config instanceof Filter)) {
45227                 Ext.apply(config, {
45228                     root: 'data'
45229                 });
45230
45231                 //support for 3.x style filters where a function can be defined as 'fn'
45232                 if (config.fn) {
45233                     config.filterFn = config.fn;
45234                 }
45235
45236                 //support a function to be passed as a filter definition
45237                 if (typeof config == 'function') {
45238                     config = {
45239                         filterFn: config
45240                     };
45241                 }
45242
45243                 filters[i] = new Filter(config);
45244             }
45245         }
45246
45247         return filters;
45248     },
45249
45250     clearFilter: function(supressEvent) {
45251
45252     },
45253
45254     isFiltered: function() {
45255
45256     },
45257
45258     filterBy: function(fn, scope) {
45259
45260     },
45261     
45262     /**
45263      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
45264      * and deleted records in the store, updating the Store's internal representation of the records
45265      * as each operation completes.
45266      */
45267     sync: function() {
45268         var me        = this,
45269             options   = {},
45270             toCreate  = me.getNewRecords(),
45271             toUpdate  = me.getUpdatedRecords(),
45272             toDestroy = me.getRemovedRecords(),
45273             needsSync = false;
45274
45275         if (toCreate.length > 0) {
45276             options.create = toCreate;
45277             needsSync = true;
45278         }
45279
45280         if (toUpdate.length > 0) {
45281             options.update = toUpdate;
45282             needsSync = true;
45283         }
45284
45285         if (toDestroy.length > 0) {
45286             options.destroy = toDestroy;
45287             needsSync = true;
45288         }
45289
45290         if (needsSync && me.fireEvent('beforesync', options) !== false) {
45291             me.proxy.batch(options, me.getBatchListeners());
45292         }
45293     },
45294
45295
45296     /**
45297      * @private
45298      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
45299      * This is broken out into a separate function to allow for customisation of the listeners
45300      * @return {Object} The listeners object
45301      */
45302     getBatchListeners: function() {
45303         var me = this,
45304             listeners = {
45305                 scope: me,
45306                 exception: me.onBatchException
45307             };
45308
45309         if (me.batchUpdateMode == 'operation') {
45310             listeners.operationcomplete = me.onBatchOperationComplete;
45311         } else {
45312             listeners.complete = me.onBatchComplete;
45313         }
45314
45315         return listeners;
45316     },
45317
45318     //deprecated, will be removed in 5.0
45319     save: function() {
45320         return this.sync.apply(this, arguments);
45321     },
45322
45323     /**
45324      * Loads the Store using its configured {@link #proxy}.
45325      * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
45326      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
45327      */
45328     load: function(options) {
45329         var me = this,
45330             operation;
45331
45332         options = options || {};
45333
45334         Ext.applyIf(options, {
45335             action : 'read',
45336             filters: me.filters.items,
45337             sorters: me.getSorters()
45338         });
45339         
45340         operation = Ext.create('Ext.data.Operation', options);
45341
45342         if (me.fireEvent('beforeload', me, operation) !== false) {
45343             me.loading = true;
45344             me.proxy.read(operation, me.onProxyLoad, me);
45345         }
45346         
45347         return me;
45348     },
45349
45350     /**
45351      * @private
45352      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
45353      * @param {Ext.data.Model} record The model instance that was edited
45354      */
45355     afterEdit : function(record) {
45356         var me = this;
45357         
45358         if (me.autoSync) {
45359             me.sync();
45360         }
45361         
45362         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
45363     },
45364
45365     /**
45366      * @private
45367      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
45368      * @param {Ext.data.Model} record The model instance that was edited
45369      */
45370     afterReject : function(record) {
45371         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
45372     },
45373
45374     /**
45375      * @private
45376      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
45377      * @param {Ext.data.Model} record The model instance that was edited
45378      */
45379     afterCommit : function(record) {
45380         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
45381     },
45382
45383     clearData: Ext.emptyFn,
45384
45385     destroyStore: function() {
45386         var me = this;
45387         
45388         if (!me.isDestroyed) {
45389             if (me.storeId) {
45390                 Ext.data.StoreManager.unregister(me);
45391             }
45392             me.clearData();
45393             me.data = null;
45394             me.tree = null;
45395             // Ext.destroy(this.proxy);
45396             me.reader = me.writer = null;
45397             me.clearListeners();
45398             me.isDestroyed = true;
45399
45400             if (me.implicitModel) {
45401                 Ext.destroy(me.model);
45402             }
45403         }
45404     },
45405     
45406     doSort: function(sorterFn) {
45407         var me = this;
45408         if (me.remoteSort) {
45409             //the load function will pick up the new sorters and request the sorted data from the proxy
45410             me.load();
45411         } else {
45412             me.data.sortBy(sorterFn);
45413             me.fireEvent('datachanged', me);
45414         }
45415     },
45416
45417     getCount: Ext.emptyFn,
45418
45419     getById: Ext.emptyFn,
45420     
45421     /**
45422      * Removes all records from the store. This method does a "fast remove",
45423      * individual remove events are not called. The {@link #clear} event is
45424      * fired upon completion.
45425      * @method
45426      */
45427     removeAll: Ext.emptyFn,
45428     // individual substores should implement a "fast" remove
45429     // and fire a clear event afterwards
45430
45431     /**
45432      * Returns true if the Store is currently performing a load operation
45433      * @return {Boolean} True if the Store is currently loading
45434      */
45435     isLoading: function() {
45436         return !!this.loading;
45437      }
45438 });
45439
45440 /**
45441  * @class Ext.util.Grouper
45442  * @extends Ext.util.Sorter
45443
45444 Represents a single grouper that can be applied to a Store. The grouper works
45445 in the same fashion as the {@link Ext.util.Sorter}.
45446
45447  * @markdown
45448  */
45449  
45450 Ext.define('Ext.util.Grouper', {
45451
45452     /* Begin Definitions */
45453
45454     extend: 'Ext.util.Sorter',
45455
45456     /* End Definitions */
45457
45458     /**
45459      * Returns the value for grouping to be used.
45460      * @param {Ext.data.Model} instance The Model instance
45461      * @return {String} The group string for this model
45462      */
45463     getGroupString: function(instance) {
45464         return instance.get(this.property);
45465     }
45466 });
45467 /**
45468  * @author Ed Spencer
45469  * @class Ext.data.Store
45470  * @extends Ext.data.AbstractStore
45471  *
45472  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
45473  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
45474  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
45475  *
45476  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
45477  *
45478 <pre><code>
45479 // Set up a {@link Ext.data.Model model} to use in our Store
45480 Ext.define('User', {
45481     extend: 'Ext.data.Model',
45482     fields: [
45483         {name: 'firstName', type: 'string'},
45484         {name: 'lastName',  type: 'string'},
45485         {name: 'age',       type: 'int'},
45486         {name: 'eyeColor',  type: 'string'}
45487     ]
45488 });
45489
45490 var myStore = Ext.create('Ext.data.Store', {
45491     model: 'User',
45492     proxy: {
45493         type: 'ajax',
45494         url : '/users.json',
45495         reader: {
45496             type: 'json',
45497             root: 'users'
45498         }
45499     },
45500     autoLoad: true
45501 });
45502 </code></pre>
45503
45504  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
45505  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
45506  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
45507  *
45508  * <p><u>Inline data</u></p>
45509  *
45510  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
45511  * into Model instances:</p>
45512  *
45513 <pre><code>
45514 Ext.create('Ext.data.Store', {
45515     model: 'User',
45516     data : [
45517         {firstName: 'Ed',    lastName: 'Spencer'},
45518         {firstName: 'Tommy', lastName: 'Maintz'},
45519         {firstName: 'Aaron', lastName: 'Conran'},
45520         {firstName: 'Jamie', lastName: 'Avins'}
45521     ]
45522 });
45523 </code></pre>
45524  *
45525  * <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
45526  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
45527  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
45528  *
45529  * <p>Additional data can also be loaded locally using {@link #add}.</p>
45530  *
45531  * <p><u>Loading Nested Data</u></p>
45532  *
45533  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
45534  * 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
45535  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
45536  * docs for a full explanation:</p>
45537  *
45538 <pre><code>
45539 var store = Ext.create('Ext.data.Store', {
45540     autoLoad: true,
45541     model: "User",
45542     proxy: {
45543         type: 'ajax',
45544         url : 'users.json',
45545         reader: {
45546             type: 'json',
45547             root: 'users'
45548         }
45549     }
45550 });
45551 </code></pre>
45552  *
45553  * <p>Which would consume a response like this:</p>
45554  *
45555 <pre><code>
45556 {
45557     "users": [
45558         {
45559             "id": 1,
45560             "name": "Ed",
45561             "orders": [
45562                 {
45563                     "id": 10,
45564                     "total": 10.76,
45565                     "status": "invoiced"
45566                 },
45567                 {
45568                     "id": 11,
45569                     "total": 13.45,
45570                     "status": "shipped"
45571                 }
45572             ]
45573         }
45574     ]
45575 }
45576 </code></pre>
45577  *
45578  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
45579  *
45580  * <p><u>Filtering and Sorting</u></p>
45581  *
45582  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
45583  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
45584  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
45585  *
45586 <pre><code>
45587 var store = Ext.create('Ext.data.Store', {
45588     model: 'User',
45589     sorters: [
45590         {
45591             property : 'age',
45592             direction: 'DESC'
45593         },
45594         {
45595             property : 'firstName',
45596             direction: 'ASC'
45597         }
45598     ],
45599
45600     filters: [
45601         {
45602             property: 'firstName',
45603             value   : /Ed/
45604         }
45605     ]
45606 });
45607 </code></pre>
45608  *
45609  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
45610  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
45611  * perform these operations instead.</p>
45612  *
45613  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
45614  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
45615  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
45616  *
45617 <pre><code>
45618 store.filter('eyeColor', 'Brown');
45619 </code></pre>
45620  *
45621  * <p>Change the sorting at any time by calling {@link #sort}:</p>
45622  *
45623 <pre><code>
45624 store.sort('height', 'ASC');
45625 </code></pre>
45626  *
45627  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
45628  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
45629  * to the MixedCollection:</p>
45630  *
45631 <pre><code>
45632 store.sorters.add(new Ext.util.Sorter({
45633     property : 'shoeSize',
45634     direction: 'ASC'
45635 }));
45636
45637 store.sort();
45638 </code></pre>
45639  *
45640  * <p><u>Registering with StoreManager</u></p>
45641  *
45642  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
45643  * This makes it easy to reuse the same store in multiple views:</p>
45644  *
45645  <pre><code>
45646 //this store can be used several times
45647 Ext.create('Ext.data.Store', {
45648     model: 'User',
45649     storeId: 'usersStore'
45650 });
45651
45652 new Ext.List({
45653     store: 'usersStore',
45654
45655     //other config goes here
45656 });
45657
45658 new Ext.view.View({
45659     store: 'usersStore',
45660
45661     //other config goes here
45662 });
45663 </code></pre>
45664  *
45665  * <p><u>Further Reading</u></p>
45666  *
45667  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
45668  * pieces and how they fit together, see:</p>
45669  *
45670  * <ul style="list-style-type: disc; padding-left: 25px">
45671  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
45672  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
45673  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
45674  * </ul>
45675  *
45676  */
45677 Ext.define('Ext.data.Store', {
45678     extend: 'Ext.data.AbstractStore',
45679
45680     alias: 'store.store',
45681
45682     requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
45683     uses: ['Ext.data.proxy.Memory'],
45684
45685     /**
45686      * @cfg {Boolean} remoteSort
45687      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
45688      */
45689     remoteSort: false,
45690
45691     /**
45692      * @cfg {Boolean} remoteFilter
45693      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
45694      */
45695     remoteFilter: false,
45696
45697     /**
45698      * @cfg {Boolean} remoteGroup
45699      * True if the grouping should apply on the server side, false if it is local only.  If the
45700      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
45701      * helper, automatically sending the grouping information to the server.
45702      */
45703     remoteGroup : false,
45704
45705     /**
45706      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
45707      * object or a Proxy instance - see {@link #setProxy} for details.
45708      */
45709
45710     /**
45711      * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
45712      */
45713
45714     /**
45715      * @property {String} groupField
45716      * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
45717      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
45718      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
45719      */
45720     groupField: undefined,
45721
45722     /**
45723      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
45724      * @property groupDir
45725      * @type String
45726      */
45727     groupDir: "ASC",
45728
45729     /**
45730      * @cfg {Number} pageSize
45731      * The number of records considered to form a 'page'. This is used to power the built-in
45732      * paging using the nextPage and previousPage functions. Defaults to 25.
45733      */
45734     pageSize: 25,
45735
45736     /**
45737      * The page that the Store has most recently loaded (see {@link #loadPage})
45738      * @property currentPage
45739      * @type Number
45740      */
45741     currentPage: 1,
45742
45743     /**
45744      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
45745      * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
45746      * large data sets to be loaded one page at a time but rendered all together.
45747      */
45748     clearOnPageLoad: true,
45749
45750     /**
45751      * @property {Boolean} loading
45752      * True if the Store is currently loading via its Proxy
45753      * @private
45754      */
45755     loading: false,
45756
45757     /**
45758      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
45759      * causing the sorters to be reapplied after filtering. Defaults to true
45760      */
45761     sortOnFilter: true,
45762
45763     /**
45764      * @cfg {Boolean} buffered
45765      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
45766      * tell the store to pre-fetch records ahead of a time.
45767      */
45768     buffered: false,
45769
45770     /**
45771      * @cfg {Number} purgePageCount
45772      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
45773      * This option is only relevant when the {@link #buffered} option is set to true.
45774      */
45775     purgePageCount: 5,
45776
45777     isStore: true,
45778
45779     onClassExtended: function(cls, data) {
45780         var model = data.model;
45781
45782         if (typeof model == 'string') {
45783             var onBeforeClassCreated = data.onBeforeClassCreated;
45784
45785             data.onBeforeClassCreated = function(cls, data) {
45786                 var me = this;
45787
45788                 Ext.require(model, function() {
45789                     onBeforeClassCreated.call(me, cls, data);
45790                 });
45791             };
45792         }
45793     },
45794
45795     /**
45796      * Creates the store.
45797      * @param {Object} config (optional) Config object
45798      */
45799     constructor: function(config) {
45800         // Clone the config so we don't modify the original config object
45801         config = Ext.Object.merge({}, config);
45802
45803         var me = this,
45804             groupers = config.groupers || me.groupers,
45805             groupField = config.groupField || me.groupField,
45806             proxy,
45807             data;
45808
45809         if (config.buffered || me.buffered) {
45810             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
45811                 return record.index;
45812             });
45813             me.pendingRequests = [];
45814             me.pagesRequested = [];
45815
45816             me.sortOnLoad = false;
45817             me.filterOnLoad = false;
45818         }
45819
45820         me.addEvents(
45821             /**
45822              * @event beforeprefetch
45823              * Fires before a prefetch occurs. Return false to cancel.
45824              * @param {Ext.data.Store} this
45825              * @param {Ext.data.Operation} operation The associated operation
45826              */
45827             'beforeprefetch',
45828             /**
45829              * @event groupchange
45830              * Fired whenever the grouping in the grid changes
45831              * @param {Ext.data.Store} store The store
45832              * @param {Ext.util.Grouper[]} groupers The array of grouper objects
45833              */
45834             'groupchange',
45835             /**
45836              * @event load
45837              * Fires whenever records have been prefetched
45838              * @param {Ext.data.Store} this
45839              * @param {Ext.util.Grouper[]} records An array of records
45840              * @param {Boolean} successful True if the operation was successful.
45841              * @param {Ext.data.Operation} operation The associated operation
45842              */
45843             'prefetch'
45844         );
45845         data = config.data || me.data;
45846
45847         /**
45848          * The MixedCollection that holds this store's local cache of records
45849          * @property data
45850          * @type Ext.util.MixedCollection
45851          */
45852         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
45853             return record.internalId;
45854         });
45855
45856         if (data) {
45857             me.inlineData = data;
45858             delete config.data;
45859         }
45860
45861         if (!groupers && groupField) {
45862             groupers = [{
45863                 property : groupField,
45864                 direction: config.groupDir || me.groupDir
45865             }];
45866         }
45867         delete config.groupers;
45868
45869         /**
45870          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
45871          * @property groupers
45872          * @type Ext.util.MixedCollection
45873          */
45874         me.groupers = Ext.create('Ext.util.MixedCollection');
45875         me.groupers.addAll(me.decodeGroupers(groupers));
45876
45877         this.callParent([config]);
45878         // don't use *config* anymore from here on... use *me* instead...
45879
45880         if (me.groupers.items.length) {
45881             me.sort(me.groupers.items, 'prepend', false);
45882         }
45883
45884         proxy = me.proxy;
45885         data = me.inlineData;
45886
45887         if (data) {
45888             if (proxy instanceof Ext.data.proxy.Memory) {
45889                 proxy.data = data;
45890                 me.read();
45891             } else {
45892                 me.add.apply(me, data);
45893             }
45894
45895             me.sort();
45896             delete me.inlineData;
45897         } else if (me.autoLoad) {
45898             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
45899             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
45900             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
45901         }
45902     },
45903
45904     onBeforeSort: function() {
45905         var groupers = this.groupers;
45906         if (groupers.getCount() > 0) {
45907             this.sort(groupers.items, 'prepend', false);
45908         }
45909     },
45910
45911     /**
45912      * @private
45913      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
45914      * @param {Object[]} groupers The groupers array
45915      * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
45916      */
45917     decodeGroupers: function(groupers) {
45918         if (!Ext.isArray(groupers)) {
45919             if (groupers === undefined) {
45920                 groupers = [];
45921             } else {
45922                 groupers = [groupers];
45923             }
45924         }
45925
45926         var length  = groupers.length,
45927             Grouper = Ext.util.Grouper,
45928             config, i;
45929
45930         for (i = 0; i < length; i++) {
45931             config = groupers[i];
45932
45933             if (!(config instanceof Grouper)) {
45934                 if (Ext.isString(config)) {
45935                     config = {
45936                         property: config
45937                     };
45938                 }
45939
45940                 Ext.applyIf(config, {
45941                     root     : 'data',
45942                     direction: "ASC"
45943                 });
45944
45945                 //support for 3.x style sorters where a function can be defined as 'fn'
45946                 if (config.fn) {
45947                     config.sorterFn = config.fn;
45948                 }
45949
45950                 //support a function to be passed as a sorter definition
45951                 if (typeof config == 'function') {
45952                     config = {
45953                         sorterFn: config
45954                     };
45955                 }
45956
45957                 groupers[i] = new Grouper(config);
45958             }
45959         }
45960
45961         return groupers;
45962     },
45963
45964     /**
45965      * Group data in the store
45966      * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
45967      * or an Array of grouper configurations.
45968      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
45969      */
45970     group: function(groupers, direction) {
45971         var me = this,
45972             hasNew = false,
45973             grouper,
45974             newGroupers;
45975
45976         if (Ext.isArray(groupers)) {
45977             newGroupers = groupers;
45978         } else if (Ext.isObject(groupers)) {
45979             newGroupers = [groupers];
45980         } else if (Ext.isString(groupers)) {
45981             grouper = me.groupers.get(groupers);
45982
45983             if (!grouper) {
45984                 grouper = {
45985                     property : groupers,
45986                     direction: direction
45987                 };
45988                 newGroupers = [grouper];
45989             } else if (direction === undefined) {
45990                 grouper.toggle();
45991             } else {
45992                 grouper.setDirection(direction);
45993             }
45994         }
45995
45996         if (newGroupers && newGroupers.length) {
45997             hasNew = true;
45998             newGroupers = me.decodeGroupers(newGroupers);
45999             me.groupers.clear();
46000             me.groupers.addAll(newGroupers);
46001         }
46002
46003         if (me.remoteGroup) {
46004             me.load({
46005                 scope: me,
46006                 callback: me.fireGroupChange
46007             });
46008         } else {
46009             // need to explicitly force a sort if we have groupers
46010             me.sort(null, null, null, hasNew);
46011             me.fireGroupChange();
46012         }
46013     },
46014
46015     /**
46016      * Clear any groupers in the store
46017      */
46018     clearGrouping: function(){
46019         var me = this;
46020         // Clear any groupers we pushed on to the sorters
46021         me.groupers.each(function(grouper){
46022             me.sorters.remove(grouper);
46023         });
46024         me.groupers.clear();
46025         if (me.remoteGroup) {
46026             me.load({
46027                 scope: me,
46028                 callback: me.fireGroupChange
46029             });
46030         } else {
46031             me.sort();
46032             me.fireEvent('groupchange', me, me.groupers);
46033         }
46034     },
46035
46036     /**
46037      * Checks if the store is currently grouped
46038      * @return {Boolean} True if the store is grouped.
46039      */
46040     isGrouped: function() {
46041         return this.groupers.getCount() > 0;
46042     },
46043
46044     /**
46045      * Fires the groupchange event. Abstracted out so we can use it
46046      * as a callback
46047      * @private
46048      */
46049     fireGroupChange: function(){
46050         this.fireEvent('groupchange', this, this.groupers);
46051     },
46052
46053     /**
46054      * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
46055      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
46056 <pre><code>
46057 var myStore = Ext.create('Ext.data.Store', {
46058     groupField: 'color',
46059     groupDir  : 'DESC'
46060 });
46061
46062 myStore.getGroups(); //returns:
46063 [
46064     {
46065         name: 'yellow',
46066         children: [
46067             //all records where the color field is 'yellow'
46068         ]
46069     },
46070     {
46071         name: 'red',
46072         children: [
46073             //all records where the color field is 'red'
46074         ]
46075     }
46076 ]
46077 </code></pre>
46078      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
46079      * @return {Object/Object[]} The grouped data
46080      */
46081     getGroups: function(requestGroupString) {
46082         var records = this.data.items,
46083             length = records.length,
46084             groups = [],
46085             pointers = {},
46086             record,
46087             groupStr,
46088             group,
46089             i;
46090
46091         for (i = 0; i < length; i++) {
46092             record = records[i];
46093             groupStr = this.getGroupString(record);
46094             group = pointers[groupStr];
46095
46096             if (group === undefined) {
46097                 group = {
46098                     name: groupStr,
46099                     children: []
46100                 };
46101
46102                 groups.push(group);
46103                 pointers[groupStr] = group;
46104             }
46105
46106             group.children.push(record);
46107         }
46108
46109         return requestGroupString ? pointers[requestGroupString] : groups;
46110     },
46111
46112     /**
46113      * @private
46114      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
46115      * matching a certain group.
46116      */
46117     getGroupsForGrouper: function(records, grouper) {
46118         var length = records.length,
46119             groups = [],
46120             oldValue,
46121             newValue,
46122             record,
46123             group,
46124             i;
46125
46126         for (i = 0; i < length; i++) {
46127             record = records[i];
46128             newValue = grouper.getGroupString(record);
46129
46130             if (newValue !== oldValue) {
46131                 group = {
46132                     name: newValue,
46133                     grouper: grouper,
46134                     records: []
46135                 };
46136                 groups.push(group);
46137             }
46138
46139             group.records.push(record);
46140
46141             oldValue = newValue;
46142         }
46143
46144         return groups;
46145     },
46146
46147     /**
46148      * @private
46149      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
46150      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
46151      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
46152      * @param {Ext.data.Model[]} records The set or subset of records to group
46153      * @param {Number} grouperIndex The grouper index to retrieve
46154      * @return {Object[]} The grouped records
46155      */
46156     getGroupsForGrouperIndex: function(records, grouperIndex) {
46157         var me = this,
46158             groupers = me.groupers,
46159             grouper = groupers.getAt(grouperIndex),
46160             groups = me.getGroupsForGrouper(records, grouper),
46161             length = groups.length,
46162             i;
46163
46164         if (grouperIndex + 1 < groupers.length) {
46165             for (i = 0; i < length; i++) {
46166                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
46167             }
46168         }
46169
46170         for (i = 0; i < length; i++) {
46171             groups[i].depth = grouperIndex;
46172         }
46173
46174         return groups;
46175     },
46176
46177     /**
46178      * @private
46179      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
46180      * this case grouping by genre and then author in a fictional books dataset):</p>
46181 <pre><code>
46182 [
46183     {
46184         name: 'Fantasy',
46185         depth: 0,
46186         records: [
46187             //book1, book2, book3, book4
46188         ],
46189         children: [
46190             {
46191                 name: 'Rowling',
46192                 depth: 1,
46193                 records: [
46194                     //book1, book2
46195                 ]
46196             },
46197             {
46198                 name: 'Tolkein',
46199                 depth: 1,
46200                 records: [
46201                     //book3, book4
46202                 ]
46203             }
46204         ]
46205     }
46206 ]
46207 </code></pre>
46208      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
46209      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
46210      * (defaults to true)
46211      * @return {Object[]} The group data
46212      */
46213     getGroupData: function(sort) {
46214         var me = this;
46215         if (sort !== false) {
46216             me.sort();
46217         }
46218
46219         return me.getGroupsForGrouperIndex(me.data.items, 0);
46220     },
46221
46222     /**
46223      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
46224      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
46225      * group by the first letter of a model's 'name' field, use the following code:</p>
46226 <pre><code>
46227 Ext.create('Ext.data.Store', {
46228     groupDir: 'ASC',
46229     getGroupString: function(instance) {
46230         return instance.get('name')[0];
46231     }
46232 });
46233 </code></pre>
46234      * @param {Ext.data.Model} instance The model instance
46235      * @return {String} The string to compare when forming groups
46236      */
46237     getGroupString: function(instance) {
46238         var group = this.groupers.first();
46239         if (group) {
46240             return instance.get(group.property);
46241         }
46242         return '';
46243     },
46244     /**
46245      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
46246      * See also <code>{@link #add}</code>.
46247      * @param {Number} index The start index at which to insert the passed Records.
46248      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
46249      */
46250     insert: function(index, records) {
46251         var me = this,
46252             sync = false,
46253             i,
46254             record,
46255             len;
46256
46257         records = [].concat(records);
46258         for (i = 0, len = records.length; i < len; i++) {
46259             record = me.createModel(records[i]);
46260             record.set(me.modelDefaults);
46261             // reassign the model in the array in case it wasn't created yet
46262             records[i] = record;
46263
46264             me.data.insert(index + i, record);
46265             record.join(me);
46266
46267             sync = sync || record.phantom === true;
46268         }
46269
46270         if (me.snapshot) {
46271             me.snapshot.addAll(records);
46272         }
46273
46274         me.fireEvent('add', me, records, index);
46275         me.fireEvent('datachanged', me);
46276         if (me.autoSync && sync) {
46277             me.sync();
46278         }
46279     },
46280
46281     /**
46282      * Adds Model instance to the Store. This method accepts either:
46283      *
46284      * - An array of Model instances or Model configuration objects.
46285      * - Any number of Model instance or Model configuration object arguments.
46286      *
46287      * The new Model instances will be added at the end of the existing collection.
46288      *
46289      * Sample usage:
46290      *
46291      *     myStore.add({some: 'data'}, {some: 'other data'});
46292      *
46293      * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
46294      * or Model configuration objects, or variable number of Model instance or config arguments.
46295      * @return {Ext.data.Model[]} The model instances that were added
46296      */
46297     add: function(records) {
46298         //accept both a single-argument array of records, or any number of record arguments
46299         if (!Ext.isArray(records)) {
46300             records = Array.prototype.slice.apply(arguments);
46301         }
46302
46303         var me = this,
46304             i = 0,
46305             length = records.length,
46306             record;
46307
46308         for (; i < length; i++) {
46309             record = me.createModel(records[i]);
46310             // reassign the model in the array in case it wasn't created yet
46311             records[i] = record;
46312         }
46313
46314         me.insert(me.data.length, records);
46315
46316         return records;
46317     },
46318
46319     /**
46320      * Converts a literal to a model, if it's not a model already
46321      * @private
46322      * @param record {Ext.data.Model/Object} The record to create
46323      * @return {Ext.data.Model}
46324      */
46325     createModel: function(record) {
46326         if (!record.isModel) {
46327             record = Ext.ModelManager.create(record, this.model);
46328         }
46329
46330         return record;
46331     },
46332
46333     /**
46334      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
46335      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
46336      * Returning <tt>false</tt> aborts and exits the iteration.
46337      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
46338      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
46339      */
46340     each: function(fn, scope) {
46341         this.data.each(fn, scope);
46342     },
46343
46344     /**
46345      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
46346      * 'datachanged' event after removal.
46347      * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
46348      */
46349     remove: function(records, /* private */ isMove) {
46350         if (!Ext.isArray(records)) {
46351             records = [records];
46352         }
46353
46354         /*
46355          * Pass the isMove parameter if we know we're going to be re-inserting this record
46356          */
46357         isMove = isMove === true;
46358         var me = this,
46359             sync = false,
46360             i = 0,
46361             length = records.length,
46362             isPhantom,
46363             index,
46364             record;
46365
46366         for (; i < length; i++) {
46367             record = records[i];
46368             index = me.data.indexOf(record);
46369
46370             if (me.snapshot) {
46371                 me.snapshot.remove(record);
46372             }
46373
46374             if (index > -1) {
46375                 isPhantom = record.phantom === true;
46376                 if (!isMove && !isPhantom) {
46377                     // don't push phantom records onto removed
46378                     me.removed.push(record);
46379                 }
46380
46381                 record.unjoin(me);
46382                 me.data.remove(record);
46383                 sync = sync || !isPhantom;
46384
46385                 me.fireEvent('remove', me, record, index);
46386             }
46387         }
46388
46389         me.fireEvent('datachanged', me);
46390         if (!isMove && me.autoSync && sync) {
46391             me.sync();
46392         }
46393     },
46394
46395     /**
46396      * Removes the model instance at the given index
46397      * @param {Number} index The record index
46398      */
46399     removeAt: function(index) {
46400         var record = this.getAt(index);
46401
46402         if (record) {
46403             this.remove(record);
46404         }
46405     },
46406
46407     /**
46408      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
46409      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
46410      * instances into the Store and calling an optional callback if required. Example usage:</p>
46411      *
46412 <pre><code>
46413 store.load({
46414     scope   : this,
46415     callback: function(records, operation, success) {
46416         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
46417         console.log(records);
46418     }
46419 });
46420 </code></pre>
46421      *
46422      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
46423      *
46424 <pre><code>
46425 store.load(function(records, operation, success) {
46426     console.log('loaded records');
46427 });
46428 </code></pre>
46429      *
46430      * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
46431      */
46432     load: function(options) {
46433         var me = this;
46434
46435         options = options || {};
46436
46437         if (Ext.isFunction(options)) {
46438             options = {
46439                 callback: options
46440             };
46441         }
46442
46443         Ext.applyIf(options, {
46444             groupers: me.groupers.items,
46445             page: me.currentPage,
46446             start: (me.currentPage - 1) * me.pageSize,
46447             limit: me.pageSize,
46448             addRecords: false
46449         });
46450
46451         return me.callParent([options]);
46452     },
46453
46454     /**
46455      * @private
46456      * Called internally when a Proxy has completed a load request
46457      */
46458     onProxyLoad: function(operation) {
46459         var me = this,
46460             resultSet = operation.getResultSet(),
46461             records = operation.getRecords(),
46462             successful = operation.wasSuccessful();
46463
46464         if (resultSet) {
46465             me.totalCount = resultSet.total;
46466         }
46467
46468         if (successful) {
46469             me.loadRecords(records, operation);
46470         }
46471
46472         me.loading = false;
46473         me.fireEvent('load', me, records, successful);
46474
46475         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
46476         //People are definitely using this so can't deprecate safely until 2.x
46477         me.fireEvent('read', me, records, operation.wasSuccessful());
46478
46479         //this is a callback that would have been passed to the 'read' function and is optional
46480         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
46481     },
46482
46483     /**
46484      * Create any new records when a write is returned from the server.
46485      * @private
46486      * @param {Ext.data.Model[]} records The array of new records
46487      * @param {Ext.data.Operation} operation The operation that just completed
46488      * @param {Boolean} success True if the operation was successful
46489      */
46490     onCreateRecords: function(records, operation, success) {
46491         if (success) {
46492             var i = 0,
46493                 data = this.data,
46494                 snapshot = this.snapshot,
46495                 length = records.length,
46496                 originalRecords = operation.records,
46497                 record,
46498                 original,
46499                 index;
46500
46501             /*
46502              * Loop over each record returned from the server. Assume they are
46503              * returned in order of how they were sent. If we find a matching
46504              * record, replace it with the newly created one.
46505              */
46506             for (; i < length; ++i) {
46507                 record = records[i];
46508                 original = originalRecords[i];
46509                 if (original) {
46510                     index = data.indexOf(original);
46511                     if (index > -1) {
46512                         data.removeAt(index);
46513                         data.insert(index, record);
46514                     }
46515                     if (snapshot) {
46516                         index = snapshot.indexOf(original);
46517                         if (index > -1) {
46518                             snapshot.removeAt(index);
46519                             snapshot.insert(index, record);
46520                         }
46521                     }
46522                     record.phantom = false;
46523                     record.join(this);
46524                 }
46525             }
46526         }
46527     },
46528
46529     /**
46530      * Update any records when a write is returned from the server.
46531      * @private
46532      * @param {Ext.data.Model[]} records The array of updated records
46533      * @param {Ext.data.Operation} operation The operation that just completed
46534      * @param {Boolean} success True if the operation was successful
46535      */
46536     onUpdateRecords: function(records, operation, success){
46537         if (success) {
46538             var i = 0,
46539                 length = records.length,
46540                 data = this.data,
46541                 snapshot = this.snapshot,
46542                 record;
46543
46544             for (; i < length; ++i) {
46545                 record = records[i];
46546                 data.replace(record);
46547                 if (snapshot) {
46548                     snapshot.replace(record);
46549                 }
46550                 record.join(this);
46551             }
46552         }
46553     },
46554
46555     /**
46556      * Remove any records when a write is returned from the server.
46557      * @private
46558      * @param {Ext.data.Model[]} records The array of removed records
46559      * @param {Ext.data.Operation} operation The operation that just completed
46560      * @param {Boolean} success True if the operation was successful
46561      */
46562     onDestroyRecords: function(records, operation, success){
46563         if (success) {
46564             var me = this,
46565                 i = 0,
46566                 length = records.length,
46567                 data = me.data,
46568                 snapshot = me.snapshot,
46569                 record;
46570
46571             for (; i < length; ++i) {
46572                 record = records[i];
46573                 record.unjoin(me);
46574                 data.remove(record);
46575                 if (snapshot) {
46576                     snapshot.remove(record);
46577                 }
46578             }
46579             me.removed = [];
46580         }
46581     },
46582
46583     //inherit docs
46584     getNewRecords: function() {
46585         return this.data.filterBy(this.filterNew).items;
46586     },
46587
46588     //inherit docs
46589     getUpdatedRecords: function() {
46590         return this.data.filterBy(this.filterUpdated).items;
46591     },
46592
46593     /**
46594      * Filters the loaded set of records by a given set of filters.
46595      *
46596      * Filtering by single field:
46597      *
46598      *     store.filter("email", /\.com$/);
46599      *
46600      * Using multiple filters:
46601      *
46602      *     store.filter([
46603      *         {property: "email", value: /\.com$/},
46604      *         {filterFn: function(item) { return item.get("age") > 10; }}
46605      *     ]);
46606      *
46607      * Using Ext.util.Filter instances instead of config objects
46608      * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
46609      *
46610      *     store.filter([
46611      *         Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
46612      *         Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
46613      *     ]);
46614      *
46615      * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
46616      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
46617      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
46618      * pass in a property string
46619      * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
46620      */
46621     filter: function(filters, value) {
46622         if (Ext.isString(filters)) {
46623             filters = {
46624                 property: filters,
46625                 value: value
46626             };
46627         }
46628
46629         var me = this,
46630             decoded = me.decodeFilters(filters),
46631             i = 0,
46632             doLocalSort = me.sortOnFilter && !me.remoteSort,
46633             length = decoded.length;
46634
46635         for (; i < length; i++) {
46636             me.filters.replace(decoded[i]);
46637         }
46638
46639         if (me.remoteFilter) {
46640             //the load function will pick up the new filters and request the filtered data from the proxy
46641             me.load();
46642         } else {
46643             /**
46644              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
46645              * records when a filter is removed or changed
46646              * @property snapshot
46647              * @type Ext.util.MixedCollection
46648              */
46649             if (me.filters.getCount()) {
46650                 me.snapshot = me.snapshot || me.data.clone();
46651                 me.data = me.data.filter(me.filters.items);
46652
46653                 if (doLocalSort) {
46654                     me.sort();
46655                 }
46656                 // fire datachanged event if it hasn't already been fired by doSort
46657                 if (!doLocalSort || me.sorters.length < 1) {
46658                     me.fireEvent('datachanged', me);
46659                 }
46660             }
46661         }
46662     },
46663
46664     /**
46665      * Revert to a view of the Record cache with no filtering applied.
46666      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
46667      * {@link #datachanged} event.
46668      */
46669     clearFilter: function(suppressEvent) {
46670         var me = this;
46671
46672         me.filters.clear();
46673
46674         if (me.remoteFilter) {
46675             me.load();
46676         } else if (me.isFiltered()) {
46677             me.data = me.snapshot.clone();
46678             delete me.snapshot;
46679
46680             if (suppressEvent !== true) {
46681                 me.fireEvent('datachanged', me);
46682             }
46683         }
46684     },
46685
46686     /**
46687      * Returns true if this store is currently filtered
46688      * @return {Boolean}
46689      */
46690     isFiltered: function() {
46691         var snapshot = this.snapshot;
46692         return !! snapshot && snapshot !== this.data;
46693     },
46694
46695     /**
46696      * Filter by a function. The specified function will be called for each
46697      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
46698      * otherwise it is filtered out.
46699      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
46700      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
46701      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
46702      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
46703      * </ul>
46704      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
46705      */
46706     filterBy: function(fn, scope) {
46707         var me = this;
46708
46709         me.snapshot = me.snapshot || me.data.clone();
46710         me.data = me.queryBy(fn, scope || me);
46711         me.fireEvent('datachanged', me);
46712     },
46713
46714     /**
46715      * Query the cached records in this Store using a filtering function. The specified function
46716      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
46717      * included in the results.
46718      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
46719      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
46720      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
46721      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
46722      * </ul>
46723      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
46724      * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
46725      **/
46726     queryBy: function(fn, scope) {
46727         var me = this,
46728         data = me.snapshot || me.data;
46729         return data.filterBy(fn, scope || me);
46730     },
46731
46732     /**
46733      * Loads an array of data straight into the Store.
46734      * 
46735      * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
46736      * processed by a reader). If your data requires processing to decode the data structure, use a
46737      * {@link Ext.data.proxy.Memory MemoryProxy} instead.
46738      * 
46739      * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
46740      * into model instances.
46741      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
46742      * to remove the old ones first.
46743      */
46744     loadData: function(data, append) {
46745         var model = this.model,
46746             length = data.length,
46747             newData = [],
46748             i,
46749             record;
46750
46751         //make sure each data element is an Ext.data.Model instance
46752         for (i = 0; i < length; i++) {
46753             record = data[i];
46754
46755             if (!(record instanceof Ext.data.Model)) {
46756                 record = Ext.ModelManager.create(record, model);
46757             }
46758             newData.push(record);
46759         }
46760
46761         this.loadRecords(newData, {addRecords: append});
46762     },
46763
46764
46765     /**
46766      * Loads data via the bound Proxy's reader
46767      *
46768      * Use this method if you are attempting to load data and want to utilize the configured data reader.
46769      *
46770      * @param {Object[]} data The full JSON object you'd like to load into the Data store.
46771      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
46772      * to remove the old ones first.
46773      */
46774     loadRawData : function(data, append) {
46775          var me      = this,
46776              result  = me.proxy.reader.read(data),
46777              records = result.records;
46778
46779          if (result.success) {
46780              me.loadRecords(records, { addRecords: append });
46781              me.fireEvent('load', me, records, true);
46782          }
46783      },
46784
46785
46786     /**
46787      * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
46788      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
46789      * @param {Ext.data.Model[]} records The array of records to load
46790      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
46791      */
46792     loadRecords: function(records, options) {
46793         var me     = this,
46794             i      = 0,
46795             length = records.length;
46796
46797         options = options || {};
46798
46799
46800         if (!options.addRecords) {
46801             delete me.snapshot;
46802             me.clearData();
46803         }
46804
46805         me.data.addAll(records);
46806
46807         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
46808         for (; i < length; i++) {
46809             if (options.start !== undefined) {
46810                 records[i].index = options.start + i;
46811
46812             }
46813             records[i].join(me);
46814         }
46815
46816         /*
46817          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
46818          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
46819          * datachanged event is fired by the call to this.add, above.
46820          */
46821         me.suspendEvents();
46822
46823         if (me.filterOnLoad && !me.remoteFilter) {
46824             me.filter();
46825         }
46826
46827         if (me.sortOnLoad && !me.remoteSort) {
46828             me.sort();
46829         }
46830
46831         me.resumeEvents();
46832         me.fireEvent('datachanged', me, records);
46833     },
46834
46835     // PAGING METHODS
46836     /**
46837      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
46838      * load operation, passing in calculated 'start' and 'limit' params
46839      * @param {Number} page The number of the page to load
46840      * @param {Object} options See options for {@link #load}
46841      */
46842     loadPage: function(page, options) {
46843         var me = this;
46844         options = Ext.apply({}, options);
46845
46846         me.currentPage = page;
46847
46848         me.read(Ext.applyIf(options, {
46849             page: page,
46850             start: (page - 1) * me.pageSize,
46851             limit: me.pageSize,
46852             addRecords: !me.clearOnPageLoad
46853         }));
46854     },
46855
46856     /**
46857      * Loads the next 'page' in the current data set
46858      * @param {Object} options See options for {@link #load}
46859      */
46860     nextPage: function(options) {
46861         this.loadPage(this.currentPage + 1, options);
46862     },
46863
46864     /**
46865      * Loads the previous 'page' in the current data set
46866      * @param {Object} options See options for {@link #load}
46867      */
46868     previousPage: function(options) {
46869         this.loadPage(this.currentPage - 1, options);
46870     },
46871
46872     // private
46873     clearData: function() {
46874         var me = this;
46875         me.data.each(function(record) {
46876             record.unjoin(me);
46877         });
46878
46879         me.data.clear();
46880     },
46881
46882     // Buffering
46883     /**
46884      * Prefetches data into the store using its configured {@link #proxy}.
46885      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
46886      * See {@link #load}
46887      */
46888     prefetch: function(options) {
46889         var me = this,
46890             operation,
46891             requestId = me.getRequestId();
46892
46893         options = options || {};
46894
46895         Ext.applyIf(options, {
46896             action : 'read',
46897             filters: me.filters.items,
46898             sorters: me.sorters.items,
46899             requestId: requestId
46900         });
46901         me.pendingRequests.push(requestId);
46902
46903         operation = Ext.create('Ext.data.Operation', options);
46904
46905         // HACK to implement loadMask support.
46906         //if (operation.blocking) {
46907         //    me.fireEvent('beforeload', me, operation);
46908         //}
46909         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
46910             me.loading = true;
46911             me.proxy.read(operation, me.onProxyPrefetch, me);
46912         }
46913
46914         return me;
46915     },
46916
46917     /**
46918      * Prefetches a page of data.
46919      * @param {Number} page The page to prefetch
46920      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
46921      * See {@link #load}
46922      */
46923     prefetchPage: function(page, options) {
46924         var me = this,
46925             pageSize = me.pageSize,
46926             start = (page - 1) * me.pageSize,
46927             end = start + pageSize;
46928
46929         // Currently not requesting this page and range isn't already satisified
46930         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
46931             options = options || {};
46932             me.pagesRequested.push(page);
46933             Ext.applyIf(options, {
46934                 page : page,
46935                 start: start,
46936                 limit: pageSize,
46937                 callback: me.onWaitForGuarantee,
46938                 scope: me
46939             });
46940
46941             me.prefetch(options);
46942         }
46943
46944     },
46945
46946     /**
46947      * Returns a unique requestId to track requests.
46948      * @private
46949      */
46950     getRequestId: function() {
46951         this.requestSeed = this.requestSeed || 1;
46952         return this.requestSeed++;
46953     },
46954
46955     /**
46956      * Called after the configured proxy completes a prefetch operation.
46957      * @private
46958      * @param {Ext.data.Operation} operation The operation that completed
46959      */
46960     onProxyPrefetch: function(operation) {
46961         var me         = this,
46962             resultSet  = operation.getResultSet(),
46963             records    = operation.getRecords(),
46964
46965             successful = operation.wasSuccessful();
46966
46967         if (resultSet) {
46968             me.totalCount = resultSet.total;
46969             me.fireEvent('totalcountchange', me.totalCount);
46970         }
46971
46972         if (successful) {
46973             me.cacheRecords(records, operation);
46974         }
46975         Ext.Array.remove(me.pendingRequests, operation.requestId);
46976         if (operation.page) {
46977             Ext.Array.remove(me.pagesRequested, operation.page);
46978         }
46979
46980         me.loading = false;
46981         me.fireEvent('prefetch', me, records, successful, operation);
46982
46983         // HACK to support loadMask
46984         if (operation.blocking) {
46985             me.fireEvent('load', me, records, successful);
46986         }
46987
46988         //this is a callback that would have been passed to the 'read' function and is optional
46989         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
46990     },
46991
46992     /**
46993      * Caches the records in the prefetch and stripes them with their server-side
46994      * index.
46995      * @private
46996      * @param {Ext.data.Model[]} records The records to cache
46997      * @param {Ext.data.Operation} The associated operation
46998      */
46999     cacheRecords: function(records, operation) {
47000         var me     = this,
47001             i      = 0,
47002             length = records.length,
47003             start  = operation ? operation.start : 0;
47004
47005         if (!Ext.isDefined(me.totalCount)) {
47006             me.totalCount = records.length;
47007             me.fireEvent('totalcountchange', me.totalCount);
47008         }
47009
47010         for (; i < length; i++) {
47011             // this is the true index, not the viewIndex
47012             records[i].index = start + i;
47013         }
47014
47015         me.prefetchData.addAll(records);
47016         if (me.purgePageCount) {
47017             me.purgeRecords();
47018         }
47019
47020     },
47021
47022
47023     /**
47024      * Purge the least recently used records in the prefetch if the purgeCount
47025      * has been exceeded.
47026      */
47027     purgeRecords: function() {
47028         var me = this,
47029             prefetchCount = me.prefetchData.getCount(),
47030             purgeCount = me.purgePageCount * me.pageSize,
47031             numRecordsToPurge = prefetchCount - purgeCount - 1,
47032             i = 0;
47033
47034         for (; i <= numRecordsToPurge; i++) {
47035             me.prefetchData.removeAt(0);
47036         }
47037     },
47038
47039     /**
47040      * Determines if the range has already been satisfied in the prefetchData.
47041      * @private
47042      * @param {Number} start The start index
47043      * @param {Number} end The end index in the range
47044      */
47045     rangeSatisfied: function(start, end) {
47046         var me = this,
47047             i = start,
47048             satisfied = true;
47049
47050         for (; i < end; i++) {
47051             if (!me.prefetchData.getByKey(i)) {
47052                 satisfied = false;
47053                 //<debug>
47054                 if (end - i > me.pageSize) {
47055                     Ext.Error.raise("A single page prefetch could never satisfy this request.");
47056                 }
47057                 //</debug>
47058                 break;
47059             }
47060         }
47061         return satisfied;
47062     },
47063
47064     /**
47065      * Determines the page from a record index
47066      * @param {Number} index The record index
47067      * @return {Number} The page the record belongs to
47068      */
47069     getPageFromRecordIndex: function(index) {
47070         return Math.floor(index / this.pageSize) + 1;
47071     },
47072
47073     /**
47074      * Handles a guaranteed range being loaded
47075      * @private
47076      */
47077     onGuaranteedRange: function() {
47078         var me = this,
47079             totalCount = me.getTotalCount(),
47080             start = me.requestStart,
47081             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
47082             range = [],
47083             record,
47084             i = start;
47085
47086         end = Math.max(0, end);
47087
47088         //<debug>
47089         if (start > end) {
47090             Ext.log({
47091                 level: 'warn',
47092                 msg: 'Start (' + start + ') was greater than end (' + end +
47093                     ') for the range of records requested (' + me.requestStart + '-' +
47094                     me.requestEnd + ')' + (this.storeId ? ' from store "' + this.storeId + '"' : '')
47095             });
47096         }
47097         //</debug>
47098
47099         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
47100             me.guaranteedStart = start;
47101             me.guaranteedEnd = end;
47102
47103             for (; i <= end; i++) {
47104                 record = me.prefetchData.getByKey(i);
47105                 //<debug>
47106 //                if (!record) {
47107 //                    Ext.log('Record with key "' + i + '" was not found and store said it was guaranteed');
47108 //                }
47109                 //</debug>
47110                 if (record) {
47111                     range.push(record);
47112                 }
47113             }
47114             me.fireEvent('guaranteedrange', range, start, end);
47115             if (me.cb) {
47116                 me.cb.call(me.scope || me, range);
47117             }
47118         }
47119
47120         me.unmask();
47121     },
47122
47123     // hack to support loadmask
47124     mask: function() {
47125         this.masked = true;
47126         this.fireEvent('beforeload');
47127     },
47128
47129     // hack to support loadmask
47130     unmask: function() {
47131         if (this.masked) {
47132             this.fireEvent('load');
47133         }
47134     },
47135
47136     /**
47137      * Returns the number of pending requests out.
47138      */
47139     hasPendingRequests: function() {
47140         return this.pendingRequests.length;
47141     },
47142
47143
47144     // wait until all requests finish, until guaranteeing the range.
47145     onWaitForGuarantee: function() {
47146         if (!this.hasPendingRequests()) {
47147             this.onGuaranteedRange();
47148         }
47149     },
47150
47151     /**
47152      * Guarantee a specific range, this will load the store with a range (that
47153      * must be the pageSize or smaller) and take care of any loading that may
47154      * be necessary.
47155      */
47156     guaranteeRange: function(start, end, cb, scope) {
47157         //<debug>
47158         if (start && end) {
47159             if (end - start > this.pageSize) {
47160                 Ext.Error.raise({
47161                     start: start,
47162                     end: end,
47163                     pageSize: this.pageSize,
47164                     msg: "Requested a bigger range than the specified pageSize"
47165                 });
47166             }
47167         }
47168         //</debug>
47169
47170         end = (end > this.totalCount) ? this.totalCount - 1 : end;
47171
47172         var me = this,
47173             i = start,
47174             prefetchData = me.prefetchData,
47175             range = [],
47176             startLoaded = !!prefetchData.getByKey(start),
47177             endLoaded = !!prefetchData.getByKey(end),
47178             startPage = me.getPageFromRecordIndex(start),
47179             endPage = me.getPageFromRecordIndex(end);
47180
47181         me.cb = cb;
47182         me.scope = scope;
47183
47184         me.requestStart = start;
47185         me.requestEnd = end;
47186         // neither beginning or end are loaded
47187         if (!startLoaded || !endLoaded) {
47188             // same page, lets load it
47189             if (startPage === endPage) {
47190                 me.mask();
47191                 me.prefetchPage(startPage, {
47192                     //blocking: true,
47193                     callback: me.onWaitForGuarantee,
47194                     scope: me
47195                 });
47196             // need to load two pages
47197             } else {
47198                 me.mask();
47199                 me.prefetchPage(startPage, {
47200                     //blocking: true,
47201                     callback: me.onWaitForGuarantee,
47202                     scope: me
47203                 });
47204                 me.prefetchPage(endPage, {
47205                     //blocking: true,
47206                     callback: me.onWaitForGuarantee,
47207                     scope: me
47208                 });
47209             }
47210         // Request was already satisfied via the prefetch
47211         } else {
47212             me.onGuaranteedRange();
47213         }
47214     },
47215
47216     // because prefetchData is stored by index
47217     // this invalidates all of the prefetchedData
47218     sort: function() {
47219         var me = this,
47220             prefetchData = me.prefetchData,
47221             sorters,
47222             start,
47223             end,
47224             range;
47225
47226         if (me.buffered) {
47227             if (me.remoteSort) {
47228                 prefetchData.clear();
47229                 me.callParent(arguments);
47230             } else {
47231                 sorters = me.getSorters();
47232                 start = me.guaranteedStart;
47233                 end = me.guaranteedEnd;
47234
47235                 if (sorters.length) {
47236                     prefetchData.sort(sorters);
47237                     range = prefetchData.getRange();
47238                     prefetchData.clear();
47239                     me.cacheRecords(range);
47240                     delete me.guaranteedStart;
47241                     delete me.guaranteedEnd;
47242                     me.guaranteeRange(start, end);
47243                 }
47244                 me.callParent(arguments);
47245             }
47246         } else {
47247             me.callParent(arguments);
47248         }
47249     },
47250
47251     // overriden to provide striping of the indexes as sorting occurs.
47252     // this cannot be done inside of sort because datachanged has already
47253     // fired and will trigger a repaint of the bound view.
47254     doSort: function(sorterFn) {
47255         var me = this;
47256         if (me.remoteSort) {
47257             //the load function will pick up the new sorters and request the sorted data from the proxy
47258             me.load();
47259         } else {
47260             me.data.sortBy(sorterFn);
47261             if (!me.buffered) {
47262                 var range = me.getRange(),
47263                     ln = range.length,
47264                     i  = 0;
47265                 for (; i < ln; i++) {
47266                     range[i].index = i;
47267                 }
47268             }
47269             me.fireEvent('datachanged', me);
47270         }
47271     },
47272
47273     /**
47274      * Finds the index of the first matching Record in this store by a specific field value.
47275      * @param {String} fieldName The name of the Record field to test.
47276      * @param {String/RegExp} value Either a string that the field value
47277      * should begin with, or a RegExp to test against the field.
47278      * @param {Number} startIndex (optional) The index to start searching at
47279      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
47280      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
47281      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
47282      * @return {Number} The matched index or -1
47283      */
47284     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
47285         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
47286         return fn ? this.data.findIndexBy(fn, null, start) : -1;
47287     },
47288
47289     /**
47290      * Finds the first matching Record in this store by a specific field value.
47291      * @param {String} fieldName The name of the Record field to test.
47292      * @param {String/RegExp} value Either a string that the field value
47293      * should begin with, or a RegExp to test against the field.
47294      * @param {Number} startIndex (optional) The index to start searching at
47295      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
47296      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
47297      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
47298      * @return {Ext.data.Model} The matched record or null
47299      */
47300     findRecord: function() {
47301         var me = this,
47302             index = me.find.apply(me, arguments);
47303         return index !== -1 ? me.getAt(index) : null;
47304     },
47305
47306     /**
47307      * @private
47308      * Returns a filter function used to test a the given property's value. Defers most of the work to
47309      * Ext.util.MixedCollection's createValueMatcher function
47310      * @param {String} property The property to create the filter function for
47311      * @param {String/RegExp} value The string/regex to compare the property value to
47312      * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
47313      * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
47314      * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
47315      * Ignored if anyMatch is true.
47316      */
47317     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
47318         if (Ext.isEmpty(value)) {
47319             return false;
47320         }
47321         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
47322         return function(r) {
47323             return value.test(r.data[property]);
47324         };
47325     },
47326
47327     /**
47328      * Finds the index of the first matching Record in this store by a specific field value.
47329      * @param {String} fieldName The name of the Record field to test.
47330      * @param {Object} value The value to match the field against.
47331      * @param {Number} startIndex (optional) The index to start searching at
47332      * @return {Number} The matched index or -1
47333      */
47334     findExact: function(property, value, start) {
47335         return this.data.findIndexBy(function(rec) {
47336             return rec.get(property) == value;
47337         },
47338         this, start);
47339     },
47340
47341     /**
47342      * Find the index of the first matching Record in this Store by a function.
47343      * If the function returns <tt>true</tt> it is considered a match.
47344      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
47345      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
47346      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
47347      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
47348      * </ul>
47349      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
47350      * @param {Number} startIndex (optional) The index to start searching at
47351      * @return {Number} The matched index or -1
47352      */
47353     findBy: function(fn, scope, start) {
47354         return this.data.findIndexBy(fn, scope, start);
47355     },
47356
47357     /**
47358      * Collects unique values for a particular dataIndex from this store.
47359      * @param {String} dataIndex The property to collect
47360      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
47361      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
47362      * @return {Object[]} An array of the unique values
47363      **/
47364     collect: function(dataIndex, allowNull, bypassFilter) {
47365         var me = this,
47366             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
47367
47368         return data.collect(dataIndex, 'data', allowNull);
47369     },
47370
47371     /**
47372      * Gets the number of cached records.
47373      * <p>If using paging, this may not be the total size of the dataset. If the data object
47374      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
47375      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
47376      * @return {Number} The number of Records in the Store's cache.
47377      */
47378     getCount: function() {
47379         return this.data.length || 0;
47380     },
47381
47382     /**
47383      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
47384      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
47385      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
47386      * could be loaded into the Store if the Store contained all data
47387      * @return {Number} The total number of Model instances available via the Proxy
47388      */
47389     getTotalCount: function() {
47390         return this.totalCount;
47391     },
47392
47393     /**
47394      * Get the Record at the specified index.
47395      * @param {Number} index The index of the Record to find.
47396      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
47397      */
47398     getAt: function(index) {
47399         return this.data.getAt(index);
47400     },
47401
47402     /**
47403      * Returns a range of Records between specified indices.
47404      * @param {Number} [startIndex=0] The starting index
47405      * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
47406      * @return {Ext.data.Model[]} An array of Records
47407      */
47408     getRange: function(start, end) {
47409         return this.data.getRange(start, end);
47410     },
47411
47412     /**
47413      * Get the Record with the specified id.
47414      * @param {String} id The id of the Record to find.
47415      * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
47416      */
47417     getById: function(id) {
47418         return (this.snapshot || this.data).findBy(function(record) {
47419             return record.getId() === id;
47420         });
47421     },
47422
47423     /**
47424      * Get the index within the cache of the passed Record.
47425      * @param {Ext.data.Model} record The Ext.data.Model object to find.
47426      * @return {Number} The index of the passed Record. Returns -1 if not found.
47427      */
47428     indexOf: function(record) {
47429         return this.data.indexOf(record);
47430     },
47431
47432
47433     /**
47434      * Get the index within the entire dataset. From 0 to the totalCount.
47435      * @param {Ext.data.Model} record The Ext.data.Model object to find.
47436      * @return {Number} The index of the passed Record. Returns -1 if not found.
47437      */
47438     indexOfTotal: function(record) {
47439         var index = record.index;
47440         if (index || index === 0) {
47441             return index;
47442         }
47443         return this.indexOf(record);
47444     },
47445
47446     /**
47447      * Get the index within the cache of the Record with the passed id.
47448      * @param {String} id The id of the Record to find.
47449      * @return {Number} The index of the Record. Returns -1 if not found.
47450      */
47451     indexOfId: function(id) {
47452         return this.indexOf(this.getById(id));
47453     },
47454
47455     /**
47456      * Remove all items from the store.
47457      * @param {Boolean} silent Prevent the `clear` event from being fired.
47458      */
47459     removeAll: function(silent) {
47460         var me = this;
47461
47462         me.clearData();
47463         if (me.snapshot) {
47464             me.snapshot.clear();
47465         }
47466         if (silent !== true) {
47467             me.fireEvent('clear', me);
47468         }
47469     },
47470
47471     /*
47472      * Aggregation methods
47473      */
47474
47475     /**
47476      * Convenience function for getting the first model instance in the store
47477      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47478      * in the store. The value returned will be an object literal with the key being the group
47479      * name and the first record being the value. The grouped parameter is only honored if
47480      * the store has a groupField.
47481      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
47482      */
47483     first: function(grouped) {
47484         var me = this;
47485
47486         if (grouped && me.isGrouped()) {
47487             return me.aggregate(function(records) {
47488                 return records.length ? records[0] : undefined;
47489             }, me, true);
47490         } else {
47491             return me.data.first();
47492         }
47493     },
47494
47495     /**
47496      * Convenience function for getting the last model instance in the store
47497      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47498      * in the store. The value returned will be an object literal with the key being the group
47499      * name and the last record being the value. The grouped parameter is only honored if
47500      * the store has a groupField.
47501      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
47502      */
47503     last: function(grouped) {
47504         var me = this;
47505
47506         if (grouped && me.isGrouped()) {
47507             return me.aggregate(function(records) {
47508                 var len = records.length;
47509                 return len ? records[len - 1] : undefined;
47510             }, me, true);
47511         } else {
47512             return me.data.last();
47513         }
47514     },
47515
47516     /**
47517      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
47518      * and <tt>end</tt> and returns the result.
47519      * @param {String} field A field in each record
47520      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47521      * in the store. The value returned will be an object literal with the key being the group
47522      * name and the sum for that group being the value. The grouped parameter is only honored if
47523      * the store has a groupField.
47524      * @return {Number} The sum
47525      */
47526     sum: function(field, grouped) {
47527         var me = this;
47528
47529         if (grouped && me.isGrouped()) {
47530             return me.aggregate(me.getSum, me, true, [field]);
47531         } else {
47532             return me.getSum(me.data.items, field);
47533         }
47534     },
47535
47536     // @private, see sum
47537     getSum: function(records, field) {
47538         var total = 0,
47539             i = 0,
47540             len = records.length;
47541
47542         for (; i < len; ++i) {
47543             total += records[i].get(field);
47544         }
47545
47546         return total;
47547     },
47548
47549     /**
47550      * Gets the count of items in the store.
47551      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47552      * in the store. The value returned will be an object literal with the key being the group
47553      * name and the count for each group being the value. The grouped parameter is only honored if
47554      * the store has a groupField.
47555      * @return {Number} the count
47556      */
47557     count: function(grouped) {
47558         var me = this;
47559
47560         if (grouped && me.isGrouped()) {
47561             return me.aggregate(function(records) {
47562                 return records.length;
47563             }, me, true);
47564         } else {
47565             return me.getCount();
47566         }
47567     },
47568
47569     /**
47570      * Gets the minimum value in the store.
47571      * @param {String} field The field in each record
47572      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47573      * in the store. The value returned will be an object literal with the key being the group
47574      * name and the minimum in the group being the value. The grouped parameter is only honored if
47575      * the store has a groupField.
47576      * @return {Object} The minimum value, if no items exist, undefined.
47577      */
47578     min: function(field, grouped) {
47579         var me = this;
47580
47581         if (grouped && me.isGrouped()) {
47582             return me.aggregate(me.getMin, me, true, [field]);
47583         } else {
47584             return me.getMin(me.data.items, field);
47585         }
47586     },
47587
47588     // @private, see min
47589     getMin: function(records, field){
47590         var i = 1,
47591             len = records.length,
47592             value, min;
47593
47594         if (len > 0) {
47595             min = records[0].get(field);
47596         }
47597
47598         for (; i < len; ++i) {
47599             value = records[i].get(field);
47600             if (value < min) {
47601                 min = value;
47602             }
47603         }
47604         return min;
47605     },
47606
47607     /**
47608      * Gets the maximum value in the store.
47609      * @param {String} field The field in each record
47610      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47611      * in the store. The value returned will be an object literal with the key being the group
47612      * name and the maximum in the group being the value. The grouped parameter is only honored if
47613      * the store has a groupField.
47614      * @return {Object} The maximum value, if no items exist, undefined.
47615      */
47616     max: function(field, grouped) {
47617         var me = this;
47618
47619         if (grouped && me.isGrouped()) {
47620             return me.aggregate(me.getMax, me, true, [field]);
47621         } else {
47622             return me.getMax(me.data.items, field);
47623         }
47624     },
47625
47626     // @private, see max
47627     getMax: function(records, field) {
47628         var i = 1,
47629             len = records.length,
47630             value,
47631             max;
47632
47633         if (len > 0) {
47634             max = records[0].get(field);
47635         }
47636
47637         for (; i < len; ++i) {
47638             value = records[i].get(field);
47639             if (value > max) {
47640                 max = value;
47641             }
47642         }
47643         return max;
47644     },
47645
47646     /**
47647      * Gets the average value in the store.
47648      * @param {String} field The field in each record
47649      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47650      * in the store. The value returned will be an object literal with the key being the group
47651      * name and the group average being the value. The grouped parameter is only honored if
47652      * the store has a groupField.
47653      * @return {Object} The average value, if no items exist, 0.
47654      */
47655     average: function(field, grouped) {
47656         var me = this;
47657         if (grouped && me.isGrouped()) {
47658             return me.aggregate(me.getAverage, me, true, [field]);
47659         } else {
47660             return me.getAverage(me.data.items, field);
47661         }
47662     },
47663
47664     // @private, see average
47665     getAverage: function(records, field) {
47666         var i = 0,
47667             len = records.length,
47668             sum = 0;
47669
47670         if (records.length > 0) {
47671             for (; i < len; ++i) {
47672                 sum += records[i].get(field);
47673             }
47674             return sum / len;
47675         }
47676         return 0;
47677     },
47678
47679     /**
47680      * Runs the aggregate function for all the records in the store.
47681      * @param {Function} fn The function to execute. The function is called with a single parameter,
47682      * an array of records for that group.
47683      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
47684      * @param {Boolean} grouped (Optional) True to perform the operation for each group
47685      * in the store. The value returned will be an object literal with the key being the group
47686      * name and the group average being the value. The grouped parameter is only honored if
47687      * the store has a groupField.
47688      * @param {Array} args (optional) Any arguments to append to the function call
47689      * @return {Object} An object literal with the group names and their appropriate values.
47690      */
47691     aggregate: function(fn, scope, grouped, args) {
47692         args = args || [];
47693         if (grouped && this.isGrouped()) {
47694             var groups = this.getGroups(),
47695                 i = 0,
47696                 len = groups.length,
47697                 out = {},
47698                 group;
47699
47700             for (; i < len; ++i) {
47701                 group = groups[i];
47702                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
47703             }
47704             return out;
47705         } else {
47706             return fn.apply(scope || this, [this.data.items].concat(args));
47707         }
47708     }
47709 }, function() {
47710     // A dummy empty store with a fieldless Model defined in it.
47711     // Just for binding to Views which are instantiated with no Store defined.
47712     // They will be able to run and render fine, and be bound to a generated Store later.
47713     Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
47714 });
47715
47716 /**
47717  * @author Ed Spencer
47718  * @class Ext.data.JsonStore
47719  * @extends Ext.data.Store
47720  * @ignore
47721  *
47722  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
47723  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
47724  *
47725  * <p>A store configuration would be something like:</p>
47726  *
47727 <pre><code>
47728 var store = new Ext.data.JsonStore({
47729     // store configs
47730     autoDestroy: true,
47731     storeId: 'myStore',
47732
47733     proxy: {
47734         type: 'ajax',
47735         url: 'get-images.php',
47736         reader: {
47737             type: 'json',
47738             root: 'images',
47739             idProperty: 'name'
47740         }
47741     },
47742
47743     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
47744     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
47745 });
47746 </code></pre>
47747  *
47748  * <p>This store is configured to consume a returned object of the form:<pre><code>
47749 {
47750     images: [
47751         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
47752         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
47753     ]
47754 }
47755 </code></pre>
47756  *
47757  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
47758  *
47759  * @xtype jsonstore
47760  */
47761 Ext.define('Ext.data.JsonStore',  {
47762     extend: 'Ext.data.Store',
47763     alias: 'store.json',
47764
47765     /**
47766      * @cfg {Ext.data.DataReader} reader @hide
47767      */
47768     constructor: function(config) {
47769         config = config || {};
47770
47771         Ext.applyIf(config, {
47772             proxy: {
47773                 type  : 'ajax',
47774                 reader: 'json',
47775                 writer: 'json'
47776             }
47777         });
47778
47779         this.callParent([config]);
47780     }
47781 });
47782
47783 /**
47784  * @class Ext.chart.axis.Time
47785  * @extends Ext.chart.axis.Numeric
47786  *
47787  * A type of axis whose units are measured in time values. Use this axis
47788  * for listing dates that you will want to group or dynamically change.
47789  * If you just want to display dates as categories then use the
47790  * Category class for axis instead.
47791  *
47792  * For example:
47793  *
47794  *     axes: [{
47795  *         type: 'Time',
47796  *         position: 'bottom',
47797  *         fields: 'date',
47798  *         title: 'Day',
47799  *         dateFormat: 'M d',
47800  *
47801  *         constrain: true,
47802  *         fromDate: new Date('1/1/11'),
47803  *         toDate: new Date('1/7/11')
47804  *     }]
47805  *
47806  * In this example we're creating a time axis that has as title *Day*.
47807  * The field the axis is bound to is `date`.
47808  * The date format to use to display the text for the axis labels is `M d`
47809  * which is a three letter month abbreviation followed by the day number.
47810  * The time axis will show values for dates between `fromDate` and `toDate`.
47811  * Since `constrain` is set to true all other values for other dates not between
47812  * the fromDate and toDate will not be displayed.
47813  *
47814  */
47815 Ext.define('Ext.chart.axis.Time', {
47816
47817     /* Begin Definitions */
47818
47819     extend: 'Ext.chart.axis.Numeric',
47820
47821     alternateClassName: 'Ext.chart.TimeAxis',
47822
47823     alias: 'axis.time',
47824
47825     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
47826
47827     /* End Definitions */
47828
47829     /**
47830      * @cfg {String/Boolean} dateFormat
47831      * Indicates the format the date will be rendered on.
47832      * For example: 'M d' will render the dates as 'Jan 30', etc.
47833      * For a list of possible format strings see {@link Ext.Date Date}
47834      */
47835     dateFormat: false,
47836
47837     /**
47838      * @cfg {Date} fromDate The starting date for the time axis.
47839      */
47840     fromDate: false,
47841
47842     /**
47843      * @cfg {Date} toDate The ending date for the time axis.
47844      */
47845     toDate: false,
47846
47847     /**
47848      * @cfg {Array/Boolean} step
47849      * An array with two components: The first is the unit of the step (day, month, year, etc).
47850      * The second one is the number of units for the step (1, 2, etc.).
47851      * Defaults to `[Ext.Date.DAY, 1]`.
47852      */
47853     step: [Ext.Date.DAY, 1],
47854     
47855     /**
47856      * @cfg {Boolean} constrain
47857      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
47858      * If false, the time axis will adapt to the new values by adding/removing steps.
47859      */
47860     constrain: false,
47861
47862     // Avoid roundtoDecimal call in Numeric Axis's constructor
47863     roundToDecimal: false,
47864     
47865     constructor: function (config) {
47866         var me = this, label, f, df;
47867         me.callParent([config]);
47868         label = me.label || {};
47869         df = this.dateFormat;
47870         if (df) {
47871             if (label.renderer) {
47872                 f = label.renderer;
47873                 label.renderer = function(v) {
47874                     v = f(v);
47875                     return Ext.Date.format(new Date(f(v)), df);
47876                 };
47877             } else {
47878                 label.renderer = function(v) {
47879                     return Ext.Date.format(new Date(v >> 0), df);
47880                 };
47881             }
47882         }
47883     },
47884
47885     doConstrain: function () {
47886         var me = this,
47887             store = me.chart.store,
47888             data = [],
47889             series = me.chart.series.items,
47890             math = Math,
47891             mmax = math.max,
47892             mmin = math.min,
47893             fields = me.fields,
47894             ln = fields.length,
47895             range = me.getRange(),
47896             min = range.min, max = range.max, i, l, excludes = [],
47897             value, values, rec, data = [];
47898         for (i = 0, l = series.length; i < l; i++) {
47899             excludes[i] = series[i].__excludes;
47900         }
47901         store.each(function(record) {
47902             for (i = 0; i < ln; i++) {
47903                 if (excludes[i]) {
47904                     continue;
47905                 }
47906                 value = record.get(fields[i]);
47907                 if (+value < +min) return;
47908                 if (+value > +max) return;
47909             }
47910             data.push(record);
47911         })
47912         me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
47913     },
47914
47915     // Before rendering, set current default step count to be number of records.
47916     processView: function () {
47917         var me = this;
47918         if (me.fromDate) {
47919             me.minimum = +me.fromDate;
47920         }
47921         if (me.toDate) {
47922             me.maximum = +me.toDate;
47923         }
47924         if (me.constrain) {
47925             me.doConstrain();
47926         }
47927      },
47928
47929     // @private modifies the store and creates the labels for the axes.
47930     calcEnds: function() {
47931         var me = this, range, step = me.step;
47932         if (step) {
47933             range = me.getRange();
47934             range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
47935             if (me.minimum) {
47936                 range.from = me.minimum;
47937             }
47938             if (me.maximum) {
47939                 range.to = me.maximum;
47940             }
47941             range.step = (range.to - range.from) / range.steps;
47942             return range;
47943         } else {
47944             return me.callParent(arguments);
47945         }
47946     }
47947  });
47948
47949
47950 /**
47951  * @class Ext.chart.series.Series
47952  *
47953  * Series is the abstract class containing the common logic to all chart series. Series includes
47954  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
47955  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
47956  *
47957  * ## Listeners
47958  *
47959  * The series class supports listeners via the Observable syntax. Some of these listeners are:
47960  *
47961  *  - `itemmouseup` When the user interacts with a marker.
47962  *  - `itemmousedown` When the user interacts with a marker.
47963  *  - `itemmousemove` When the user iteracts with a marker.
47964  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
47965  *
47966  * For example:
47967  *
47968  *     series: [{
47969  *             type: 'column',
47970  *             axis: 'left',
47971  *             listeners: {
47972  *                     'afterrender': function() {
47973  *                             console('afterrender');
47974  *                     }
47975  *             },
47976  *             xField: 'category',
47977  *             yField: 'data1'
47978  *     }]
47979  */
47980 Ext.define('Ext.chart.series.Series', {
47981
47982     /* Begin Definitions */
47983
47984     mixins: {
47985         observable: 'Ext.util.Observable',
47986         labels: 'Ext.chart.Label',
47987         highlights: 'Ext.chart.Highlight',
47988         tips: 'Ext.chart.Tip',
47989         callouts: 'Ext.chart.Callout'
47990     },
47991
47992     /* End Definitions */
47993
47994     /**
47995      * @cfg {Boolean/Object} highlight
47996      * If set to `true` it will highlight the markers or the series when hovering
47997      * with the mouse. This parameter can also be an object with the same style
47998      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
47999      * styles to markers and series.
48000      */
48001
48002     /**
48003      * @cfg {Object} tips
48004      * Add tooltips to the visualization's markers. The options for the tips are the
48005      * same configuration used with {@link Ext.tip.ToolTip}. For example:
48006      *
48007      *     tips: {
48008      *       trackMouse: true,
48009      *       width: 140,
48010      *       height: 28,
48011      *       renderer: function(storeItem, item) {
48012      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
48013      *       }
48014      *     },
48015      */
48016
48017     /**
48018      * @cfg {String} type
48019      * The type of series. Set in subclasses.
48020      */
48021     type: null,
48022
48023     /**
48024      * @cfg {String} title
48025      * The human-readable name of the series.
48026      */
48027     title: null,
48028
48029     /**
48030      * @cfg {Boolean} showInLegend
48031      * Whether to show this series in the legend.
48032      */
48033     showInLegend: true,
48034
48035     /**
48036      * @cfg {Function} renderer
48037      * A function that can be overridden to set custom styling properties to each rendered element.
48038      * Passes in (sprite, record, attributes, index, store) to the function.
48039      */
48040     renderer: function(sprite, record, attributes, index, store) {
48041         return attributes;
48042     },
48043
48044     /**
48045      * @cfg {Array} shadowAttributes
48046      * An array with shadow attributes
48047      */
48048     shadowAttributes: null,
48049
48050     //@private triggerdrawlistener flag
48051     triggerAfterDraw: false,
48052
48053     /**
48054      * @cfg {Object} listeners
48055      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
48056      *
48057      *  - itemmouseover
48058      *  - itemmouseout
48059      *  - itemmousedown
48060      *  - itemmouseup
48061      */
48062
48063     constructor: function(config) {
48064         var me = this;
48065         if (config) {
48066             Ext.apply(me, config);
48067         }
48068
48069         me.shadowGroups = [];
48070
48071         me.mixins.labels.constructor.call(me, config);
48072         me.mixins.highlights.constructor.call(me, config);
48073         me.mixins.tips.constructor.call(me, config);
48074         me.mixins.callouts.constructor.call(me, config);
48075
48076         me.addEvents({
48077             scope: me,
48078             itemmouseover: true,
48079             itemmouseout: true,
48080             itemmousedown: true,
48081             itemmouseup: true,
48082             mouseleave: true,
48083             afterdraw: true,
48084
48085             /**
48086              * @event titlechange
48087              * Fires when the series title is changed via {@link #setTitle}.
48088              * @param {String} title The new title value
48089              * @param {Number} index The index in the collection of titles
48090              */
48091             titlechange: true
48092         });
48093
48094         me.mixins.observable.constructor.call(me, config);
48095
48096         me.on({
48097             scope: me,
48098             itemmouseover: me.onItemMouseOver,
48099             itemmouseout: me.onItemMouseOut,
48100             mouseleave: me.onMouseLeave
48101         });
48102     },
48103     
48104     /**
48105      * Iterate over each of the records for this series. The default implementation simply iterates
48106      * through the entire data store, but individual series implementations can override this to
48107      * provide custom handling, e.g. adding/removing records.
48108      * @param {Function} fn The function to execute for each record.
48109      * @param {Object} scope Scope for the fn.
48110      */
48111     eachRecord: function(fn, scope) {
48112         var chart = this.chart;
48113         (chart.substore || chart.store).each(fn, scope);
48114     },
48115
48116     /**
48117      * Return the number of records being displayed in this series. Defaults to the number of
48118      * records in the store; individual series implementations can override to provide custom handling.
48119      */
48120     getRecordCount: function() {
48121         var chart = this.chart,
48122             store = chart.substore || chart.store;
48123         return store ? store.getCount() : 0;
48124     },
48125
48126     /**
48127      * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
48128      * @param index
48129      */
48130     isExcluded: function(index) {
48131         var excludes = this.__excludes;
48132         return !!(excludes && excludes[index]);
48133     },
48134
48135     // @private set the bbox and clipBox for the series
48136     setBBox: function(noGutter) {
48137         var me = this,
48138             chart = me.chart,
48139             chartBBox = chart.chartBBox,
48140             gutterX = noGutter ? 0 : chart.maxGutter[0],
48141             gutterY = noGutter ? 0 : chart.maxGutter[1],
48142             clipBox, bbox;
48143
48144         clipBox = {
48145             x: chartBBox.x,
48146             y: chartBBox.y,
48147             width: chartBBox.width,
48148             height: chartBBox.height
48149         };
48150         me.clipBox = clipBox;
48151
48152         bbox = {
48153             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
48154             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
48155             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
48156             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
48157         };
48158         me.bbox = bbox;
48159     },
48160
48161     // @private set the animation for the sprite
48162     onAnimate: function(sprite, attr) {
48163         var me = this;
48164         sprite.stopAnimation();
48165         if (me.triggerAfterDraw) {
48166             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
48167         } else {
48168             me.triggerAfterDraw = true;
48169             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
48170                 listeners: {
48171                     'afteranimate': function() {
48172                         me.triggerAfterDraw = false;
48173                         me.fireEvent('afterrender');
48174                     }
48175                 }
48176             }));
48177         }
48178     },
48179
48180     // @private return the gutter.
48181     getGutters: function() {
48182         return [0, 0];
48183     },
48184
48185     // @private wrapper for the itemmouseover event.
48186     onItemMouseOver: function(item) {
48187         var me = this;
48188         if (item.series === me) {
48189             if (me.highlight) {
48190                 me.highlightItem(item);
48191             }
48192             if (me.tooltip) {
48193                 me.showTip(item);
48194             }
48195         }
48196     },
48197
48198     // @private wrapper for the itemmouseout event.
48199     onItemMouseOut: function(item) {
48200         var me = this;
48201         if (item.series === me) {
48202             me.unHighlightItem();
48203             if (me.tooltip) {
48204                 me.hideTip(item);
48205             }
48206         }
48207     },
48208
48209     // @private wrapper for the mouseleave event.
48210     onMouseLeave: function() {
48211         var me = this;
48212         me.unHighlightItem();
48213         if (me.tooltip) {
48214             me.hideTip();
48215         }
48216     },
48217
48218     /**
48219      * For a given x/y point relative to the Surface, find a corresponding item from this
48220      * series, if any.
48221      * @param {Number} x
48222      * @param {Number} y
48223      * @return {Object} An object describing the item, or null if there is no matching item.
48224      * The exact contents of this object will vary by series type, but should always contain the following:
48225      * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
48226      * @return {Object} return.value the value(s) of the item's data point
48227      * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
48228      * for this data item, which can be used as e.g. a tooltip anchor point.
48229      * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
48230      */
48231     getItemForPoint: function(x, y) {
48232         //if there are no items to query just return null.
48233         if (!this.items || !this.items.length || this.seriesIsHidden) {
48234             return null;
48235         }
48236         var me = this,
48237             items = me.items,
48238             bbox = me.bbox,
48239             item, i, ln;
48240         // Check bounds
48241         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
48242             return null;
48243         }
48244         for (i = 0, ln = items.length; i < ln; i++) {
48245             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
48246                 return items[i];
48247             }
48248         }
48249
48250         return null;
48251     },
48252
48253     isItemInPoint: function(x, y, item, i) {
48254         return false;
48255     },
48256
48257     /**
48258      * Hides all the elements in the series.
48259      */
48260     hideAll: function() {
48261         var me = this,
48262             items = me.items,
48263             item, len, i, j, l, sprite, shadows;
48264
48265         me.seriesIsHidden = true;
48266         me._prevShowMarkers = me.showMarkers;
48267
48268         me.showMarkers = false;
48269         //hide all labels
48270         me.hideLabels(0);
48271         //hide all sprites
48272         for (i = 0, len = items.length; i < len; i++) {
48273             item = items[i];
48274             sprite = item.sprite;
48275             if (sprite) {
48276                 sprite.setAttributes({
48277                     hidden: true
48278                 }, true);
48279             }
48280
48281             if (sprite && sprite.shadows) {
48282                 shadows = sprite.shadows;
48283                 for (j = 0, l = shadows.length; j < l; ++j) {
48284                     shadows[j].setAttributes({
48285                         hidden: true
48286                     }, true);
48287                 }
48288             }
48289         }
48290     },
48291
48292     /**
48293      * Shows all the elements in the series.
48294      */
48295     showAll: function() {
48296         var me = this,
48297             prevAnimate = me.chart.animate;
48298         me.chart.animate = false;
48299         me.seriesIsHidden = false;
48300         me.showMarkers = me._prevShowMarkers;
48301         me.drawSeries();
48302         me.chart.animate = prevAnimate;
48303     },
48304
48305     /**
48306      * Returns a string with the color to be used for the series legend item.
48307      */
48308     getLegendColor: function(index) {
48309         var me = this, fill, stroke;
48310         if (me.seriesStyle) {
48311             fill = me.seriesStyle.fill;
48312             stroke = me.seriesStyle.stroke;
48313             if (fill && fill != 'none') {
48314                 return fill;
48315             }
48316             return stroke;
48317         }
48318         return '#000';
48319     },
48320
48321     /**
48322      * Checks whether the data field should be visible in the legend
48323      * @private
48324      * @param {Number} index The index of the current item
48325      */
48326     visibleInLegend: function(index){
48327         var excludes = this.__excludes;
48328         if (excludes) {
48329             return !excludes[index];
48330         }
48331         return !this.seriesIsHidden;
48332     },
48333
48334     /**
48335      * Changes the value of the {@link #title} for the series.
48336      * Arguments can take two forms:
48337      * <ul>
48338      * <li>A single String value: this will be used as the new single title for the series (applies
48339      * to series with only one yField)</li>
48340      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
48341      * </ul>
48342      * @param {Number} index
48343      * @param {String} title
48344      */
48345     setTitle: function(index, title) {
48346         var me = this,
48347             oldTitle = me.title;
48348
48349         if (Ext.isString(index)) {
48350             title = index;
48351             index = 0;
48352         }
48353
48354         if (Ext.isArray(oldTitle)) {
48355             oldTitle[index] = title;
48356         } else {
48357             me.title = title;
48358         }
48359
48360         me.fireEvent('titlechange', title, index);
48361     }
48362 });
48363
48364 /**
48365  * @class Ext.chart.series.Cartesian
48366  * @extends Ext.chart.series.Series
48367  *
48368  * Common base class for series implementations which plot values using x/y coordinates.
48369  */
48370 Ext.define('Ext.chart.series.Cartesian', {
48371
48372     /* Begin Definitions */
48373
48374     extend: 'Ext.chart.series.Series',
48375
48376     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
48377
48378     /* End Definitions */
48379
48380     /**
48381      * The field used to access the x axis value from the items from the data
48382      * source.
48383      *
48384      * @cfg xField
48385      * @type String
48386      */
48387     xField: null,
48388
48389     /**
48390      * The field used to access the y-axis value from the items from the data
48391      * source.
48392      *
48393      * @cfg yField
48394      * @type String
48395      */
48396     yField: null,
48397
48398     /**
48399      * @cfg {String} axis
48400      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
48401      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
48402      * relative scale will be used.
48403      */
48404     axis: 'left',
48405
48406     getLegendLabels: function() {
48407         var me = this,
48408             labels = [],
48409             combinations = me.combinations;
48410
48411         Ext.each([].concat(me.yField), function(yField, i) {
48412             var title = me.title;
48413             // Use the 'title' config if present, otherwise use the raw yField name
48414             labels.push((Ext.isArray(title) ? title[i] : title) || yField);
48415         });
48416
48417         // Handle yFields combined via legend drag-drop
48418         if (combinations) {
48419             Ext.each(combinations, function(combo) {
48420                 var label0 = labels[combo[0]],
48421                     label1 = labels[combo[1]];
48422                 labels[combo[1]] = label0 + ' & ' + label1;
48423                 labels.splice(combo[0], 1);
48424             });
48425         }
48426
48427         return labels;
48428     },
48429
48430     /**
48431      * @protected Iterates over a given record's values for each of this series's yFields,
48432      * executing a given function for each value. Any yFields that have been combined
48433      * via legend drag-drop will be treated as a single value.
48434      * @param {Ext.data.Model} record
48435      * @param {Function} fn
48436      * @param {Object} scope
48437      */
48438     eachYValue: function(record, fn, scope) {
48439         Ext.each(this.getYValueAccessors(), function(accessor, i) {
48440             fn.call(scope, accessor(record), i);
48441         });
48442     },
48443
48444     /**
48445      * @protected Returns the number of yField values, taking into account fields combined
48446      * via legend drag-drop.
48447      * @return {Number}
48448      */
48449     getYValueCount: function() {
48450         return this.getYValueAccessors().length;
48451     },
48452
48453     combine: function(index1, index2) {
48454         var me = this,
48455             accessors = me.getYValueAccessors(),
48456             accessor1 = accessors[index1],
48457             accessor2 = accessors[index2];
48458
48459         // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
48460         accessors[index2] = function(record) {
48461             return accessor1(record) + accessor2(record);
48462         };
48463         accessors.splice(index1, 1);
48464
48465         me.callParent([index1, index2]);
48466     },
48467
48468     clearCombinations: function() {
48469         // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
48470         delete this.yValueAccessors;
48471         this.callParent();
48472     },
48473
48474     /**
48475      * @protected Returns an array of functions, each of which returns the value of the yField
48476      * corresponding to function's index in the array, for a given record (each function takes the
48477      * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
48478      * this list of accessors will be kept in sync with those combinations.
48479      * @return {Array} array of accessor functions
48480      */
48481     getYValueAccessors: function() {
48482         var me = this,
48483             accessors = me.yValueAccessors;
48484         if (!accessors) {
48485             accessors = me.yValueAccessors = [];
48486             Ext.each([].concat(me.yField), function(yField) {
48487                 accessors.push(function(record) {
48488                     return record.get(yField);
48489                 });
48490             });
48491         }
48492         return accessors;
48493     },
48494
48495     /**
48496      * Calculate the min and max values for this series's xField.
48497      * @return {Array} [min, max]
48498      */
48499     getMinMaxXValues: function() {
48500         var me = this,
48501             min, max,
48502             xField = me.xField;
48503
48504         if (me.getRecordCount() > 0) {
48505             min = Infinity;
48506             max = -min;
48507             me.eachRecord(function(record) {
48508                 var xValue = record.get(xField);
48509                 if (xValue > max) {
48510                     max = xValue;
48511                 }
48512                 if (xValue < min) {
48513                     min = xValue;
48514                 }
48515             });
48516         } else {
48517             min = max = 0;
48518         }
48519         return [min, max];
48520     },
48521
48522     /**
48523      * Calculate the min and max values for this series's yField(s). Takes into account yField
48524      * combinations, exclusions, and stacking.
48525      * @return {Array} [min, max]
48526      */
48527     getMinMaxYValues: function() {
48528         var me = this,
48529             stacked = me.stacked,
48530             min, max,
48531             positiveTotal, negativeTotal;
48532
48533         function eachYValueStacked(yValue, i) {
48534             if (!me.isExcluded(i)) {
48535                 if (yValue < 0) {
48536                     negativeTotal += yValue;
48537                 } else {
48538                     positiveTotal += yValue;
48539                 }
48540             }
48541         }
48542
48543         function eachYValue(yValue, i) {
48544             if (!me.isExcluded(i)) {
48545                 if (yValue > max) {
48546                     max = yValue;
48547                 }
48548                 if (yValue < min) {
48549                     min = yValue;
48550                 }
48551             }
48552         }
48553
48554         if (me.getRecordCount() > 0) {
48555             min = Infinity;
48556             max = -min;
48557             me.eachRecord(function(record) {
48558                 if (stacked) {
48559                     positiveTotal = 0;
48560                     negativeTotal = 0;
48561                     me.eachYValue(record, eachYValueStacked);
48562                     if (positiveTotal > max) {
48563                         max = positiveTotal;
48564                     }
48565                     if (negativeTotal < min) {
48566                         min = negativeTotal;
48567                     }
48568                 } else {
48569                     me.eachYValue(record, eachYValue);
48570                 }
48571             });
48572         } else {
48573             min = max = 0;
48574         }
48575         return [min, max];
48576     },
48577
48578     getAxesForXAndYFields: function() {
48579         var me = this,
48580             axes = me.chart.axes,
48581             axis = [].concat(me.axis),
48582             xAxis, yAxis;
48583
48584         if (Ext.Array.indexOf(axis, 'top') > -1) {
48585             xAxis = 'top';
48586         } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
48587             xAxis = 'bottom';
48588         } else {
48589             if (axes.get('top')) {
48590                 xAxis = 'top';
48591             } else if (axes.get('bottom')) {
48592                 xAxis = 'bottom';
48593             }
48594         }
48595
48596         if (Ext.Array.indexOf(axis, 'left') > -1) {
48597             yAxis = 'left';
48598         } else if (Ext.Array.indexOf(axis, 'right') > -1) {
48599             yAxis = 'right';
48600         } else {
48601             if (axes.get('left')) {
48602                 yAxis = 'left';
48603             } else if (axes.get('right')) {
48604                 yAxis = 'right';
48605             }
48606         }
48607
48608         return {
48609             xAxis: xAxis,
48610             yAxis: yAxis
48611         };
48612     }
48613
48614
48615 });
48616
48617 /**
48618  * @class Ext.chart.series.Area
48619  * @extends Ext.chart.series.Cartesian
48620  *
48621  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
48622  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
48623  * documentation for more information. A typical configuration object for the area series could be:
48624  *
48625  *     @example
48626  *     var store = Ext.create('Ext.data.JsonStore', {
48627  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
48628  *         data: [
48629  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
48630  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
48631  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
48632  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
48633  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
48634  *         ]
48635  *     });
48636  *
48637  *     Ext.create('Ext.chart.Chart', {
48638  *         renderTo: Ext.getBody(),
48639  *         width: 500,
48640  *         height: 300,
48641  *         store: store,
48642  *         axes: [
48643  *             {
48644  *                 type: 'Numeric',
48645  *                 grid: true,
48646  *                 position: 'left',
48647  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
48648  *                 title: 'Sample Values',
48649  *                 grid: {
48650  *                     odd: {
48651  *                         opacity: 1,
48652  *                         fill: '#ddd',
48653  *                         stroke: '#bbb',
48654  *                         'stroke-width': 1
48655  *                     }
48656  *                 },
48657  *                 minimum: 0,
48658  *                 adjustMinimumByMajorUnit: 0
48659  *             },
48660  *             {
48661  *                 type: 'Category',
48662  *                 position: 'bottom',
48663  *                 fields: ['name'],
48664  *                 title: 'Sample Metrics',
48665  *                 grid: true,
48666  *                 label: {
48667  *                     rotate: {
48668  *                         degrees: 315
48669  *                     }
48670  *                 }
48671  *             }
48672  *         ],
48673  *         series: [{
48674  *             type: 'area',
48675  *             highlight: false,
48676  *             axis: 'left',
48677  *             xField: 'name',
48678  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
48679  *             style: {
48680  *                 opacity: 0.93
48681  *             }
48682  *         }]
48683  *     });
48684  *
48685  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
48686  * 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,
48687  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
48688  * to the style object.
48689  *
48690  * @xtype area
48691  */
48692 Ext.define('Ext.chart.series.Area', {
48693
48694     /* Begin Definitions */
48695
48696     extend: 'Ext.chart.series.Cartesian',
48697
48698     alias: 'series.area',
48699
48700     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
48701
48702     /* End Definitions */
48703
48704     type: 'area',
48705
48706     // @private Area charts are alyways stacked
48707     stacked: true,
48708
48709     /**
48710      * @cfg {Object} style
48711      * Append styling properties to this object for it to override theme properties.
48712      */
48713     style: {},
48714
48715     constructor: function(config) {
48716         this.callParent(arguments);
48717         var me = this,
48718             surface = me.chart.surface,
48719             i, l;
48720         Ext.apply(me, config, {
48721             __excludes: [],
48722             highlightCfg: {
48723                 lineWidth: 3,
48724                 stroke: '#55c',
48725                 opacity: 0.8,
48726                 color: '#f00'
48727             }
48728         });
48729         if (me.highlight) {
48730             me.highlightSprite = surface.add({
48731                 type: 'path',
48732                 path: ['M', 0, 0],
48733                 zIndex: 1000,
48734                 opacity: 0.3,
48735                 lineWidth: 5,
48736                 hidden: true,
48737                 stroke: '#444'
48738             });
48739         }
48740         me.group = surface.getGroup(me.seriesId);
48741     },
48742
48743     // @private Shrinks dataSets down to a smaller size
48744     shrink: function(xValues, yValues, size) {
48745         var len = xValues.length,
48746             ratio = Math.floor(len / size),
48747             i, j,
48748             xSum = 0,
48749             yCompLen = this.areas.length,
48750             ySum = [],
48751             xRes = [],
48752             yRes = [];
48753         //initialize array
48754         for (j = 0; j < yCompLen; ++j) {
48755             ySum[j] = 0;
48756         }
48757         for (i = 0; i < len; ++i) {
48758             xSum += xValues[i];
48759             for (j = 0; j < yCompLen; ++j) {
48760                 ySum[j] += yValues[i][j];
48761             }
48762             if (i % ratio == 0) {
48763                 //push averages
48764                 xRes.push(xSum/ratio);
48765                 for (j = 0; j < yCompLen; ++j) {
48766                     ySum[j] /= ratio;
48767                 }
48768                 yRes.push(ySum);
48769                 //reset sum accumulators
48770                 xSum = 0;
48771                 for (j = 0, ySum = []; j < yCompLen; ++j) {
48772                     ySum[j] = 0;
48773                 }
48774             }
48775         }
48776         return {
48777             x: xRes,
48778             y: yRes
48779         };
48780     },
48781
48782     // @private Get chart and data boundaries
48783     getBounds: function() {
48784         var me = this,
48785             chart = me.chart,
48786             store = chart.getChartStore(),
48787             areas = [].concat(me.yField),
48788             areasLen = areas.length,
48789             xValues = [],
48790             yValues = [],
48791             infinity = Infinity,
48792             minX = infinity,
48793             minY = infinity,
48794             maxX = -infinity,
48795             maxY = -infinity,
48796             math = Math,
48797             mmin = math.min,
48798             mmax = math.max,
48799             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
48800
48801         me.setBBox();
48802         bbox = me.bbox;
48803
48804         // Run through the axis
48805         if (me.axis) {
48806             axis = chart.axes.get(me.axis);
48807             if (axis) {
48808                 out = axis.calcEnds();
48809                 minY = out.from || axis.prevMin;
48810                 maxY = mmax(out.to || axis.prevMax, 0);
48811             }
48812         }
48813
48814         if (me.yField && !Ext.isNumber(minY)) {
48815             axis = Ext.create('Ext.chart.axis.Axis', {
48816                 chart: chart,
48817                 fields: [].concat(me.yField)
48818             });
48819             out = axis.calcEnds();
48820             minY = out.from || axis.prevMin;
48821             maxY = mmax(out.to || axis.prevMax, 0);
48822         }
48823
48824         if (!Ext.isNumber(minY)) {
48825             minY = 0;
48826         }
48827         if (!Ext.isNumber(maxY)) {
48828             maxY = 0;
48829         }
48830
48831         store.each(function(record, i) {
48832             xValue = record.get(me.xField);
48833             yValue = [];
48834             if (typeof xValue != 'number') {
48835                 xValue = i;
48836             }
48837             xValues.push(xValue);
48838             acumY = 0;
48839             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
48840                 areaElem = record.get(areas[areaIndex]);
48841                 if (typeof areaElem == 'number') {
48842                     minY = mmin(minY, areaElem);
48843                     yValue.push(areaElem);
48844                     acumY += areaElem;
48845                 }
48846             }
48847             minX = mmin(minX, xValue);
48848             maxX = mmax(maxX, xValue);
48849             maxY = mmax(maxY, acumY);
48850             yValues.push(yValue);
48851         }, me);
48852
48853         xScale = bbox.width / ((maxX - minX) || 1);
48854         yScale = bbox.height / ((maxY - minY) || 1);
48855
48856         ln = xValues.length;
48857         if ((ln > bbox.width) && me.areas) {
48858             sumValues = me.shrink(xValues, yValues, bbox.width);
48859             xValues = sumValues.x;
48860             yValues = sumValues.y;
48861         }
48862
48863         return {
48864             bbox: bbox,
48865             minX: minX,
48866             minY: minY,
48867             xValues: xValues,
48868             yValues: yValues,
48869             xScale: xScale,
48870             yScale: yScale,
48871             areasLen: areasLen
48872         };
48873     },
48874
48875     // @private Build an array of paths for the chart
48876     getPaths: function() {
48877         var me = this,
48878             chart = me.chart,
48879             store = chart.getChartStore(),
48880             first = true,
48881             bounds = me.getBounds(),
48882             bbox = bounds.bbox,
48883             items = me.items = [],
48884             componentPaths = [],
48885             componentPath,
48886             paths = [],
48887             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
48888
48889         ln = bounds.xValues.length;
48890         // Start the path
48891         for (i = 0; i < ln; i++) {
48892             xValue = bounds.xValues[i];
48893             yValue = bounds.yValues[i];
48894             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
48895             acumY = 0;
48896             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
48897                 // Excluded series
48898                 if (me.__excludes[areaIndex]) {
48899                     continue;
48900                 }
48901                 if (!componentPaths[areaIndex]) {
48902                     componentPaths[areaIndex] = [];
48903                 }
48904                 areaElem = yValue[areaIndex];
48905                 acumY += areaElem;
48906                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
48907                 if (!paths[areaIndex]) {
48908                     paths[areaIndex] = ['M', x, y];
48909                     componentPaths[areaIndex].push(['L', x, y]);
48910                 } else {
48911                     paths[areaIndex].push('L', x, y);
48912                     componentPaths[areaIndex].push(['L', x, y]);
48913                 }
48914                 if (!items[areaIndex]) {
48915                     items[areaIndex] = {
48916                         pointsUp: [],
48917                         pointsDown: [],
48918                         series: me
48919                     };
48920                 }
48921                 items[areaIndex].pointsUp.push([x, y]);
48922             }
48923         }
48924
48925         // Close the paths
48926         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
48927             // Excluded series
48928             if (me.__excludes[areaIndex]) {
48929                 continue;
48930             }
48931             path = paths[areaIndex];
48932             // Close bottom path to the axis
48933             if (areaIndex == 0 || first) {
48934                 first = false;
48935                 path.push('L', x, bbox.y + bbox.height,
48936                           'L', bbox.x, bbox.y + bbox.height,
48937                           'Z');
48938             }
48939             // Close other paths to the one before them
48940             else {
48941                 componentPath = componentPaths[prevAreaIndex];
48942                 componentPath.reverse();
48943                 path.push('L', x, componentPath[0][2]);
48944                 for (i = 0; i < ln; i++) {
48945                     path.push(componentPath[i][0],
48946                               componentPath[i][1],
48947                               componentPath[i][2]);
48948                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
48949                 }
48950                 path.push('L', bbox.x, path[2], 'Z');
48951             }
48952             prevAreaIndex = areaIndex;
48953         }
48954         return {
48955             paths: paths,
48956             areasLen: bounds.areasLen
48957         };
48958     },
48959
48960     /**
48961      * Draws the series for the current chart.
48962      */
48963     drawSeries: function() {
48964         var me = this,
48965             chart = me.chart,
48966             store = chart.getChartStore(),
48967             surface = chart.surface,
48968             animate = chart.animate,
48969             group = me.group,
48970             endLineStyle = Ext.apply(me.seriesStyle, me.style),
48971             colorArrayStyle = me.colorArrayStyle,
48972             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
48973             areaIndex, areaElem, paths, path, rendererAttributes;
48974
48975         me.unHighlightItem();
48976         me.cleanHighlights();
48977
48978         if (!store || !store.getCount()) {
48979             return;
48980         }
48981
48982         paths = me.getPaths();
48983
48984         if (!me.areas) {
48985             me.areas = [];
48986         }
48987
48988         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
48989             // Excluded series
48990             if (me.__excludes[areaIndex]) {
48991                 continue;
48992             }
48993             if (!me.areas[areaIndex]) {
48994                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
48995                     type: 'path',
48996                     group: group,
48997                     // 'clip-rect': me.clipBox,
48998                     path: paths.paths[areaIndex],
48999                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
49000                     fill: colorArrayStyle[areaIndex % colorArrayLength]
49001                 }, endLineStyle || {}));
49002             }
49003             areaElem = me.areas[areaIndex];
49004             path = paths.paths[areaIndex];
49005             if (animate) {
49006                 //Add renderer to line. There is not a unique record associated with this.
49007                 rendererAttributes = me.renderer(areaElem, false, {
49008                     path: path,
49009                     // 'clip-rect': me.clipBox,
49010                     fill: colorArrayStyle[areaIndex % colorArrayLength],
49011                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
49012                 }, areaIndex, store);
49013                 //fill should not be used here but when drawing the special fill path object
49014                 me.animation = me.onAnimate(areaElem, {
49015                     to: rendererAttributes
49016                 });
49017             } else {
49018                 rendererAttributes = me.renderer(areaElem, false, {
49019                     path: path,
49020                     // 'clip-rect': me.clipBox,
49021                     hidden: false,
49022                     fill: colorArrayStyle[areaIndex % colorArrayLength],
49023                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
49024                 }, areaIndex, store);
49025                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
49026             }
49027         }
49028         me.renderLabels();
49029         me.renderCallouts();
49030     },
49031
49032     // @private
49033     onAnimate: function(sprite, attr) {
49034         sprite.show();
49035         return this.callParent(arguments);
49036     },
49037
49038     // @private
49039     onCreateLabel: function(storeItem, item, i, display) {
49040         var me = this,
49041             group = me.labelsGroup,
49042             config = me.label,
49043             bbox = me.bbox,
49044             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
49045
49046         return me.chart.surface.add(Ext.apply({
49047             'type': 'text',
49048             'text-anchor': 'middle',
49049             'group': group,
49050             'x': item.point[0],
49051             'y': bbox.y + bbox.height / 2
49052         }, endLabelStyle || {}));
49053     },
49054
49055     // @private
49056     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
49057         var me = this,
49058             chart = me.chart,
49059             resizing = chart.resizing,
49060             config = me.label,
49061             format = config.renderer,
49062             field = config.field,
49063             bbox = me.bbox,
49064             x = item.point[0],
49065             y = item.point[1],
49066             bb, width, height;
49067
49068         label.setAttributes({
49069             text: format(storeItem.get(field[index])),
49070             hidden: true
49071         }, true);
49072
49073         bb = label.getBBox();
49074         width = bb.width / 2;
49075         height = bb.height / 2;
49076
49077         x = x - width < bbox.x? bbox.x + width : x;
49078         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
49079         y = y - height < bbox.y? bbox.y + height : y;
49080         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
49081
49082         if (me.chart.animate && !me.chart.resizing) {
49083             label.show(true);
49084             me.onAnimate(label, {
49085                 to: {
49086                     x: x,
49087                     y: y
49088                 }
49089             });
49090         } else {
49091             label.setAttributes({
49092                 x: x,
49093                 y: y
49094             }, true);
49095             if (resizing) {
49096                 me.animation.on('afteranimate', function() {
49097                     label.show(true);
49098                 });
49099             } else {
49100                 label.show(true);
49101             }
49102         }
49103     },
49104
49105     // @private
49106     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
49107         var me = this,
49108             chart = me.chart,
49109             surface = chart.surface,
49110             resizing = chart.resizing,
49111             config = me.callouts,
49112             items = me.items,
49113             prev = (i == 0) ? false : items[i -1].point,
49114             next = (i == items.length -1) ? false : items[i +1].point,
49115             cur = item.point,
49116             dir, norm, normal, a, aprev, anext,
49117             bbox = callout.label.getBBox(),
49118             offsetFromViz = 30,
49119             offsetToSide = 10,
49120             offsetBox = 3,
49121             boxx, boxy, boxw, boxh,
49122             p, clipRect = me.clipRect,
49123             x, y;
49124
49125         //get the right two points
49126         if (!prev) {
49127             prev = cur;
49128         }
49129         if (!next) {
49130             next = cur;
49131         }
49132         a = (next[1] - prev[1]) / (next[0] - prev[0]);
49133         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
49134         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
49135
49136         norm = Math.sqrt(1 + a * a);
49137         dir = [1 / norm, a / norm];
49138         normal = [-dir[1], dir[0]];
49139
49140         //keep the label always on the outer part of the "elbow"
49141         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
49142             normal[0] *= -1;
49143             normal[1] *= -1;
49144         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
49145             normal[0] *= -1;
49146             normal[1] *= -1;
49147         }
49148
49149         //position
49150         x = cur[0] + normal[0] * offsetFromViz;
49151         y = cur[1] + normal[1] * offsetFromViz;
49152
49153         //box position and dimensions
49154         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
49155         boxy = y - bbox.height /2 - offsetBox;
49156         boxw = bbox.width + 2 * offsetBox;
49157         boxh = bbox.height + 2 * offsetBox;
49158
49159         //now check if we're out of bounds and invert the normal vector correspondingly
49160         //this may add new overlaps between labels (but labels won't be out of bounds).
49161         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
49162             normal[0] *= -1;
49163         }
49164         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
49165             normal[1] *= -1;
49166         }
49167
49168         //update positions
49169         x = cur[0] + normal[0] * offsetFromViz;
49170         y = cur[1] + normal[1] * offsetFromViz;
49171
49172         //update box position and dimensions
49173         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
49174         boxy = y - bbox.height /2 - offsetBox;
49175         boxw = bbox.width + 2 * offsetBox;
49176         boxh = bbox.height + 2 * offsetBox;
49177
49178         //set the line from the middle of the pie to the box.
49179         callout.lines.setAttributes({
49180             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
49181         }, true);
49182         //set box position
49183         callout.box.setAttributes({
49184             x: boxx,
49185             y: boxy,
49186             width: boxw,
49187             height: boxh
49188         }, true);
49189         //set text position
49190         callout.label.setAttributes({
49191             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
49192             y: y
49193         }, true);
49194         for (p in callout) {
49195             callout[p].show(true);
49196         }
49197     },
49198
49199     isItemInPoint: function(x, y, item, i) {
49200         var me = this,
49201             pointsUp = item.pointsUp,
49202             pointsDown = item.pointsDown,
49203             abs = Math.abs,
49204             dist = Infinity, p, pln, point;
49205
49206         for (p = 0, pln = pointsUp.length; p < pln; p++) {
49207             point = [pointsUp[p][0], pointsUp[p][1]];
49208             if (dist > abs(x - point[0])) {
49209                 dist = abs(x - point[0]);
49210             } else {
49211                 point = pointsUp[p -1];
49212                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
49213                     item.storeIndex = p -1;
49214                     item.storeField = me.yField[i];
49215                     item.storeItem = me.chart.store.getAt(p -1);
49216                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
49217                     return true;
49218                 } else {
49219                     break;
49220                 }
49221             }
49222         }
49223         return false;
49224     },
49225
49226     /**
49227      * Highlight this entire series.
49228      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
49229      */
49230     highlightSeries: function() {
49231         var area, to, fillColor;
49232         if (this._index !== undefined) {
49233             area = this.areas[this._index];
49234             if (area.__highlightAnim) {
49235                 area.__highlightAnim.paused = true;
49236             }
49237             area.__highlighted = true;
49238             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
49239             area.__prevFill = area.__prevFill || area.attr.fill;
49240             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
49241             fillColor = Ext.draw.Color.fromString(area.__prevFill);
49242             to = {
49243                 lineWidth: (area.__prevLineWidth || 0) + 2
49244             };
49245             if (fillColor) {
49246                 to.fill = fillColor.getLighter(0.2).toString();
49247             }
49248             else {
49249                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
49250             }
49251             if (this.chart.animate) {
49252                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
49253                     target: area,
49254                     to: to
49255                 }, this.chart.animate));
49256             }
49257             else {
49258                 area.setAttributes(to, true);
49259             }
49260         }
49261     },
49262
49263     /**
49264      * UnHighlight this entire series.
49265      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
49266      */
49267     unHighlightSeries: function() {
49268         var area;
49269         if (this._index !== undefined) {
49270             area = this.areas[this._index];
49271             if (area.__highlightAnim) {
49272                 area.__highlightAnim.paused = true;
49273             }
49274             if (area.__highlighted) {
49275                 area.__highlighted = false;
49276                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
49277                     target: area,
49278                     to: {
49279                         fill: area.__prevFill,
49280                         opacity: area.__prevOpacity,
49281                         lineWidth: area.__prevLineWidth
49282                     }
49283                 });
49284             }
49285         }
49286     },
49287
49288     /**
49289      * Highlight the specified item. If no item is provided the whole series will be highlighted.
49290      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
49291      */
49292     highlightItem: function(item) {
49293         var me = this,
49294             points, path;
49295         if (!item) {
49296             this.highlightSeries();
49297             return;
49298         }
49299         points = item._points;
49300         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
49301                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
49302         me.highlightSprite.setAttributes({
49303             path: path,
49304             hidden: false
49305         }, true);
49306     },
49307
49308     /**
49309      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
49310      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
49311      */
49312     unHighlightItem: function(item) {
49313         if (!item) {
49314             this.unHighlightSeries();
49315         }
49316
49317         if (this.highlightSprite) {
49318             this.highlightSprite.hide(true);
49319         }
49320     },
49321
49322     // @private
49323     hideAll: function() {
49324         if (!isNaN(this._index)) {
49325             this.__excludes[this._index] = true;
49326             this.areas[this._index].hide(true);
49327             this.drawSeries();
49328         }
49329     },
49330
49331     // @private
49332     showAll: function() {
49333         if (!isNaN(this._index)) {
49334             this.__excludes[this._index] = false;
49335             this.areas[this._index].show(true);
49336             this.drawSeries();
49337         }
49338     },
49339
49340     /**
49341      * Returns the color of the series (to be displayed as color for the series legend item).
49342      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
49343      */
49344     getLegendColor: function(index) {
49345         var me = this;
49346         return me.colorArrayStyle[index % me.colorArrayStyle.length];
49347     }
49348 });
49349 /**
49350  * @class Ext.chart.series.Area
49351  * @extends Ext.chart.series.Cartesian
49352  *
49353  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
49354  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
49355  * documentation for more information. A typical configuration object for the area series could be:
49356  *
49357  *     @example
49358  *     var store = Ext.create('Ext.data.JsonStore', {
49359  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
49360  *         data: [
49361  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
49362  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
49363  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
49364  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
49365  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
49366  *         ]
49367  *     });
49368  *
49369  *     Ext.create('Ext.chart.Chart', {
49370  *         renderTo: Ext.getBody(),
49371  *         width: 500,
49372  *         height: 300,
49373  *         store: store,
49374  *         axes: [
49375  *             {
49376  *                 type: 'Numeric',
49377  *                 grid: true,
49378  *                 position: 'left',
49379  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
49380  *                 title: 'Sample Values',
49381  *                 grid: {
49382  *                     odd: {
49383  *                         opacity: 1,
49384  *                         fill: '#ddd',
49385  *                         stroke: '#bbb',
49386  *                         'stroke-width': 1
49387  *                     }
49388  *                 },
49389  *                 minimum: 0,
49390  *                 adjustMinimumByMajorUnit: 0
49391  *             },
49392  *             {
49393  *                 type: 'Category',
49394  *                 position: 'bottom',
49395  *                 fields: ['name'],
49396  *                 title: 'Sample Metrics',
49397  *                 grid: true,
49398  *                 label: {
49399  *                     rotate: {
49400  *                         degrees: 315
49401  *                     }
49402  *                 }
49403  *             }
49404  *         ],
49405  *         series: [{
49406  *             type: 'area',
49407  *             highlight: false,
49408  *             axis: 'left',
49409  *             xField: 'name',
49410  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
49411  *             style: {
49412  *                 opacity: 0.93
49413  *             }
49414  *         }]
49415  *     });
49416  *
49417  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
49418  * 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,
49419  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
49420  * to the style object.
49421  *
49422  * @xtype area
49423  */
49424 Ext.define('Ext.chart.series.Area', {
49425
49426     /* Begin Definitions */
49427
49428     extend: 'Ext.chart.series.Cartesian',
49429
49430     alias: 'series.area',
49431
49432     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
49433
49434     /* End Definitions */
49435
49436     type: 'area',
49437
49438     // @private Area charts are alyways stacked
49439     stacked: true,
49440
49441     /**
49442      * @cfg {Object} style
49443      * Append styling properties to this object for it to override theme properties.
49444      */
49445     style: {},
49446
49447     constructor: function(config) {
49448         this.callParent(arguments);
49449         var me = this,
49450             surface = me.chart.surface,
49451             i, l;
49452         Ext.apply(me, config, {
49453             __excludes: [],
49454             highlightCfg: {
49455                 lineWidth: 3,
49456                 stroke: '#55c',
49457                 opacity: 0.8,
49458                 color: '#f00'
49459             }
49460         });
49461         if (me.highlight) {
49462             me.highlightSprite = surface.add({
49463                 type: 'path',
49464                 path: ['M', 0, 0],
49465                 zIndex: 1000,
49466                 opacity: 0.3,
49467                 lineWidth: 5,
49468                 hidden: true,
49469                 stroke: '#444'
49470             });
49471         }
49472         me.group = surface.getGroup(me.seriesId);
49473     },
49474
49475     // @private Shrinks dataSets down to a smaller size
49476     shrink: function(xValues, yValues, size) {
49477         var len = xValues.length,
49478             ratio = Math.floor(len / size),
49479             i, j,
49480             xSum = 0,
49481             yCompLen = this.areas.length,
49482             ySum = [],
49483             xRes = [],
49484             yRes = [];
49485         //initialize array
49486         for (j = 0; j < yCompLen; ++j) {
49487             ySum[j] = 0;
49488         }
49489         for (i = 0; i < len; ++i) {
49490             xSum += xValues[i];
49491             for (j = 0; j < yCompLen; ++j) {
49492                 ySum[j] += yValues[i][j];
49493             }
49494             if (i % ratio == 0) {
49495                 //push averages
49496                 xRes.push(xSum/ratio);
49497                 for (j = 0; j < yCompLen; ++j) {
49498                     ySum[j] /= ratio;
49499                 }
49500                 yRes.push(ySum);
49501                 //reset sum accumulators
49502                 xSum = 0;
49503                 for (j = 0, ySum = []; j < yCompLen; ++j) {
49504                     ySum[j] = 0;
49505                 }
49506             }
49507         }
49508         return {
49509             x: xRes,
49510             y: yRes
49511         };
49512     },
49513
49514     // @private Get chart and data boundaries
49515     getBounds: function() {
49516         var me = this,
49517             chart = me.chart,
49518             store = chart.getChartStore(),
49519             areas = [].concat(me.yField),
49520             areasLen = areas.length,
49521             xValues = [],
49522             yValues = [],
49523             infinity = Infinity,
49524             minX = infinity,
49525             minY = infinity,
49526             maxX = -infinity,
49527             maxY = -infinity,
49528             math = Math,
49529             mmin = math.min,
49530             mmax = math.max,
49531             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
49532
49533         me.setBBox();
49534         bbox = me.bbox;
49535
49536         // Run through the axis
49537         if (me.axis) {
49538             axis = chart.axes.get(me.axis);
49539             if (axis) {
49540                 out = axis.calcEnds();
49541                 minY = out.from || axis.prevMin;
49542                 maxY = mmax(out.to || axis.prevMax, 0);
49543             }
49544         }
49545
49546         if (me.yField && !Ext.isNumber(minY)) {
49547             axis = Ext.create('Ext.chart.axis.Axis', {
49548                 chart: chart,
49549                 fields: [].concat(me.yField)
49550             });
49551             out = axis.calcEnds();
49552             minY = out.from || axis.prevMin;
49553             maxY = mmax(out.to || axis.prevMax, 0);
49554         }
49555
49556         if (!Ext.isNumber(minY)) {
49557             minY = 0;
49558         }
49559         if (!Ext.isNumber(maxY)) {
49560             maxY = 0;
49561         }
49562
49563         store.each(function(record, i) {
49564             xValue = record.get(me.xField);
49565             yValue = [];
49566             if (typeof xValue != 'number') {
49567                 xValue = i;
49568             }
49569             xValues.push(xValue);
49570             acumY = 0;
49571             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
49572                 areaElem = record.get(areas[areaIndex]);
49573                 if (typeof areaElem == 'number') {
49574                     minY = mmin(minY, areaElem);
49575                     yValue.push(areaElem);
49576                     acumY += areaElem;
49577                 }
49578             }
49579             minX = mmin(minX, xValue);
49580             maxX = mmax(maxX, xValue);
49581             maxY = mmax(maxY, acumY);
49582             yValues.push(yValue);
49583         }, me);
49584
49585         xScale = bbox.width / ((maxX - minX) || 1);
49586         yScale = bbox.height / ((maxY - minY) || 1);
49587
49588         ln = xValues.length;
49589         if ((ln > bbox.width) && me.areas) {
49590             sumValues = me.shrink(xValues, yValues, bbox.width);
49591             xValues = sumValues.x;
49592             yValues = sumValues.y;
49593         }
49594
49595         return {
49596             bbox: bbox,
49597             minX: minX,
49598             minY: minY,
49599             xValues: xValues,
49600             yValues: yValues,
49601             xScale: xScale,
49602             yScale: yScale,
49603             areasLen: areasLen
49604         };
49605     },
49606
49607     // @private Build an array of paths for the chart
49608     getPaths: function() {
49609         var me = this,
49610             chart = me.chart,
49611             store = chart.getChartStore(),
49612             first = true,
49613             bounds = me.getBounds(),
49614             bbox = bounds.bbox,
49615             items = me.items = [],
49616             componentPaths = [],
49617             componentPath,
49618             paths = [],
49619             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
49620
49621         ln = bounds.xValues.length;
49622         // Start the path
49623         for (i = 0; i < ln; i++) {
49624             xValue = bounds.xValues[i];
49625             yValue = bounds.yValues[i];
49626             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
49627             acumY = 0;
49628             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
49629                 // Excluded series
49630                 if (me.__excludes[areaIndex]) {
49631                     continue;
49632                 }
49633                 if (!componentPaths[areaIndex]) {
49634                     componentPaths[areaIndex] = [];
49635                 }
49636                 areaElem = yValue[areaIndex];
49637                 acumY += areaElem;
49638                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
49639                 if (!paths[areaIndex]) {
49640                     paths[areaIndex] = ['M', x, y];
49641                     componentPaths[areaIndex].push(['L', x, y]);
49642                 } else {
49643                     paths[areaIndex].push('L', x, y);
49644                     componentPaths[areaIndex].push(['L', x, y]);
49645                 }
49646                 if (!items[areaIndex]) {
49647                     items[areaIndex] = {
49648                         pointsUp: [],
49649                         pointsDown: [],
49650                         series: me
49651                     };
49652                 }
49653                 items[areaIndex].pointsUp.push([x, y]);
49654             }
49655         }
49656
49657         // Close the paths
49658         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
49659             // Excluded series
49660             if (me.__excludes[areaIndex]) {
49661                 continue;
49662             }
49663             path = paths[areaIndex];
49664             // Close bottom path to the axis
49665             if (areaIndex == 0 || first) {
49666                 first = false;
49667                 path.push('L', x, bbox.y + bbox.height,
49668                           'L', bbox.x, bbox.y + bbox.height,
49669                           'Z');
49670             }
49671             // Close other paths to the one before them
49672             else {
49673                 componentPath = componentPaths[prevAreaIndex];
49674                 componentPath.reverse();
49675                 path.push('L', x, componentPath[0][2]);
49676                 for (i = 0; i < ln; i++) {
49677                     path.push(componentPath[i][0],
49678                               componentPath[i][1],
49679                               componentPath[i][2]);
49680                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
49681                 }
49682                 path.push('L', bbox.x, path[2], 'Z');
49683             }
49684             prevAreaIndex = areaIndex;
49685         }
49686         return {
49687             paths: paths,
49688             areasLen: bounds.areasLen
49689         };
49690     },
49691
49692     /**
49693      * Draws the series for the current chart.
49694      */
49695     drawSeries: function() {
49696         var me = this,
49697             chart = me.chart,
49698             store = chart.getChartStore(),
49699             surface = chart.surface,
49700             animate = chart.animate,
49701             group = me.group,
49702             endLineStyle = Ext.apply(me.seriesStyle, me.style),
49703             colorArrayStyle = me.colorArrayStyle,
49704             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
49705             areaIndex, areaElem, paths, path, rendererAttributes;
49706
49707         me.unHighlightItem();
49708         me.cleanHighlights();
49709
49710         if (!store || !store.getCount()) {
49711             return;
49712         }
49713
49714         paths = me.getPaths();
49715
49716         if (!me.areas) {
49717             me.areas = [];
49718         }
49719
49720         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
49721             // Excluded series
49722             if (me.__excludes[areaIndex]) {
49723                 continue;
49724             }
49725             if (!me.areas[areaIndex]) {
49726                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
49727                     type: 'path',
49728                     group: group,
49729                     // 'clip-rect': me.clipBox,
49730                     path: paths.paths[areaIndex],
49731                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
49732                     fill: colorArrayStyle[areaIndex % colorArrayLength]
49733                 }, endLineStyle || {}));
49734             }
49735             areaElem = me.areas[areaIndex];
49736             path = paths.paths[areaIndex];
49737             if (animate) {
49738                 //Add renderer to line. There is not a unique record associated with this.
49739                 rendererAttributes = me.renderer(areaElem, false, {
49740                     path: path,
49741                     // 'clip-rect': me.clipBox,
49742                     fill: colorArrayStyle[areaIndex % colorArrayLength],
49743                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
49744                 }, areaIndex, store);
49745                 //fill should not be used here but when drawing the special fill path object
49746                 me.animation = me.onAnimate(areaElem, {
49747                     to: rendererAttributes
49748                 });
49749             } else {
49750                 rendererAttributes = me.renderer(areaElem, false, {
49751                     path: path,
49752                     // 'clip-rect': me.clipBox,
49753                     hidden: false,
49754                     fill: colorArrayStyle[areaIndex % colorArrayLength],
49755                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
49756                 }, areaIndex, store);
49757                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
49758             }
49759         }
49760         me.renderLabels();
49761         me.renderCallouts();
49762     },
49763
49764     // @private
49765     onAnimate: function(sprite, attr) {
49766         sprite.show();
49767         return this.callParent(arguments);
49768     },
49769
49770     // @private
49771     onCreateLabel: function(storeItem, item, i, display) {
49772         var me = this,
49773             group = me.labelsGroup,
49774             config = me.label,
49775             bbox = me.bbox,
49776             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
49777
49778         return me.chart.surface.add(Ext.apply({
49779             'type': 'text',
49780             'text-anchor': 'middle',
49781             'group': group,
49782             'x': item.point[0],
49783             'y': bbox.y + bbox.height / 2
49784         }, endLabelStyle || {}));
49785     },
49786
49787     // @private
49788     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
49789         var me = this,
49790             chart = me.chart,
49791             resizing = chart.resizing,
49792             config = me.label,
49793             format = config.renderer,
49794             field = config.field,
49795             bbox = me.bbox,
49796             x = item.point[0],
49797             y = item.point[1],
49798             bb, width, height;
49799
49800         label.setAttributes({
49801             text: format(storeItem.get(field[index])),
49802             hidden: true
49803         }, true);
49804
49805         bb = label.getBBox();
49806         width = bb.width / 2;
49807         height = bb.height / 2;
49808
49809         x = x - width < bbox.x? bbox.x + width : x;
49810         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
49811         y = y - height < bbox.y? bbox.y + height : y;
49812         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
49813
49814         if (me.chart.animate && !me.chart.resizing) {
49815             label.show(true);
49816             me.onAnimate(label, {
49817                 to: {
49818                     x: x,
49819                     y: y
49820                 }
49821             });
49822         } else {
49823             label.setAttributes({
49824                 x: x,
49825                 y: y
49826             }, true);
49827             if (resizing) {
49828                 me.animation.on('afteranimate', function() {
49829                     label.show(true);
49830                 });
49831             } else {
49832                 label.show(true);
49833             }
49834         }
49835     },
49836
49837     // @private
49838     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
49839         var me = this,
49840             chart = me.chart,
49841             surface = chart.surface,
49842             resizing = chart.resizing,
49843             config = me.callouts,
49844             items = me.items,
49845             prev = (i == 0) ? false : items[i -1].point,
49846             next = (i == items.length -1) ? false : items[i +1].point,
49847             cur = item.point,
49848             dir, norm, normal, a, aprev, anext,
49849             bbox = callout.label.getBBox(),
49850             offsetFromViz = 30,
49851             offsetToSide = 10,
49852             offsetBox = 3,
49853             boxx, boxy, boxw, boxh,
49854             p, clipRect = me.clipRect,
49855             x, y;
49856
49857         //get the right two points
49858         if (!prev) {
49859             prev = cur;
49860         }
49861         if (!next) {
49862             next = cur;
49863         }
49864         a = (next[1] - prev[1]) / (next[0] - prev[0]);
49865         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
49866         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
49867
49868         norm = Math.sqrt(1 + a * a);
49869         dir = [1 / norm, a / norm];
49870         normal = [-dir[1], dir[0]];
49871
49872         //keep the label always on the outer part of the "elbow"
49873         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
49874             normal[0] *= -1;
49875             normal[1] *= -1;
49876         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
49877             normal[0] *= -1;
49878             normal[1] *= -1;
49879         }
49880
49881         //position
49882         x = cur[0] + normal[0] * offsetFromViz;
49883         y = cur[1] + normal[1] * offsetFromViz;
49884
49885         //box position and dimensions
49886         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
49887         boxy = y - bbox.height /2 - offsetBox;
49888         boxw = bbox.width + 2 * offsetBox;
49889         boxh = bbox.height + 2 * offsetBox;
49890
49891         //now check if we're out of bounds and invert the normal vector correspondingly
49892         //this may add new overlaps between labels (but labels won't be out of bounds).
49893         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
49894             normal[0] *= -1;
49895         }
49896         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
49897             normal[1] *= -1;
49898         }
49899
49900         //update positions
49901         x = cur[0] + normal[0] * offsetFromViz;
49902         y = cur[1] + normal[1] * offsetFromViz;
49903
49904         //update box position and dimensions
49905         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
49906         boxy = y - bbox.height /2 - offsetBox;
49907         boxw = bbox.width + 2 * offsetBox;
49908         boxh = bbox.height + 2 * offsetBox;
49909
49910         //set the line from the middle of the pie to the box.
49911         callout.lines.setAttributes({
49912             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
49913         }, true);
49914         //set box position
49915         callout.box.setAttributes({
49916             x: boxx,
49917             y: boxy,
49918             width: boxw,
49919             height: boxh
49920         }, true);
49921         //set text position
49922         callout.label.setAttributes({
49923             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
49924             y: y
49925         }, true);
49926         for (p in callout) {
49927             callout[p].show(true);
49928         }
49929     },
49930
49931     isItemInPoint: function(x, y, item, i) {
49932         var me = this,
49933             pointsUp = item.pointsUp,
49934             pointsDown = item.pointsDown,
49935             abs = Math.abs,
49936             dist = Infinity, p, pln, point;
49937
49938         for (p = 0, pln = pointsUp.length; p < pln; p++) {
49939             point = [pointsUp[p][0], pointsUp[p][1]];
49940             if (dist > abs(x - point[0])) {
49941                 dist = abs(x - point[0]);
49942             } else {
49943                 point = pointsUp[p -1];
49944                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
49945                     item.storeIndex = p -1;
49946                     item.storeField = me.yField[i];
49947                     item.storeItem = me.chart.store.getAt(p -1);
49948                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
49949                     return true;
49950                 } else {
49951                     break;
49952                 }
49953             }
49954         }
49955         return false;
49956     },
49957
49958     /**
49959      * Highlight this entire series.
49960      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
49961      */
49962     highlightSeries: function() {
49963         var area, to, fillColor;
49964         if (this._index !== undefined) {
49965             area = this.areas[this._index];
49966             if (area.__highlightAnim) {
49967                 area.__highlightAnim.paused = true;
49968             }
49969             area.__highlighted = true;
49970             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
49971             area.__prevFill = area.__prevFill || area.attr.fill;
49972             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
49973             fillColor = Ext.draw.Color.fromString(area.__prevFill);
49974             to = {
49975                 lineWidth: (area.__prevLineWidth || 0) + 2
49976             };
49977             if (fillColor) {
49978                 to.fill = fillColor.getLighter(0.2).toString();
49979             }
49980             else {
49981                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
49982             }
49983             if (this.chart.animate) {
49984                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
49985                     target: area,
49986                     to: to
49987                 }, this.chart.animate));
49988             }
49989             else {
49990                 area.setAttributes(to, true);
49991             }
49992         }
49993     },
49994
49995     /**
49996      * UnHighlight this entire series.
49997      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
49998      */
49999     unHighlightSeries: function() {
50000         var area;
50001         if (this._index !== undefined) {
50002             area = this.areas[this._index];
50003             if (area.__highlightAnim) {
50004                 area.__highlightAnim.paused = true;
50005             }
50006             if (area.__highlighted) {
50007                 area.__highlighted = false;
50008                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
50009                     target: area,
50010                     to: {
50011                         fill: area.__prevFill,
50012                         opacity: area.__prevOpacity,
50013                         lineWidth: area.__prevLineWidth
50014                     }
50015                 });
50016             }
50017         }
50018     },
50019
50020     /**
50021      * Highlight the specified item. If no item is provided the whole series will be highlighted.
50022      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
50023      */
50024     highlightItem: function(item) {
50025         var me = this,
50026             points, path;
50027         if (!item) {
50028             this.highlightSeries();
50029             return;
50030         }
50031         points = item._points;
50032         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
50033                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
50034         me.highlightSprite.setAttributes({
50035             path: path,
50036             hidden: false
50037         }, true);
50038     },
50039
50040     /**
50041      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
50042      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
50043      */
50044     unHighlightItem: function(item) {
50045         if (!item) {
50046             this.unHighlightSeries();
50047         }
50048
50049         if (this.highlightSprite) {
50050             this.highlightSprite.hide(true);
50051         }
50052     },
50053
50054     // @private
50055     hideAll: function() {
50056         if (!isNaN(this._index)) {
50057             this.__excludes[this._index] = true;
50058             this.areas[this._index].hide(true);
50059             this.drawSeries();
50060         }
50061     },
50062
50063     // @private
50064     showAll: function() {
50065         if (!isNaN(this._index)) {
50066             this.__excludes[this._index] = false;
50067             this.areas[this._index].show(true);
50068             this.drawSeries();
50069         }
50070     },
50071
50072     /**
50073      * Returns the color of the series (to be displayed as color for the series legend item).
50074      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
50075      */
50076     getLegendColor: function(index) {
50077         var me = this;
50078         return me.colorArrayStyle[index % me.colorArrayStyle.length];
50079     }
50080 });
50081
50082 /**
50083  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
50084  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
50085  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
50086  * A typical configuration object for the bar series could be:
50087  *
50088  *     @example
50089  *     var store = Ext.create('Ext.data.JsonStore', {
50090  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
50091  *         data: [
50092  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
50093  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
50094  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
50095  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
50096  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
50097  *         ]
50098  *     });
50099  *
50100  *     Ext.create('Ext.chart.Chart', {
50101  *         renderTo: Ext.getBody(),
50102  *         width: 500,
50103  *         height: 300,
50104  *         animate: true,
50105  *         store: store,
50106  *         axes: [{
50107  *             type: 'Numeric',
50108  *             position: 'bottom',
50109  *             fields: ['data1'],
50110  *             label: {
50111  *                 renderer: Ext.util.Format.numberRenderer('0,0')
50112  *             },
50113  *             title: 'Sample Values',
50114  *             grid: true,
50115  *             minimum: 0
50116  *         }, {
50117  *             type: 'Category',
50118  *             position: 'left',
50119  *             fields: ['name'],
50120  *             title: 'Sample Metrics'
50121  *         }],
50122  *         series: [{
50123  *             type: 'bar',
50124  *             axis: 'bottom',
50125  *             highlight: true,
50126  *             tips: {
50127  *               trackMouse: true,
50128  *               width: 140,
50129  *               height: 28,
50130  *               renderer: function(storeItem, item) {
50131  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
50132  *               }
50133  *             },
50134  *             label: {
50135  *               display: 'insideEnd',
50136  *                 field: 'data1',
50137  *                 renderer: Ext.util.Format.numberRenderer('0'),
50138  *                 orientation: 'horizontal',
50139  *                 color: '#333',
50140  *                 'text-anchor': 'middle'
50141  *             },
50142  *             xField: 'name',
50143  *             yField: ['data1']
50144  *         }]
50145  *     });
50146  *
50147  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
50148  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
50149  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
50150  * to display the information found in the `data1` property of each element store, to render a formated text with the
50151  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
50152  * other styles like `color`, `text-anchor`, etc.
50153  */
50154 Ext.define('Ext.chart.series.Bar', {
50155
50156     /* Begin Definitions */
50157
50158     extend: 'Ext.chart.series.Cartesian',
50159
50160     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
50161
50162     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
50163
50164     /* End Definitions */
50165
50166     type: 'bar',
50167
50168     alias: 'series.bar',
50169     /**
50170      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
50171      */
50172     column: false,
50173
50174     /**
50175      * @cfg style Style properties that will override the theming series styles.
50176      */
50177     style: {},
50178
50179     /**
50180      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
50181      */
50182     gutter: 38.2,
50183
50184     /**
50185      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
50186      */
50187     groupGutter: 38.2,
50188
50189     /**
50190      * @cfg {Number} xPadding Padding between the left/right axes and the bars
50191      */
50192     xPadding: 0,
50193
50194     /**
50195      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
50196      */
50197     yPadding: 10,
50198
50199     constructor: function(config) {
50200         this.callParent(arguments);
50201         var me = this,
50202             surface = me.chart.surface,
50203             shadow = me.chart.shadow,
50204             i, l;
50205         Ext.apply(me, config, {
50206             highlightCfg: {
50207                 lineWidth: 3,
50208                 stroke: '#55c',
50209                 opacity: 0.8,
50210                 color: '#f00'
50211             },
50212
50213             shadowAttributes: [{
50214                 "stroke-width": 6,
50215                 "stroke-opacity": 0.05,
50216                 stroke: 'rgb(200, 200, 200)',
50217                 translate: {
50218                     x: 1.2,
50219                     y: 1.2
50220                 }
50221             }, {
50222                 "stroke-width": 4,
50223                 "stroke-opacity": 0.1,
50224                 stroke: 'rgb(150, 150, 150)',
50225                 translate: {
50226                     x: 0.9,
50227                     y: 0.9
50228                 }
50229             }, {
50230                 "stroke-width": 2,
50231                 "stroke-opacity": 0.15,
50232                 stroke: 'rgb(100, 100, 100)',
50233                 translate: {
50234                     x: 0.6,
50235                     y: 0.6
50236                 }
50237             }]
50238         });
50239         me.group = surface.getGroup(me.seriesId + '-bars');
50240         if (shadow) {
50241             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
50242                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
50243             }
50244         }
50245     },
50246
50247     // @private sets the bar girth.
50248     getBarGirth: function() {
50249         var me = this,
50250             store = me.chart.getChartStore(),
50251             column = me.column,
50252             ln = store.getCount(),
50253             gutter = me.gutter / 100;
50254
50255         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
50256     },
50257
50258     // @private returns the gutters.
50259     getGutters: function() {
50260         var me = this,
50261             column = me.column,
50262             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
50263         return me.column ? [gutter, 0] : [0, gutter];
50264     },
50265
50266     // @private Get chart and data boundaries
50267     getBounds: function() {
50268         var me = this,
50269             chart = me.chart,
50270             store = chart.getChartStore(),
50271             bars = [].concat(me.yField),
50272             barsLen = bars.length,
50273             groupBarsLen = barsLen,
50274             groupGutter = me.groupGutter / 100,
50275             column = me.column,
50276             xPadding = me.xPadding,
50277             yPadding = me.yPadding,
50278             stacked = me.stacked,
50279             barWidth = me.getBarGirth(),
50280             math = Math,
50281             mmax = math.max,
50282             mabs = math.abs,
50283             groupBarWidth, bbox, minY, maxY, axis, out,
50284             scale, zero, total, rec, j, plus, minus;
50285
50286         me.setBBox(true);
50287         bbox = me.bbox;
50288
50289         //Skip excluded series
50290         if (me.__excludes) {
50291             for (j = 0, total = me.__excludes.length; j < total; j++) {
50292                 if (me.__excludes[j]) {
50293                     groupBarsLen--;
50294                 }
50295             }
50296         }
50297
50298         if (me.axis) {
50299             axis = chart.axes.get(me.axis);
50300             if (axis) {
50301                 out = axis.calcEnds();
50302                 minY = out.from;
50303                 maxY = out.to;
50304             }
50305         }
50306
50307         if (me.yField && !Ext.isNumber(minY)) {
50308             axis = Ext.create('Ext.chart.axis.Axis', {
50309                 chart: chart,
50310                 fields: [].concat(me.yField)
50311             });
50312             out = axis.calcEnds();
50313             minY = out.from;
50314             maxY = out.to;
50315         }
50316
50317         if (!Ext.isNumber(minY)) {
50318             minY = 0;
50319         }
50320         if (!Ext.isNumber(maxY)) {
50321             maxY = 0;
50322         }
50323         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
50324         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
50325         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
50326
50327         if (stacked) {
50328             total = [[], []];
50329             store.each(function(record, i) {
50330                 total[0][i] = total[0][i] || 0;
50331                 total[1][i] = total[1][i] || 0;
50332                 for (j = 0; j < barsLen; j++) {
50333                     if (me.__excludes && me.__excludes[j]) {
50334                         continue;
50335                     }
50336                     rec = record.get(bars[j]);
50337                     total[+(rec > 0)][i] += mabs(rec);
50338                 }
50339             });
50340             total[+(maxY > 0)].push(mabs(maxY));
50341             total[+(minY > 0)].push(mabs(minY));
50342             minus = mmax.apply(math, total[0]);
50343             plus = mmax.apply(math, total[1]);
50344             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
50345             zero = zero + minus * scale * (column ? -1 : 1);
50346         }
50347         else if (minY / maxY < 0) {
50348             zero = zero - minY * scale * (column ? -1 : 1);
50349         }
50350         return {
50351             bars: bars,
50352             bbox: bbox,
50353             barsLen: barsLen,
50354             groupBarsLen: groupBarsLen,
50355             barWidth: barWidth,
50356             groupBarWidth: groupBarWidth,
50357             scale: scale,
50358             zero: zero,
50359             xPadding: xPadding,
50360             yPadding: yPadding,
50361             signed: minY / maxY < 0,
50362             minY: minY,
50363             maxY: maxY
50364         };
50365     },
50366
50367     // @private Build an array of paths for the chart
50368     getPaths: function() {
50369         var me = this,
50370             chart = me.chart,
50371             store = chart.getChartStore(),
50372             bounds = me.bounds = me.getBounds(),
50373             items = me.items = [],
50374             gutter = me.gutter / 100,
50375             groupGutter = me.groupGutter / 100,
50376             animate = chart.animate,
50377             column = me.column,
50378             group = me.group,
50379             enableShadows = chart.shadow,
50380             shadowGroups = me.shadowGroups,
50381             shadowAttributes = me.shadowAttributes,
50382             shadowGroupsLn = shadowGroups.length,
50383             bbox = bounds.bbox,
50384             xPadding = me.xPadding,
50385             yPadding = me.yPadding,
50386             stacked = me.stacked,
50387             barsLen = bounds.barsLen,
50388             colors = me.colorArrayStyle,
50389             colorLength = colors && colors.length || 0,
50390             math = Math,
50391             mmax = math.max,
50392             mmin = math.min,
50393             mabs = math.abs,
50394             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
50395             shadowIndex, shadow, sprite, offset, floorY;
50396
50397         store.each(function(record, i, total) {
50398             bottom = bounds.zero;
50399             top = bounds.zero;
50400             totalDim = 0;
50401             totalNegDim = 0;
50402             hasShadow = false;
50403             for (j = 0, counter = 0; j < barsLen; j++) {
50404                 // Excluded series
50405                 if (me.__excludes && me.__excludes[j]) {
50406                     continue;
50407                 }
50408                 yValue = record.get(bounds.bars[j]);
50409                 height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
50410                 barAttr = {
50411                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
50412                 };
50413                 if (column) {
50414                     Ext.apply(barAttr, {
50415                         height: height,
50416                         width: mmax(bounds.groupBarWidth, 0),
50417                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
50418                         y: bottom - height
50419                     });
50420                 }
50421                 else {
50422                     // draw in reverse order
50423                     offset = (total - 1) - i;
50424                     Ext.apply(barAttr, {
50425                         height: mmax(bounds.groupBarWidth, 0),
50426                         width: height + (bottom == bounds.zero),
50427                         x: bottom + (bottom != bounds.zero),
50428                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
50429                     });
50430                 }
50431                 if (height < 0) {
50432                     if (column) {
50433                         barAttr.y = top;
50434                         barAttr.height = mabs(height);
50435                     } else {
50436                         barAttr.x = top + height;
50437                         barAttr.width = mabs(height);
50438                     }
50439                 }
50440                 if (stacked) {
50441                     if (height < 0) {
50442                         top += height * (column ? -1 : 1);
50443                     } else {
50444                         bottom += height * (column ? -1 : 1);
50445                     }
50446                     totalDim += mabs(height);
50447                     if (height < 0) {
50448                         totalNegDim += mabs(height);
50449                     }
50450                 }
50451                 barAttr.x = Math.floor(barAttr.x) + 1;
50452                 floorY = Math.floor(barAttr.y);
50453                 if (!Ext.isIE9 && barAttr.y > floorY) {
50454                     floorY--;
50455                 }
50456                 barAttr.y = floorY;
50457                 barAttr.width = Math.floor(barAttr.width);
50458                 barAttr.height = Math.floor(barAttr.height);
50459                 items.push({
50460                     series: me,
50461                     storeItem: record,
50462                     value: [record.get(me.xField), yValue],
50463                     attr: barAttr,
50464                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
50465                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
50466                 });
50467                 // When resizing, reset before animating
50468                 if (animate && chart.resizing) {
50469                     attrs = column ? {
50470                         x: barAttr.x,
50471                         y: bounds.zero,
50472                         width: barAttr.width,
50473                         height: 0
50474                     } : {
50475                         x: bounds.zero,
50476                         y: barAttr.y,
50477                         width: 0,
50478                         height: barAttr.height
50479                     };
50480                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
50481                         hasShadow = true;
50482                         //update shadows
50483                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
50484                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
50485                             if (shadow) {
50486                                 shadow.setAttributes(attrs, true);
50487                             }
50488                         }
50489                     }
50490                     //update sprite position and width/height
50491                     sprite = group.getAt(i * barsLen + j);
50492                     if (sprite) {
50493                         sprite.setAttributes(attrs, true);
50494                     }
50495                 }
50496                 counter++;
50497             }
50498             if (stacked && items.length) {
50499                 items[i * counter].totalDim = totalDim;
50500                 items[i * counter].totalNegDim = totalNegDim;
50501             }
50502         }, me);
50503     },
50504
50505     // @private render/setAttributes on the shadows
50506     renderShadows: function(i, barAttr, baseAttrs, bounds) {
50507         var me = this,
50508             chart = me.chart,
50509             surface = chart.surface,
50510             animate = chart.animate,
50511             stacked = me.stacked,
50512             shadowGroups = me.shadowGroups,
50513             shadowAttributes = me.shadowAttributes,
50514             shadowGroupsLn = shadowGroups.length,
50515             store = chart.getChartStore(),
50516             column = me.column,
50517             items = me.items,
50518             shadows = [],
50519             zero = bounds.zero,
50520             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
50521
50522         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
50523             j = i / bounds.groupBarsLen;
50524             //create shadows
50525             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
50526                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
50527                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
50528                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
50529                 if (!shadow) {
50530                     shadow = surface.add(Ext.apply({
50531                         type: 'rect',
50532                         group: shadowGroups[shadowIndex]
50533                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
50534                 }
50535                 if (stacked) {
50536                     totalDim = items[i].totalDim;
50537                     totalNegDim = items[i].totalNegDim;
50538                     if (column) {
50539                         shadowBarAttr.y = zero - totalNegDim;
50540                         shadowBarAttr.height = totalDim;
50541                     }
50542                     else {
50543                         shadowBarAttr.x = zero - totalNegDim;
50544                         shadowBarAttr.width = totalDim;
50545                     }
50546                 }
50547                 if (animate) {
50548                     if (!stacked) {
50549                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
50550                         me.onAnimate(shadow, { to: rendererAttributes });
50551                     }
50552                     else {
50553                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
50554                         shadow.setAttributes(rendererAttributes, true);
50555                     }
50556                 }
50557                 else {
50558                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
50559                     shadow.setAttributes(rendererAttributes, true);
50560                 }
50561                 shadows.push(shadow);
50562             }
50563         }
50564         return shadows;
50565     },
50566
50567     /**
50568      * Draws the series for the current chart.
50569      */
50570     drawSeries: function() {
50571         var me = this,
50572             chart = me.chart,
50573             store = chart.getChartStore(),
50574             surface = chart.surface,
50575             animate = chart.animate,
50576             stacked = me.stacked,
50577             column = me.column,
50578             enableShadows = chart.shadow,
50579             shadowGroups = me.shadowGroups,
50580             shadowGroupsLn = shadowGroups.length,
50581             group = me.group,
50582             seriesStyle = me.seriesStyle,
50583             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
50584             bounds, endSeriesStyle, barAttr, attrs, anim;
50585
50586         if (!store || !store.getCount()) {
50587             return;
50588         }
50589
50590         //fill colors are taken from the colors array.
50591         delete seriesStyle.fill;
50592         endSeriesStyle = Ext.apply(seriesStyle, this.style);
50593         me.unHighlightItem();
50594         me.cleanHighlights();
50595
50596         me.getPaths();
50597         bounds = me.bounds;
50598         items = me.items;
50599
50600         baseAttrs = column ? {
50601             y: bounds.zero,
50602             height: 0
50603         } : {
50604             x: bounds.zero,
50605             width: 0
50606         };
50607         ln = items.length;
50608         // Create new or reuse sprites and animate/display
50609         for (i = 0; i < ln; i++) {
50610             sprite = group.getAt(i);
50611             barAttr = items[i].attr;
50612
50613             if (enableShadows) {
50614                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
50615             }
50616
50617             // Create a new sprite if needed (no height)
50618             if (!sprite) {
50619                 attrs = Ext.apply({}, baseAttrs, barAttr);
50620                 attrs = Ext.apply(attrs, endSeriesStyle || {});
50621                 sprite = surface.add(Ext.apply({}, {
50622                     type: 'rect',
50623                     group: group
50624                 }, attrs));
50625             }
50626             if (animate) {
50627                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
50628                 sprite._to = rendererAttributes;
50629                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
50630                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
50631                     j = i / bounds.barsLen;
50632                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
50633                         anim.on('afteranimate', function() {
50634                             this.show(true);
50635                         }, shadowGroups[shadowIndex].getAt(j));
50636                     }
50637                 }
50638             }
50639             else {
50640                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
50641                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
50642             }
50643             items[i].sprite = sprite;
50644         }
50645
50646         // Hide unused sprites
50647         ln = group.getCount();
50648         for (j = i; j < ln; j++) {
50649             group.getAt(j).hide(true);
50650         }
50651         // Hide unused shadows
50652         if (enableShadows) {
50653             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
50654                 shadowGroup = shadowGroups[shadowIndex];
50655                 ln = shadowGroup.getCount();
50656                 for (j = i; j < ln; j++) {
50657                     shadowGroup.getAt(j).hide(true);
50658                 }
50659             }
50660         }
50661         me.renderLabels();
50662     },
50663
50664     // @private handled when creating a label.
50665     onCreateLabel: function(storeItem, item, i, display) {
50666         var me = this,
50667             surface = me.chart.surface,
50668             group = me.labelsGroup,
50669             config = me.label,
50670             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
50671             sprite;
50672         return surface.add(Ext.apply({
50673             type: 'text',
50674             group: group
50675         }, endLabelStyle || {}));
50676     },
50677
50678     // @private callback used when placing a label.
50679     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
50680         // Determine the label's final position. Starts with the configured preferred value but
50681         // may get flipped from inside to outside or vice-versa depending on space.
50682         var me = this,
50683             opt = me.bounds,
50684             groupBarWidth = opt.groupBarWidth,
50685             column = me.column,
50686             chart = me.chart,
50687             chartBBox = chart.chartBBox,
50688             resizing = chart.resizing,
50689             xValue = item.value[0],
50690             yValue = item.value[1],
50691             attr = item.attr,
50692             config = me.label,
50693             rotate = config.orientation == 'vertical',
50694             field = [].concat(config.field),
50695             format = config.renderer,
50696             text = format(storeItem.get(field[index])),
50697             size = me.getLabelSize(text),
50698             width = size.width,
50699             height = size.height,
50700             zero = opt.zero,
50701             outside = 'outside',
50702             insideStart = 'insideStart',
50703             insideEnd = 'insideEnd',
50704             offsetX = 10,
50705             offsetY = 6,
50706             signed = opt.signed,
50707             x, y, finalAttr;
50708
50709         label.setAttributes({
50710             text: text
50711         });
50712
50713         label.isOutside = false;
50714         if (column) {
50715             if (display == outside) {
50716                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
50717                     display = insideEnd;
50718                 }
50719             } else {
50720                 if (height + offsetY > attr.height) {
50721                     display = outside;
50722                     label.isOutside = true;
50723                 }
50724             }
50725             x = attr.x + groupBarWidth / 2;
50726             y = display == insideStart ?
50727                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
50728                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
50729                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
50730         }
50731         else {
50732             if (display == outside) {
50733                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
50734                     display = insideEnd;
50735                 }
50736             }
50737             else {
50738                 if (width + offsetX > attr.width) {
50739                     display = outside;
50740                     label.isOutside = true;
50741                 }
50742             }
50743             x = display == insideStart ?
50744                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
50745                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
50746                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
50747             y = attr.y + groupBarWidth / 2;
50748         }
50749         //set position
50750         finalAttr = {
50751             x: x,
50752             y: y
50753         };
50754         //rotate
50755         if (rotate) {
50756             finalAttr.rotate = {
50757                 x: x,
50758                 y: y,
50759                 degrees: 270
50760             };
50761         }
50762         //check for resizing
50763         if (animate && resizing) {
50764             if (column) {
50765                 x = attr.x + attr.width / 2;
50766                 y = zero;
50767             } else {
50768                 x = zero;
50769                 y = attr.y + attr.height / 2;
50770             }
50771             label.setAttributes({
50772                 x: x,
50773                 y: y
50774             }, true);
50775             if (rotate) {
50776                 label.setAttributes({
50777                     rotate: {
50778                         x: x,
50779                         y: y,
50780                         degrees: 270
50781                     }
50782                 }, true);
50783             }
50784         }
50785         //handle animation
50786         if (animate) {
50787             me.onAnimate(label, { to: finalAttr });
50788         }
50789         else {
50790             label.setAttributes(Ext.apply(finalAttr, {
50791                 hidden: false
50792             }), true);
50793         }
50794     },
50795
50796     /* @private
50797      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
50798      * changing visible sprites.
50799      * @param value
50800      */
50801     getLabelSize: function(value) {
50802         var tester = this.testerLabel,
50803             config = this.label,
50804             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
50805             rotated = config.orientation === 'vertical',
50806             bbox, w, h,
50807             undef;
50808         if (!tester) {
50809             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
50810                 type: 'text',
50811                 opacity: 0
50812             }, endLabelStyle));
50813         }
50814         tester.setAttributes({
50815             text: value
50816         }, true);
50817
50818         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
50819         bbox = tester.getBBox();
50820         w = bbox.width;
50821         h = bbox.height;
50822         return {
50823             width: rotated ? h : w,
50824             height: rotated ? w : h
50825         };
50826     },
50827
50828     // @private used to animate label, markers and other sprites.
50829     onAnimate: function(sprite, attr) {
50830         sprite.show();
50831         return this.callParent(arguments);
50832     },
50833
50834     isItemInPoint: function(x, y, item) {
50835         var bbox = item.sprite.getBBox();
50836         return bbox.x <= x && bbox.y <= y
50837             && (bbox.x + bbox.width) >= x
50838             && (bbox.y + bbox.height) >= y;
50839     },
50840
50841     // @private hide all markers
50842     hideAll: function() {
50843         var axes = this.chart.axes;
50844         if (!isNaN(this._index)) {
50845             if (!this.__excludes) {
50846                 this.__excludes = [];
50847             }
50848             this.__excludes[this._index] = true;
50849             this.drawSeries();
50850             axes.each(function(axis) {
50851                 axis.drawAxis();
50852             });
50853         }
50854     },
50855
50856     // @private show all markers
50857     showAll: function() {
50858         var axes = this.chart.axes;
50859         if (!isNaN(this._index)) {
50860             if (!this.__excludes) {
50861                 this.__excludes = [];
50862             }
50863             this.__excludes[this._index] = false;
50864             this.drawSeries();
50865             axes.each(function(axis) {
50866                 axis.drawAxis();
50867             });
50868         }
50869     },
50870
50871     /**
50872      * Returns a string with the color to be used for the series legend item.
50873      * @param index
50874      */
50875     getLegendColor: function(index) {
50876         var me = this,
50877             colorLength = me.colorArrayStyle.length;
50878
50879         if (me.style && me.style.fill) {
50880             return me.style.fill;
50881         } else {
50882             return me.colorArrayStyle[index % colorLength];
50883         }
50884     },
50885
50886     highlightItem: function(item) {
50887         this.callParent(arguments);
50888         this.renderLabels();
50889     },
50890
50891     unHighlightItem: function() {
50892         this.callParent(arguments);
50893         this.renderLabels();
50894     },
50895
50896     cleanHighlights: function() {
50897         this.callParent(arguments);
50898         this.renderLabels();
50899     }
50900 });
50901 /**
50902  * @class Ext.chart.series.Column
50903  * @extends Ext.chart.series.Bar
50904  *
50905  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
50906  * visualization technique to display quantitative information for different categories that can
50907  * show some progression (or regression) in the data set. As with all other series, the Column Series
50908  * must be appended in the *series* Chart array configuration. See the Chart documentation for more
50909  * information. A typical configuration object for the column series could be:
50910  *
50911  *     @example
50912  *     var store = Ext.create('Ext.data.JsonStore', {
50913  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
50914  *         data: [
50915  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
50916  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
50917  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
50918  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
50919  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
50920  *         ]
50921  *     });
50922  *
50923  *     Ext.create('Ext.chart.Chart', {
50924  *         renderTo: Ext.getBody(),
50925  *         width: 500,
50926  *         height: 300,
50927  *         animate: true,
50928  *         store: store,
50929  *         axes: [
50930  *             {
50931  *                 type: 'Numeric',
50932  *                 position: 'left',
50933  *                 fields: ['data1'],
50934  *                 label: {
50935  *                     renderer: Ext.util.Format.numberRenderer('0,0')
50936  *                 },
50937  *                 title: 'Sample Values',
50938  *                 grid: true,
50939  *                 minimum: 0
50940  *             },
50941  *             {
50942  *                 type: 'Category',
50943  *                 position: 'bottom',
50944  *                 fields: ['name'],
50945  *                 title: 'Sample Metrics'
50946  *             }
50947  *         ],
50948  *         series: [
50949  *             {
50950  *                 type: 'column',
50951  *                 axis: 'left',
50952  *                 highlight: true,
50953  *                 tips: {
50954  *                   trackMouse: true,
50955  *                   width: 140,
50956  *                   height: 28,
50957  *                   renderer: function(storeItem, item) {
50958  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
50959  *                   }
50960  *                 },
50961  *                 label: {
50962  *                   display: 'insideEnd',
50963  *                   'text-anchor': 'middle',
50964  *                     field: 'data1',
50965  *                     renderer: Ext.util.Format.numberRenderer('0'),
50966  *                     orientation: 'vertical',
50967  *                     color: '#333'
50968  *                 },
50969  *                 xField: 'name',
50970  *                 yField: 'data1'
50971  *             }
50972  *         ]
50973  *     });
50974  *
50975  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
50976  * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
50977  * field to the data store `name` property and the `yField` as the data1 property of a store element.
50978  */
50979 Ext.define('Ext.chart.series.Column', {
50980
50981     /* Begin Definitions */
50982
50983     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
50984
50985     extend: 'Ext.chart.series.Bar',
50986
50987     /* End Definitions */
50988
50989     type: 'column',
50990     alias: 'series.column',
50991
50992     column: true,
50993
50994     /**
50995      * @cfg {Number} xPadding
50996      * Padding between the left/right axes and the bars
50997      */
50998     xPadding: 10,
50999
51000     /**
51001      * @cfg {Number} yPadding
51002      * Padding between the top/bottom axes and the bars
51003      */
51004     yPadding: 0
51005 });
51006 /**
51007  * @class Ext.chart.series.Gauge
51008  * @extends Ext.chart.series.Series
51009  * 
51010  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
51011  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
51012  * visualization and using the `setValue` method to adjust the value you want.
51013  *
51014  * A chart/series configuration for the Gauge visualization could look like this:
51015  * 
51016  *     {
51017  *         xtype: 'chart',
51018  *         store: store,
51019  *         axes: [{
51020  *             type: 'gauge',
51021  *             position: 'gauge',
51022  *             minimum: 0,
51023  *             maximum: 100,
51024  *             steps: 10,
51025  *             margin: -10
51026  *         }],
51027  *         series: [{
51028  *             type: 'gauge',
51029  *             field: 'data1',
51030  *             donut: false,
51031  *             colorSet: ['#F49D10', '#ddd']
51032  *         }]
51033  *     }
51034  * 
51035  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
51036  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
51037  * the visual display and the color set to be used with the visualization.
51038  * 
51039  * @xtype gauge
51040  */
51041 Ext.define('Ext.chart.series.Gauge', {
51042
51043     /* Begin Definitions */
51044
51045     extend: 'Ext.chart.series.Series',
51046
51047     /* End Definitions */
51048
51049     type: "gauge",
51050     alias: 'series.gauge',
51051
51052     rad: Math.PI / 180,
51053
51054     /**
51055      * @cfg {Number} highlightDuration
51056      * The duration for the pie slice highlight effect.
51057      */
51058     highlightDuration: 150,
51059
51060     /**
51061      * @cfg {String} angleField (required)
51062      * The store record field name to be used for the pie angles.
51063      * The values bound to this field name must be positive real numbers.
51064      */
51065     angleField: false,
51066
51067     /**
51068      * @cfg {Boolean} needle
51069      * Use the Gauge Series as an area series or add a needle to it. Default's false.
51070      */
51071     needle: false,
51072     
51073     /**
51074      * @cfg {Boolean/Number} donut
51075      * Use the entire disk or just a fraction of it for the gauge. Default's false.
51076      */
51077     donut: false,
51078
51079     /**
51080      * @cfg {Boolean} showInLegend
51081      * Whether to add the pie chart elements as legend items. Default's false.
51082      */
51083     showInLegend: false,
51084
51085     /**
51086      * @cfg {Object} style
51087      * An object containing styles for overriding series styles from Theming.
51088      */
51089     style: {},
51090     
51091     constructor: function(config) {
51092         this.callParent(arguments);
51093         var me = this,
51094             chart = me.chart,
51095             surface = chart.surface,
51096             store = chart.store,
51097             shadow = chart.shadow, i, l, cfg;
51098         Ext.apply(me, config, {
51099             shadowAttributes: [{
51100                 "stroke-width": 6,
51101                 "stroke-opacity": 1,
51102                 stroke: 'rgb(200, 200, 200)',
51103                 translate: {
51104                     x: 1.2,
51105                     y: 2
51106                 }
51107             },
51108             {
51109                 "stroke-width": 4,
51110                 "stroke-opacity": 1,
51111                 stroke: 'rgb(150, 150, 150)',
51112                 translate: {
51113                     x: 0.9,
51114                     y: 1.5
51115                 }
51116             },
51117             {
51118                 "stroke-width": 2,
51119                 "stroke-opacity": 1,
51120                 stroke: 'rgb(100, 100, 100)',
51121                 translate: {
51122                     x: 0.6,
51123                     y: 1
51124                 }
51125             }]
51126         });
51127         me.group = surface.getGroup(me.seriesId);
51128         if (shadow) {
51129             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
51130                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
51131             }
51132         }
51133         surface.customAttributes.segment = function(opt) {
51134             return me.getSegment(opt);
51135         };
51136     },
51137     
51138     //@private updates some onbefore render parameters.
51139     initialize: function() {
51140         var me = this,
51141             store = me.chart.getChartStore();
51142         //Add yFields to be used in Legend.js
51143         me.yField = [];
51144         if (me.label.field) {
51145             store.each(function(rec) {
51146                 me.yField.push(rec.get(me.label.field));
51147             });
51148         }
51149     },
51150
51151     // @private returns an object with properties for a Slice
51152     getSegment: function(opt) {
51153         var me = this,
51154             rad = me.rad,
51155             cos = Math.cos,
51156             sin = Math.sin,
51157             abs = Math.abs,
51158             x = me.centerX,
51159             y = me.centerY,
51160             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
51161             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
51162             delta = 1e-2,
51163             r = opt.endRho - opt.startRho,
51164             startAngle = opt.startAngle,
51165             endAngle = opt.endAngle,
51166             midAngle = (startAngle + endAngle) / 2 * rad,
51167             margin = opt.margin || 0,
51168             flag = abs(endAngle - startAngle) > 180,
51169             a1 = Math.min(startAngle, endAngle) * rad,
51170             a2 = Math.max(startAngle, endAngle) * rad,
51171             singleSlice = false;
51172
51173         x += margin * cos(midAngle);
51174         y += margin * sin(midAngle);
51175
51176         x1 = x + opt.startRho * cos(a1);
51177         y1 = y + opt.startRho * sin(a1);
51178
51179         x2 = x + opt.endRho * cos(a1);
51180         y2 = y + opt.endRho * sin(a1);
51181
51182         x3 = x + opt.startRho * cos(a2);
51183         y3 = y + opt.startRho * sin(a2);
51184
51185         x4 = x + opt.endRho * cos(a2);
51186         y4 = y + opt.endRho * sin(a2);
51187
51188         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
51189             singleSlice = true;
51190         }
51191         //Solves mysterious clipping bug with IE
51192         if (singleSlice) {
51193             return {
51194                 path: [
51195                 ["M", x1, y1],
51196                 ["L", x2, y2],
51197                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
51198                 ["Z"]]
51199             };
51200         } else {
51201             return {
51202                 path: [
51203                 ["M", x1, y1],
51204                 ["L", x2, y2],
51205                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
51206                 ["L", x3, y3],
51207                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
51208                 ["Z"]]
51209             };
51210         }
51211     },
51212
51213     // @private utility function to calculate the middle point of a pie slice.
51214     calcMiddle: function(item) {
51215         var me = this,
51216             rad = me.rad,
51217             slice = item.slice,
51218             x = me.centerX,
51219             y = me.centerY,
51220             startAngle = slice.startAngle,
51221             endAngle = slice.endAngle,
51222             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
51223             donut = +me.donut,
51224             a1 = Math.min(startAngle, endAngle) * rad,
51225             a2 = Math.max(startAngle, endAngle) * rad,
51226             midAngle = -(a1 + (a2 - a1) / 2),
51227             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
51228             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
51229
51230         item.middle = {
51231             x: xm,
51232             y: ym
51233         };
51234     },
51235
51236     /**
51237      * Draws the series for the current chart.
51238      */
51239     drawSeries: function() {
51240         var me = this,
51241             chart = me.chart,
51242             store = chart.getChartStore(),
51243             group = me.group,
51244             animate = me.chart.animate,
51245             axis = me.chart.axes.get(0),
51246             minimum = axis && axis.minimum || me.minimum || 0,
51247             maximum = axis && axis.maximum || me.maximum || 0,
51248             field = me.angleField || me.field || me.xField,
51249             surface = chart.surface,
51250             chartBBox = chart.chartBBox,
51251             rad = me.rad,
51252             donut = +me.donut,
51253             values = {},
51254             items = [],
51255             seriesStyle = me.seriesStyle,
51256             seriesLabelStyle = me.seriesLabelStyle,
51257             colorArrayStyle = me.colorArrayStyle,
51258             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
51259             gutterX = chart.maxGutter[0],
51260             gutterY = chart.maxGutter[1],
51261             cos = Math.cos,
51262             sin = Math.sin,
51263             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
51264             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
51265             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
51266         
51267         Ext.apply(seriesStyle, me.style || {});
51268
51269         me.setBBox();
51270         bbox = me.bbox;
51271
51272         //override theme colors
51273         if (me.colorSet) {
51274             colorArrayStyle = me.colorSet;
51275             colorArrayLength = colorArrayStyle.length;
51276         }
51277         
51278         //if not store or store is empty then there's nothing to draw
51279         if (!store || !store.getCount()) {
51280             return;
51281         }
51282         
51283         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
51284         centerY = me.centerY = chartBBox.y + chartBBox.height;
51285         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
51286         me.slices = slices = [];
51287         me.items = items = [];
51288         
51289         if (!me.value) {
51290             record = store.getAt(0);
51291             me.value = record.get(field);
51292         }
51293         
51294         value = me.value;
51295         if (me.needle) {
51296             sliceA = {
51297                 series: me,
51298                 value: value,
51299                 startAngle: -180,
51300                 endAngle: 0,
51301                 rho: me.radius
51302             };
51303             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
51304             slices.push(sliceA);
51305         } else {
51306             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
51307             sliceA = {
51308                 series: me,
51309                 value: value,
51310                 startAngle: -180,
51311                 endAngle: splitAngle,
51312                 rho: me.radius
51313             };
51314             sliceB = {
51315                 series: me,
51316                 value: me.maximum - value,
51317                 startAngle: splitAngle,
51318                 endAngle: 0,
51319                 rho: me.radius
51320             };
51321             slices.push(sliceA, sliceB);
51322         }
51323         
51324         //do pie slices after.
51325         for (i = 0, ln = slices.length; i < ln; i++) {
51326             slice = slices[i];
51327             sprite = group.getAt(i);
51328             //set pie slice properties
51329             rendererAttributes = Ext.apply({
51330                 segment: {
51331                     startAngle: slice.startAngle,
51332                     endAngle: slice.endAngle,
51333                     margin: 0,
51334                     rho: slice.rho,
51335                     startRho: slice.rho * +donut / 100,
51336                     endRho: slice.rho
51337                 } 
51338             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
51339
51340             item = Ext.apply({},
51341             rendererAttributes.segment, {
51342                 slice: slice,
51343                 series: me,
51344                 storeItem: record,
51345                 index: i
51346             });
51347             items[i] = item;
51348             // Create a new sprite if needed (no height)
51349             if (!sprite) {
51350                 spriteOptions = Ext.apply({
51351                     type: "path",
51352                     group: group
51353                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
51354                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
51355             }
51356             slice.sprite = slice.sprite || [];
51357             item.sprite = sprite;
51358             slice.sprite.push(sprite);
51359             if (animate) {
51360                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
51361                 sprite._to = rendererAttributes;
51362                 me.onAnimate(sprite, {
51363                     to: rendererAttributes
51364                 });
51365             } else {
51366                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
51367                     hidden: false
51368                 }), i, store);
51369                 sprite.setAttributes(rendererAttributes, true);
51370             }
51371         }
51372         
51373         if (me.needle) {
51374             splitAngle = splitAngle * Math.PI / 180;
51375             
51376             if (!me.needleSprite) {
51377                 me.needleSprite = me.chart.surface.add({
51378                     type: 'path',
51379                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
51380                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
51381                            'L', centerX + me.radius * cos(splitAngle),
51382                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
51383                     'stroke-width': 4,
51384                     'stroke': '#222'
51385                 });
51386             } else {
51387                 if (animate) {
51388                     me.onAnimate(me.needleSprite, {
51389                         to: {
51390                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
51391                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
51392                                'L', centerX + me.radius * cos(splitAngle),
51393                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
51394                         }
51395                     });
51396                 } else {
51397                     me.needleSprite.setAttributes({
51398                         type: 'path',
51399                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
51400                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
51401                                'L', centerX + me.radius * cos(splitAngle),
51402                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
51403                     });
51404                 }
51405             }
51406             me.needleSprite.setAttributes({
51407                 hidden: false    
51408             }, true);
51409         }
51410         
51411         delete me.value;
51412     },
51413     
51414     /**
51415      * Sets the Gauge chart to the current specified value.
51416     */
51417     setValue: function (value) {
51418         this.value = value;
51419         this.drawSeries();
51420     },
51421
51422     // @private callback for when creating a label sprite.
51423     onCreateLabel: function(storeItem, item, i, display) {},
51424
51425     // @private callback for when placing a label sprite.
51426     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
51427
51428     // @private callback for when placing a callout.
51429     onPlaceCallout: function() {},
51430
51431     // @private handles sprite animation for the series.
51432     onAnimate: function(sprite, attr) {
51433         sprite.show();
51434         return this.callParent(arguments);
51435     },
51436
51437     isItemInPoint: function(x, y, item, i) {
51438         return false;
51439     },
51440     
51441     // @private shows all elements in the series.
51442     showAll: function() {
51443         if (!isNaN(this._index)) {
51444             this.__excludes[this._index] = false;
51445             this.drawSeries();
51446         }
51447     },
51448     
51449     /**
51450      * Returns the color of the series (to be displayed as color for the series legend item).
51451      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
51452      */
51453     getLegendColor: function(index) {
51454         var me = this;
51455         return me.colorArrayStyle[index % me.colorArrayStyle.length];
51456     }
51457 });
51458
51459
51460 /**
51461  * @class Ext.chart.series.Line
51462  * @extends Ext.chart.series.Cartesian
51463  *
51464  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
51465  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
51466  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
51467  * documentation for more information. A typical configuration object for the line series could be:
51468  *
51469  *     @example
51470  *     var store = Ext.create('Ext.data.JsonStore', {
51471  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
51472  *         data: [
51473  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
51474  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
51475  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
51476  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
51477  *             { 'name': 'metric five',  'data1': 4,  'data2': 4,  'data3': 36, 'data4': 13, 'data5': 33 }
51478  *         ]
51479  *     });
51480  *
51481  *     Ext.create('Ext.chart.Chart', {
51482  *         renderTo: Ext.getBody(),
51483  *         width: 500,
51484  *         height: 300,
51485  *         animate: true,
51486  *         store: store,
51487  *         axes: [
51488  *             {
51489  *                 type: 'Numeric',
51490  *                 position: 'left',
51491  *                 fields: ['data1', 'data2'],
51492  *                 label: {
51493  *                     renderer: Ext.util.Format.numberRenderer('0,0')
51494  *                 },
51495  *                 title: 'Sample Values',
51496  *                 grid: true,
51497  *                 minimum: 0
51498  *             },
51499  *             {
51500  *                 type: 'Category',
51501  *                 position: 'bottom',
51502  *                 fields: ['name'],
51503  *                 title: 'Sample Metrics'
51504  *             }
51505  *         ],
51506  *         series: [
51507  *             {
51508  *                 type: 'line',
51509  *                 highlight: {
51510  *                     size: 7,
51511  *                     radius: 7
51512  *                 },
51513  *                 axis: 'left',
51514  *                 xField: 'name',
51515  *                 yField: 'data1',
51516  *                 markerConfig: {
51517  *                     type: 'cross',
51518  *                     size: 4,
51519  *                     radius: 4,
51520  *                     'stroke-width': 0
51521  *                 }
51522  *             },
51523  *             {
51524  *                 type: 'line',
51525  *                 highlight: {
51526  *                     size: 7,
51527  *                     radius: 7
51528  *                 },
51529  *                 axis: 'left',
51530  *                 fill: true,
51531  *                 xField: 'name',
51532  *                 yField: 'data2',
51533  *                 markerConfig: {
51534  *                     type: 'circle',
51535  *                     size: 4,
51536  *                     radius: 4,
51537  *                     'stroke-width': 0
51538  *                 }
51539  *             }
51540  *         ]
51541  *     });
51542  *
51543  * In this configuration we're adding two series (or lines), one bound to the `data1`
51544  * property of the store and the other to `data3`. The type for both configurations is
51545  * `line`. The `xField` for both series is the same, the name propert of the store.
51546  * Both line series share the same axis, the left axis. You can set particular marker
51547  * configuration by adding properties onto the markerConfig object. Both series have
51548  * an object as highlight so that markers animate smoothly to the properties in highlight
51549  * when hovered. The second series has `fill=true` which means that the line will also
51550  * have an area below it of the same color.
51551  *
51552  * **Note:** In the series definition remember to explicitly set the axis to bind the
51553  * values of the line series to. This can be done by using the `axis` configuration property.
51554  */
51555 Ext.define('Ext.chart.series.Line', {
51556
51557     /* Begin Definitions */
51558
51559     extend: 'Ext.chart.series.Cartesian',
51560
51561     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
51562
51563     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
51564
51565     /* End Definitions */
51566
51567     type: 'line',
51568
51569     alias: 'series.line',
51570
51571     /**
51572      * @cfg {String} axis
51573      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
51574      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
51575      * relative scale will be used.
51576      */
51577
51578     /**
51579      * @cfg {Number} selectionTolerance
51580      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
51581      */
51582     selectionTolerance: 20,
51583
51584     /**
51585      * @cfg {Boolean} showMarkers
51586      * Whether markers should be displayed at the data points along the line. If true,
51587      * then the {@link #markerConfig} config item will determine the markers' styling.
51588      */
51589     showMarkers: true,
51590
51591     /**
51592      * @cfg {Object} markerConfig
51593      * The display style for the markers. Only used if {@link #showMarkers} is true.
51594      * The markerConfig is a configuration object containing the same set of properties defined in
51595      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
51596      * pass the object:
51597      *
51598      <pre><code>
51599         markerConfig: {
51600             type: 'circle',
51601             radius: 4,
51602             'fill': '#f00'
51603         }
51604      </code></pre>
51605
51606      */
51607     markerConfig: {},
51608
51609     /**
51610      * @cfg {Object} style
51611      * An object containing style properties for the visualization lines and fill.
51612      * These styles will override the theme styles.  The following are valid style properties:
51613      *
51614      * - `stroke` - an rgb or hex color string for the background color of the line
51615      * - `stroke-width` - the width of the stroke (integer)
51616      * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
51617      * - `opacity` - the opacity of the line and the fill color (decimal)
51618      *
51619      * Example usage:
51620      *
51621      *     style: {
51622      *         stroke: '#00ff00',
51623      *         'stroke-width': 10,
51624      *         fill: '#80A080',
51625      *         opacity: 0.2
51626      *     }
51627      */
51628     style: {},
51629
51630     /**
51631      * @cfg {Boolean/Number} smooth
51632      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
51633      * straight line segments will be drawn.
51634      *
51635      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
51636      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
51637      *
51638      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
51639      */
51640     smooth: false,
51641
51642     /**
51643      * @private Default numeric smoothing value to be used when {@link #smooth} = true.
51644      */
51645     defaultSmoothness: 3,
51646
51647     /**
51648      * @cfg {Boolean} fill
51649      * If true, the area below the line will be filled in using the {@link #style eefill} and
51650      * {@link #style opacity} config properties. Defaults to false.
51651      */
51652     fill: false,
51653
51654     constructor: function(config) {
51655         this.callParent(arguments);
51656         var me = this,
51657             surface = me.chart.surface,
51658             shadow = me.chart.shadow,
51659             i, l;
51660         Ext.apply(me, config, {
51661             highlightCfg: {
51662                 'stroke-width': 3
51663             },
51664             shadowAttributes: [{
51665                 "stroke-width": 6,
51666                 "stroke-opacity": 0.05,
51667                 stroke: 'rgb(0, 0, 0)',
51668                 translate: {
51669                     x: 1,
51670                     y: 1
51671                 }
51672             }, {
51673                 "stroke-width": 4,
51674                 "stroke-opacity": 0.1,
51675                 stroke: 'rgb(0, 0, 0)',
51676                 translate: {
51677                     x: 1,
51678                     y: 1
51679                 }
51680             }, {
51681                 "stroke-width": 2,
51682                 "stroke-opacity": 0.15,
51683                 stroke: 'rgb(0, 0, 0)',
51684                 translate: {
51685                     x: 1,
51686                     y: 1
51687                 }
51688             }]
51689         });
51690         me.group = surface.getGroup(me.seriesId);
51691         if (me.showMarkers) {
51692             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
51693         }
51694         if (shadow) {
51695             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
51696                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
51697             }
51698         }
51699     },
51700
51701     // @private makes an average of points when there are more data points than pixels to be rendered.
51702     shrink: function(xValues, yValues, size) {
51703         // Start at the 2nd point...
51704         var len = xValues.length,
51705             ratio = Math.floor(len / size),
51706             i = 1,
51707             xSum = 0,
51708             ySum = 0,
51709             xRes = [xValues[0]],
51710             yRes = [yValues[0]];
51711
51712         for (; i < len; ++i) {
51713             xSum += xValues[i] || 0;
51714             ySum += yValues[i] || 0;
51715             if (i % ratio == 0) {
51716                 xRes.push(xSum/ratio);
51717                 yRes.push(ySum/ratio);
51718                 xSum = 0;
51719                 ySum = 0;
51720             }
51721         }
51722         return {
51723             x: xRes,
51724             y: yRes
51725         };
51726     },
51727
51728     /**
51729      * Draws the series for the current chart.
51730      */
51731     drawSeries: function() {
51732         var me = this,
51733             chart = me.chart,
51734             chartAxes = chart.axes,
51735             store = chart.getChartStore(),
51736             storeCount = store.getCount(),
51737             surface = me.chart.surface,
51738             bbox = {},
51739             group = me.group,
51740             showMarkers = me.showMarkers,
51741             markerGroup = me.markerGroup,
51742             enableShadows = chart.shadow,
51743             shadowGroups = me.shadowGroups,
51744             shadowAttributes = me.shadowAttributes,
51745             smooth = me.smooth,
51746             lnsh = shadowGroups.length,
51747             dummyPath = ["M"],
51748             path = ["M"],
51749             renderPath = ["M"],
51750             smoothPath = ["M"],
51751             markerIndex = chart.markerIndex,
51752             axes = [].concat(me.axis),
51753             shadowBarAttr,
51754             xValues = [],
51755             xValueMap = {},
51756             yValues = [],
51757             yValueMap = {},
51758             onbreak = false,
51759             storeIndices = [],
51760             markerStyle = me.markerStyle,
51761             seriesStyle = me.style,
51762             colorArrayStyle = me.colorArrayStyle,
51763             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
51764             isNumber = Ext.isNumber,
51765             seriesIdx = me.seriesIdx, 
51766             boundAxes = me.getAxesForXAndYFields(),
51767             boundXAxis = boundAxes.xAxis,
51768             boundYAxis = boundAxes.yAxis,
51769             shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
51770             x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
51771             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
51772             endLineStyle, type, count, items;
51773
51774         if (me.fireEvent('beforedraw', me) === false) {
51775             return;
51776         }
51777
51778         //if store is empty or the series is excluded in the legend then there's nothing to draw.
51779         if (!storeCount || me.seriesIsHidden) {
51780             items = this.items;
51781             if (items) {
51782                 for (i = 0, ln = items.length; i < ln; ++i) {
51783                     if (items[i].sprite) {
51784                         items[i].sprite.hide(true);
51785                     }
51786                 }
51787             }
51788             return;
51789         }
51790
51791         //prepare style objects for line and markers
51792         endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
51793         type = endMarkerStyle.type;
51794         delete endMarkerStyle.type;
51795         endLineStyle = seriesStyle;
51796         //if no stroke with is specified force it to 0.5 because this is
51797         //about making *lines*
51798         if (!endLineStyle['stroke-width']) {
51799             endLineStyle['stroke-width'] = 0.5;
51800         }
51801         //If we're using a time axis and we need to translate the points,
51802         //then reuse the first markers as the last markers.
51803         if (markerIndex && markerGroup && markerGroup.getCount()) {
51804             for (i = 0; i < markerIndex; i++) {
51805                 marker = markerGroup.getAt(i);
51806                 markerGroup.remove(marker);
51807                 markerGroup.add(marker);
51808                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
51809                 marker.setAttributes({
51810                     x: 0,
51811                     y: 0,
51812                     translate: {
51813                         x: markerAux.attr.translation.x,
51814                         y: markerAux.attr.translation.y
51815                     }
51816                 }, true);
51817             }
51818         }
51819
51820         me.unHighlightItem();
51821         me.cleanHighlights();
51822
51823         me.setBBox();
51824         bbox = me.bbox;
51825         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
51826         for (i = 0, ln = axes.length; i < ln; i++) {
51827             axis = chartAxes.get(axes[i]);
51828             if (axis) {
51829                 ends = axis.calcEnds();
51830                 if (axis.position == 'top' || axis.position == 'bottom') {
51831                     minX = ends.from;
51832                     maxX = ends.to;
51833                 }
51834                 else {
51835                     minY = ends.from;
51836                     maxY = ends.to;
51837                 }
51838             }
51839         }
51840         // If a field was specified without a corresponding axis, create one to get bounds
51841         //only do this for the axis where real values are bound (that's why we check for
51842         //me.axis)
51843         if (me.xField && !isNumber(minX) &&
51844             (boundXAxis == 'bottom' || boundXAxis == 'top') && 
51845             !chartAxes.get(boundXAxis)) {
51846             axis = Ext.create('Ext.chart.axis.Axis', {
51847                 chart: chart,
51848                 fields: [].concat(me.xField)
51849             }).calcEnds();
51850             minX = axis.from;
51851             maxX = axis.to;
51852         }
51853         if (me.yField && !isNumber(minY) &&
51854             (boundYAxis == 'right' || boundYAxis == 'left') &&
51855             !chartAxes.get(boundYAxis)) {
51856             axis = Ext.create('Ext.chart.axis.Axis', {
51857                 chart: chart,
51858                 fields: [].concat(me.yField)
51859             }).calcEnds();
51860             minY = axis.from;
51861             maxY = axis.to;
51862         }
51863         if (isNaN(minX)) {
51864             minX = 0;
51865             xScale = bbox.width / ((storeCount - 1) || 1);
51866         }
51867         else {
51868             xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
51869         }
51870
51871         if (isNaN(minY)) {
51872             minY = 0;
51873             yScale = bbox.height / ((storeCount - 1) || 1);
51874         }
51875         else {
51876             yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
51877         }
51878
51879         // Extract all x and y values from the store
51880         me.eachRecord(function(record, i) {
51881             xValue = record.get(me.xField);
51882
51883             // Ensure a value
51884             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
51885                 //set as uniform distribution if the axis is a category axis.
51886                 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
51887                     if (xValue in xValueMap) {
51888                         xValue = xValueMap[xValue];
51889                     } else {
51890                         xValue = xValueMap[xValue] = i;
51891                     }
51892             }
51893
51894             // Filter out values that don't fit within the pan/zoom buffer area
51895             yValue = record.get(me.yField);
51896             //skip undefined values
51897             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
51898                 //<debug warn>
51899                 if (Ext.isDefined(Ext.global.console)) {
51900                     Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ", record, xValue, yValue);
51901                 }
51902                 //</debug>
51903                 return;
51904             }
51905             // Ensure a value
51906             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
51907                 //set as uniform distribution if the axis is a category axis.
51908                 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
51909                 yValue = i;
51910             }
51911             storeIndices.push(i);
51912             xValues.push(xValue);
51913             yValues.push(yValue);
51914         });
51915
51916         ln = xValues.length;
51917         if (ln > bbox.width) {
51918             coords = me.shrink(xValues, yValues, bbox.width);
51919             xValues = coords.x;
51920             yValues = coords.y;
51921         }
51922
51923         me.items = [];
51924
51925         count = 0;
51926         ln = xValues.length;
51927         for (i = 0; i < ln; i++) {
51928             xValue = xValues[i];
51929             yValue = yValues[i];
51930             if (yValue === false) {
51931                 if (path.length == 1) {
51932                     path = [];
51933                 }
51934                 onbreak = true;
51935                 me.items.push(false);
51936                 continue;
51937             } else {
51938                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
51939                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
51940                 if (onbreak) {
51941                     onbreak = false;
51942                     path.push('M');
51943                 }
51944                 path = path.concat([x, y]);
51945             }
51946             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
51947                 firstY = y;
51948                 firstX = x;
51949             }
51950             // If this is the first line, create a dummypath to animate in from.
51951             if (!me.line || chart.resizing) {
51952                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
51953             }
51954
51955             // When resizing, reset before animating
51956             if (chart.animate && chart.resizing && me.line) {
51957                 me.line.setAttributes({
51958                     path: dummyPath
51959                 }, true);
51960                 if (me.fillPath) {
51961                     me.fillPath.setAttributes({
51962                         path: dummyPath,
51963                         opacity: 0.2
51964                     }, true);
51965                 }
51966                 if (me.line.shadows) {
51967                     shadows = me.line.shadows;
51968                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
51969                         shadow = shadows[j];
51970                         shadow.setAttributes({
51971                             path: dummyPath
51972                         }, true);
51973                     }
51974                 }
51975             }
51976             if (showMarkers) {
51977                 marker = markerGroup.getAt(count++);
51978                 if (!marker) {
51979                     marker = Ext.chart.Shape[type](surface, Ext.apply({
51980                         group: [group, markerGroup],
51981                         x: 0, y: 0,
51982                         translate: {
51983                             x: +(prevX || x),
51984                             y: prevY || (bbox.y + bbox.height / 2)
51985                         },
51986                         value: '"' + xValue + ', ' + yValue + '"',
51987                         zIndex: 4000
51988                     }, endMarkerStyle));
51989                     marker._to = {
51990                         translate: {
51991                             x: +x,
51992                             y: +y
51993                         }
51994                     };
51995                 } else {
51996                     marker.setAttributes({
51997                         value: '"' + xValue + ', ' + yValue + '"',
51998                         x: 0, y: 0,
51999                         hidden: false
52000                     }, true);
52001                     marker._to = {
52002                         translate: {
52003                             x: +x, 
52004                             y: +y
52005                         }
52006                     };
52007                 }
52008             }
52009             me.items.push({
52010                 series: me,
52011                 value: [xValue, yValue],
52012                 point: [x, y],
52013                 sprite: marker,
52014                 storeItem: store.getAt(storeIndices[i])
52015             });
52016             prevX = x;
52017             prevY = y;
52018         }
52019
52020         if (path.length <= 1) {
52021             //nothing to be rendered
52022             return;
52023         }
52024
52025         if (me.smooth) {
52026             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
52027         }
52028
52029         renderPath = smooth ? smoothPath : path;
52030
52031         //Correct path if we're animating timeAxis intervals
52032         if (chart.markerIndex && me.previousPath) {
52033             fromPath = me.previousPath;
52034             if (!smooth) {
52035                 Ext.Array.erase(fromPath, 1, 2);
52036             }
52037         } else {
52038             fromPath = path;
52039         }
52040
52041         // Only create a line if one doesn't exist.
52042         if (!me.line) {
52043             me.line = surface.add(Ext.apply({
52044                 type: 'path',
52045                 group: group,
52046                 path: dummyPath,
52047                 stroke: endLineStyle.stroke || endLineStyle.fill
52048             }, endLineStyle || {}));
52049
52050             if (enableShadows) {
52051                 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
52052             }
52053
52054             //unset fill here (there's always a default fill withing the themes).
52055             me.line.setAttributes({
52056                 fill: 'none',
52057                 zIndex: 3000
52058             });
52059             if (!endLineStyle.stroke && colorArrayLength) {
52060                 me.line.setAttributes({
52061                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
52062                 }, true);
52063             }
52064             if (enableShadows) {
52065                 //create shadows
52066                 shadows = me.line.shadows = [];
52067                 for (shindex = 0; shindex < lnsh; shindex++) {
52068                     shadowBarAttr = shadowAttributes[shindex];
52069                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
52070                     shadow = surface.add(Ext.apply({}, {
52071                         type: 'path',
52072                         group: shadowGroups[shindex]
52073                     }, shadowBarAttr));
52074                     shadows.push(shadow);
52075                 }
52076             }
52077         }
52078         if (me.fill) {
52079             fillPath = renderPath.concat([
52080                 ["L", x, bbox.y + bbox.height],
52081                 ["L", firstX, bbox.y + bbox.height],
52082                 ["L", firstX, firstY]
52083             ]);
52084             if (!me.fillPath) {
52085                 me.fillPath = surface.add({
52086                     group: group,
52087                     type: 'path',
52088                     opacity: endLineStyle.opacity || 0.3,
52089                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
52090                     path: dummyPath
52091                 });
52092             }
52093         }
52094         markerCount = showMarkers && markerGroup.getCount();
52095         if (chart.animate) {
52096             fill = me.fill;
52097             line = me.line;
52098             //Add renderer to line. There is not unique record associated with this.
52099             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
52100             Ext.apply(rendererAttributes, endLineStyle || {}, {
52101                 stroke: endLineStyle.stroke || endLineStyle.fill
52102             });
52103             //fill should not be used here but when drawing the special fill path object
52104             delete rendererAttributes.fill;
52105             line.show(true);
52106             if (chart.markerIndex && me.previousPath) {
52107                 me.animation = animation = me.onAnimate(line, {
52108                     to: rendererAttributes,
52109                     from: {
52110                         path: fromPath
52111                     }
52112                 });
52113             } else {
52114                 me.animation = animation = me.onAnimate(line, {
52115                     to: rendererAttributes
52116                 });
52117             }
52118             //animate shadows
52119             if (enableShadows) {
52120                 shadows = line.shadows;
52121                 for(j = 0; j < lnsh; j++) {
52122                     shadows[j].show(true);
52123                     if (chart.markerIndex && me.previousPath) {
52124                         me.onAnimate(shadows[j], {
52125                             to: { path: renderPath },
52126                             from: { path: fromPath }
52127                         });
52128                     } else {
52129                         me.onAnimate(shadows[j], {
52130                             to: { path: renderPath }
52131                         });
52132                     }
52133                 }
52134             }
52135             //animate fill path
52136             if (fill) {
52137                 me.fillPath.show(true);
52138                 me.onAnimate(me.fillPath, {
52139                     to: Ext.apply({}, {
52140                         path: fillPath,
52141                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
52142                         'stroke-width': 0
52143                     }, endLineStyle || {})
52144                 });
52145             }
52146             //animate markers
52147             if (showMarkers) {
52148                 count = 0;
52149                 for(i = 0; i < ln; i++) {
52150                     if (me.items[i]) {
52151                         item = markerGroup.getAt(count++);
52152                         if (item) {
52153                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
52154                             me.onAnimate(item, {
52155                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
52156                             });
52157                             item.show(true);
52158                         }
52159                     }
52160                 }
52161                 for(; count < markerCount; count++) {
52162                     item = markerGroup.getAt(count);
52163                     item.hide(true);
52164                 }
52165 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
52166 //                    item = markerGroup.getAt(i);
52167 //                    item.hide(true);
52168 //                }
52169             }
52170         } else {
52171             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
52172             Ext.apply(rendererAttributes, endLineStyle || {}, {
52173                 stroke: endLineStyle.stroke || endLineStyle.fill
52174             });
52175             //fill should not be used here but when drawing the special fill path object
52176             delete rendererAttributes.fill;
52177             me.line.setAttributes(rendererAttributes, true);
52178             //set path for shadows
52179             if (enableShadows) {
52180                 shadows = me.line.shadows;
52181                 for(j = 0; j < lnsh; j++) {
52182                     shadows[j].setAttributes({
52183                         path: renderPath,
52184                         hidden: false
52185                     }, true);
52186                 }
52187             }
52188             if (me.fill) {
52189                 me.fillPath.setAttributes({
52190                     path: fillPath,
52191                     hidden: false
52192                 }, true);
52193             }
52194             if (showMarkers) {
52195                 count = 0;
52196                 for(i = 0; i < ln; i++) {
52197                     if (me.items[i]) {
52198                         item = markerGroup.getAt(count++);
52199                         if (item) {
52200                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
52201                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
52202                             item.show(true);
52203                         }
52204                     }
52205                 }
52206                 for(; count < markerCount; count++) {
52207                     item = markerGroup.getAt(count);
52208                     item.hide(true);
52209                 }
52210             }
52211         }
52212
52213         if (chart.markerIndex) {
52214             if (me.smooth) {
52215                 Ext.Array.erase(path, 1, 2);
52216             } else {
52217                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
52218             }
52219             me.previousPath = path;
52220         }
52221         me.renderLabels();
52222         me.renderCallouts();
52223
52224         me.fireEvent('draw', me);
52225     },
52226
52227     // @private called when a label is to be created.
52228     onCreateLabel: function(storeItem, item, i, display) {
52229         var me = this,
52230             group = me.labelsGroup,
52231             config = me.label,
52232             bbox = me.bbox,
52233             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
52234
52235         return me.chart.surface.add(Ext.apply({
52236             'type': 'text',
52237             'text-anchor': 'middle',
52238             'group': group,
52239             'x': item.point[0],
52240             'y': bbox.y + bbox.height / 2
52241         }, endLabelStyle || {}));
52242     },
52243
52244     // @private called when a label is to be created.
52245     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
52246         var me = this,
52247             chart = me.chart,
52248             resizing = chart.resizing,
52249             config = me.label,
52250             format = config.renderer,
52251             field = config.field,
52252             bbox = me.bbox,
52253             x = item.point[0],
52254             y = item.point[1],
52255             radius = item.sprite.attr.radius,
52256             bb, width, height;
52257
52258         label.setAttributes({
52259             text: format(storeItem.get(field)),
52260             hidden: true
52261         }, true);
52262
52263         if (display == 'rotate') {
52264             label.setAttributes({
52265                 'text-anchor': 'start',
52266                 'rotation': {
52267                     x: x,
52268                     y: y,
52269                     degrees: -45
52270                 }
52271             }, true);
52272             //correct label position to fit into the box
52273             bb = label.getBBox();
52274             width = bb.width;
52275             height = bb.height;
52276             x = x < bbox.x? bbox.x : x;
52277             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
52278             y = (y - height < bbox.y)? bbox.y + height : y;
52279
52280         } else if (display == 'under' || display == 'over') {
52281             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
52282             bb = item.sprite.getBBox();
52283             bb.width = bb.width || (radius * 2);
52284             bb.height = bb.height || (radius * 2);
52285             y = y + (display == 'over'? -bb.height : bb.height);
52286             //correct label position to fit into the box
52287             bb = label.getBBox();
52288             width = bb.width/2;
52289             height = bb.height/2;
52290             x = x - width < bbox.x? bbox.x + width : x;
52291             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
52292             y = y - height < bbox.y? bbox.y + height : y;
52293             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
52294         }
52295
52296         if (me.chart.animate && !me.chart.resizing) {
52297             label.show(true);
52298             me.onAnimate(label, {
52299                 to: {
52300                     x: x,
52301                     y: y
52302                 }
52303             });
52304         } else {
52305             label.setAttributes({
52306                 x: x,
52307                 y: y
52308             }, true);
52309             if (resizing && me.animation) {
52310                 me.animation.on('afteranimate', function() {
52311                     label.show(true);
52312                 });
52313             } else {
52314                 label.show(true);
52315             }
52316         }
52317     },
52318
52319     //@private Overriding highlights.js highlightItem method.
52320     highlightItem: function() {
52321         var me = this;
52322         me.callParent(arguments);
52323         if (me.line && !me.highlighted) {
52324             if (!('__strokeWidth' in me.line)) {
52325                 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
52326             }
52327             if (me.line.__anim) {
52328                 me.line.__anim.paused = true;
52329             }
52330             me.line.__anim = Ext.create('Ext.fx.Anim', {
52331                 target: me.line,
52332                 to: {
52333                     'stroke-width': me.line.__strokeWidth + 3
52334                 }
52335             });
52336             me.highlighted = true;
52337         }
52338     },
52339
52340     //@private Overriding highlights.js unHighlightItem method.
52341     unHighlightItem: function() {
52342         var me = this;
52343         me.callParent(arguments);
52344         if (me.line && me.highlighted) {
52345             me.line.__anim = Ext.create('Ext.fx.Anim', {
52346                 target: me.line,
52347                 to: {
52348                     'stroke-width': me.line.__strokeWidth
52349                 }
52350             });
52351             me.highlighted = false;
52352         }
52353     },
52354
52355     //@private called when a callout needs to be placed.
52356     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
52357         if (!display) {
52358             return;
52359         }
52360
52361         var me = this,
52362             chart = me.chart,
52363             surface = chart.surface,
52364             resizing = chart.resizing,
52365             config = me.callouts,
52366             items = me.items,
52367             prev = i == 0? false : items[i -1].point,
52368             next = (i == items.length -1)? false : items[i +1].point,
52369             cur = [+item.point[0], +item.point[1]],
52370             dir, norm, normal, a, aprev, anext,
52371             offsetFromViz = config.offsetFromViz || 30,
52372             offsetToSide = config.offsetToSide || 10,
52373             offsetBox = config.offsetBox || 3,
52374             boxx, boxy, boxw, boxh,
52375             p, clipRect = me.clipRect,
52376             bbox = {
52377                 width: config.styles.width || 10,
52378                 height: config.styles.height || 10
52379             },
52380             x, y;
52381
52382         //get the right two points
52383         if (!prev) {
52384             prev = cur;
52385         }
52386         if (!next) {
52387             next = cur;
52388         }
52389         a = (next[1] - prev[1]) / (next[0] - prev[0]);
52390         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
52391         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
52392
52393         norm = Math.sqrt(1 + a * a);
52394         dir = [1 / norm, a / norm];
52395         normal = [-dir[1], dir[0]];
52396
52397         //keep the label always on the outer part of the "elbow"
52398         if (aprev > 0 && anext < 0 && normal[1] < 0
52399             || aprev < 0 && anext > 0 && normal[1] > 0) {
52400             normal[0] *= -1;
52401             normal[1] *= -1;
52402         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
52403                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
52404             normal[0] *= -1;
52405             normal[1] *= -1;
52406         }
52407         //position
52408         x = cur[0] + normal[0] * offsetFromViz;
52409         y = cur[1] + normal[1] * offsetFromViz;
52410
52411         //box position and dimensions
52412         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
52413         boxy = y - bbox.height /2 - offsetBox;
52414         boxw = bbox.width + 2 * offsetBox;
52415         boxh = bbox.height + 2 * offsetBox;
52416
52417         //now check if we're out of bounds and invert the normal vector correspondingly
52418         //this may add new overlaps between labels (but labels won't be out of bounds).
52419         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
52420             normal[0] *= -1;
52421         }
52422         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
52423             normal[1] *= -1;
52424         }
52425
52426         //update positions
52427         x = cur[0] + normal[0] * offsetFromViz;
52428         y = cur[1] + normal[1] * offsetFromViz;
52429
52430         //update box position and dimensions
52431         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
52432         boxy = y - bbox.height /2 - offsetBox;
52433         boxw = bbox.width + 2 * offsetBox;
52434         boxh = bbox.height + 2 * offsetBox;
52435
52436         if (chart.animate) {
52437             //set the line from the middle of the pie to the box.
52438             me.onAnimate(callout.lines, {
52439                 to: {
52440                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
52441                 }
52442             });
52443             //set component position
52444             if (callout.panel) {
52445                 callout.panel.setPosition(boxx, boxy, true);
52446             }
52447         }
52448         else {
52449             //set the line from the middle of the pie to the box.
52450             callout.lines.setAttributes({
52451                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
52452             }, true);
52453             //set component position
52454             if (callout.panel) {
52455                 callout.panel.setPosition(boxx, boxy);
52456             }
52457         }
52458         for (p in callout) {
52459             callout[p].show(true);
52460         }
52461     },
52462
52463     isItemInPoint: function(x, y, item, i) {
52464         var me = this,
52465             items = me.items,
52466             tolerance = me.selectionTolerance,
52467             result = null,
52468             prevItem,
52469             nextItem,
52470             prevPoint,
52471             nextPoint,
52472             ln,
52473             x1,
52474             y1,
52475             x2,
52476             y2,
52477             xIntersect,
52478             yIntersect,
52479             dist1, dist2, dist, midx, midy,
52480             sqrt = Math.sqrt, abs = Math.abs;
52481
52482         nextItem = items[i];
52483         prevItem = i && items[i - 1];
52484
52485         if (i >= ln) {
52486             prevItem = items[ln - 1];
52487         }
52488         prevPoint = prevItem && prevItem.point;
52489         nextPoint = nextItem && nextItem.point;
52490         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
52491         y1 = prevItem ? prevPoint[1] : nextPoint[1];
52492         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
52493         y2 = nextItem ? nextPoint[1] : prevPoint[1];
52494         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
52495         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
52496         dist = Math.min(dist1, dist2);
52497
52498         if (dist <= tolerance) {
52499             return dist == dist1? prevItem : nextItem;
52500         }
52501         return false;
52502     },
52503
52504     // @private toggle visibility of all series elements (markers, sprites).
52505     toggleAll: function(show) {
52506         var me = this,
52507             i, ln, shadow, shadows;
52508         if (!show) {
52509             Ext.chart.series.Cartesian.prototype.hideAll.call(me);
52510         }
52511         else {
52512             Ext.chart.series.Cartesian.prototype.showAll.call(me);
52513         }
52514         if (me.line) {
52515             me.line.setAttributes({
52516                 hidden: !show
52517             }, true);
52518             //hide shadows too
52519             if (me.line.shadows) {
52520                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
52521                     shadow = shadows[i];
52522                     shadow.setAttributes({
52523                         hidden: !show
52524                     }, true);
52525                 }
52526             }
52527         }
52528         if (me.fillPath) {
52529             me.fillPath.setAttributes({
52530                 hidden: !show
52531             }, true);
52532         }
52533     },
52534
52535     // @private hide all series elements (markers, sprites).
52536     hideAll: function() {
52537         this.toggleAll(false);
52538     },
52539
52540     // @private hide all series elements (markers, sprites).
52541     showAll: function() {
52542         this.toggleAll(true);
52543     }
52544 });
52545
52546 /**
52547  * @class Ext.chart.series.Pie
52548  * @extends Ext.chart.series.Series
52549  *
52550  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
52551  * categories that also have a meaning as a whole.
52552  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
52553  * documentation for more information. A typical configuration object for the pie series could be:
52554  *
52555  *     @example
52556  *     var store = Ext.create('Ext.data.JsonStore', {
52557  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
52558  *         data: [
52559  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
52560  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
52561  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
52562  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
52563  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
52564  *         ]
52565  *     });
52566  *
52567  *     Ext.create('Ext.chart.Chart', {
52568  *         renderTo: Ext.getBody(),
52569  *         width: 500,
52570  *         height: 350,
52571  *         animate: true,
52572  *         store: store,
52573  *         theme: 'Base:gradients',
52574  *         series: [{
52575  *             type: 'pie',
52576  *             field: 'data1',
52577  *             showInLegend: true,
52578  *             tips: {
52579  *                 trackMouse: true,
52580  *                 width: 140,
52581  *                 height: 28,
52582  *                 renderer: function(storeItem, item) {
52583  *                     // calculate and display percentage on hover
52584  *                     var total = 0;
52585  *                     store.each(function(rec) {
52586  *                         total += rec.get('data1');
52587  *                     });
52588  *                     this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
52589  *                 }
52590  *             },
52591  *             highlight: {
52592  *                 segment: {
52593  *                     margin: 20
52594  *                 }
52595  *             },
52596  *             label: {
52597  *                 field: 'name',
52598  *                 display: 'rotate',
52599  *                 contrast: true,
52600  *                 font: '18px Arial'
52601  *             }
52602  *         }]
52603  *     });
52604  *
52605  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
52606  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
52607  *
52608  * 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
52609  * 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.
52610  *
52611  * 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
52612  * and size through the `font` parameter.
52613  *
52614  * @xtype pie
52615  */
52616 Ext.define('Ext.chart.series.Pie', {
52617
52618     /* Begin Definitions */
52619
52620     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
52621
52622     extend: 'Ext.chart.series.Series',
52623
52624     /* End Definitions */
52625
52626     type: "pie",
52627
52628     alias: 'series.pie',
52629
52630     rad: Math.PI / 180,
52631
52632     /**
52633      * @cfg {Number} highlightDuration
52634      * The duration for the pie slice highlight effect.
52635      */
52636     highlightDuration: 150,
52637
52638     /**
52639      * @cfg {String} angleField (required)
52640      * The store record field name to be used for the pie angles.
52641      * The values bound to this field name must be positive real numbers.
52642      */
52643     angleField: false,
52644
52645     /**
52646      * @cfg {String} lengthField
52647      * The store record field name to be used for the pie slice lengths.
52648      * The values bound to this field name must be positive real numbers.
52649      */
52650     lengthField: false,
52651
52652     /**
52653      * @cfg {Boolean/Number} donut
52654      * Whether to set the pie chart as donut chart.
52655      * Default's false. Can be set to a particular percentage to set the radius
52656      * of the donut chart.
52657      */
52658     donut: false,
52659
52660     /**
52661      * @cfg {Boolean} showInLegend
52662      * Whether to add the pie chart elements as legend items. Default's false.
52663      */
52664     showInLegend: false,
52665
52666     /**
52667      * @cfg {Array} colorSet
52668      * An array of color values which will be used, in order, as the pie slice fill colors.
52669      */
52670
52671     /**
52672      * @cfg {Object} style
52673      * An object containing styles for overriding series styles from Theming.
52674      */
52675     style: {},
52676
52677     constructor: function(config) {
52678         this.callParent(arguments);
52679         var me = this,
52680             chart = me.chart,
52681             surface = chart.surface,
52682             store = chart.store,
52683             shadow = chart.shadow, i, l, cfg;
52684         Ext.applyIf(me, {
52685             highlightCfg: {
52686                 segment: {
52687                     margin: 20
52688                 }
52689             }
52690         });
52691         Ext.apply(me, config, {
52692             shadowAttributes: [{
52693                 "stroke-width": 6,
52694                 "stroke-opacity": 1,
52695                 stroke: 'rgb(200, 200, 200)',
52696                 translate: {
52697                     x: 1.2,
52698                     y: 2
52699                 }
52700             },
52701             {
52702                 "stroke-width": 4,
52703                 "stroke-opacity": 1,
52704                 stroke: 'rgb(150, 150, 150)',
52705                 translate: {
52706                     x: 0.9,
52707                     y: 1.5
52708                 }
52709             },
52710             {
52711                 "stroke-width": 2,
52712                 "stroke-opacity": 1,
52713                 stroke: 'rgb(100, 100, 100)',
52714                 translate: {
52715                     x: 0.6,
52716                     y: 1
52717                 }
52718             }]
52719         });
52720         me.group = surface.getGroup(me.seriesId);
52721         if (shadow) {
52722             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
52723                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
52724             }
52725         }
52726         surface.customAttributes.segment = function(opt) {
52727             return me.getSegment(opt);
52728         };
52729         me.__excludes = me.__excludes || [];
52730     },
52731
52732     //@private updates some onbefore render parameters.
52733     initialize: function() {
52734         var me = this,
52735             store = me.chart.getChartStore();
52736         //Add yFields to be used in Legend.js
52737         me.yField = [];
52738         if (me.label.field) {
52739             store.each(function(rec) {
52740                 me.yField.push(rec.get(me.label.field));
52741             });
52742         }
52743     },
52744
52745     // @private returns an object with properties for a PieSlice.
52746     getSegment: function(opt) {
52747         var me = this,
52748             rad = me.rad,
52749             cos = Math.cos,
52750             sin = Math.sin,
52751             x = me.centerX,
52752             y = me.centerY,
52753             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
52754             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
52755             x5 = 0, y5 = 0, x6 = 0, y6 = 0,
52756             delta = 1e-2,
52757             startAngle = opt.startAngle,
52758             endAngle = opt.endAngle,
52759             midAngle = (startAngle + endAngle) / 2 * rad,
52760             margin = opt.margin || 0,
52761             a1 = Math.min(startAngle, endAngle) * rad,
52762             a2 = Math.max(startAngle, endAngle) * rad,
52763             c1 = cos(a1), s1 = sin(a1),
52764             c2 = cos(a2), s2 = sin(a2),
52765             cm = cos(midAngle), sm = sin(midAngle),
52766             flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
52767
52768         if (a2 - a1 < delta) {
52769             return {path: ""};
52770         }
52771
52772         if (margin !== 0) {
52773             x += margin * cm;
52774             y += margin * sm;
52775         }
52776
52777         x2 = x + opt.endRho * c1;
52778         y2 = y + opt.endRho * s1;
52779
52780         x4 = x + opt.endRho * c2;
52781         y4 = y + opt.endRho * s2;
52782
52783         if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
52784             cm = hsqr2;
52785             sm = -hsqr2;
52786             flag = 1;
52787         }
52788
52789         x6 = x + opt.endRho * cm;
52790         y6 = y + opt.endRho * sm;
52791
52792         // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
52793         // Better fix the VML engine for half circles.
52794
52795         if (opt.startRho !== 0) {
52796             x1 = x + opt.startRho * c1;
52797             y1 = y + opt.startRho * s1;
52798     
52799             x3 = x + opt.startRho * c2;
52800             y3 = y + opt.startRho * s2;
52801     
52802             x5 = x + opt.startRho * cm;
52803             y5 = y + opt.startRho * sm;
52804
52805             return {
52806                 path: [
52807                     ["M", x2, y2],
52808                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
52809                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
52810                     ["L", x3, y3],
52811                     ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
52812                     ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
52813                     ["Z"]
52814                 ]
52815             };
52816         } else {
52817             return {
52818                 path: [
52819                     ["M", x, y],
52820                     ["L", x2, y2],
52821                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
52822                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
52823                     ["L", x, y],
52824                     ["Z"]
52825                 ]
52826             };
52827         }
52828     },
52829
52830     // @private utility function to calculate the middle point of a pie slice.
52831     calcMiddle: function(item) {
52832         var me = this,
52833             rad = me.rad,
52834             slice = item.slice,
52835             x = me.centerX,
52836             y = me.centerY,
52837             startAngle = slice.startAngle,
52838             endAngle = slice.endAngle,
52839             donut = +me.donut,
52840             midAngle = -(startAngle + endAngle) * rad / 2,
52841             r = (item.endRho + item.startRho) / 2,
52842             xm = x + r * Math.cos(midAngle),
52843             ym = y - r * Math.sin(midAngle);
52844
52845         item.middle = {
52846             x: xm,
52847             y: ym
52848         };
52849     },
52850
52851     /**
52852      * Draws the series for the current chart.
52853      */
52854     drawSeries: function() {
52855         var me = this,
52856             store = me.chart.getChartStore(),
52857             group = me.group,
52858             animate = me.chart.animate,
52859             field = me.angleField || me.field || me.xField,
52860             lenField = [].concat(me.lengthField),
52861             totalLenField = 0,
52862             colors = me.colorSet,
52863             chart = me.chart,
52864             surface = chart.surface,
52865             chartBBox = chart.chartBBox,
52866             enableShadows = chart.shadow,
52867             shadowGroups = me.shadowGroups,
52868             shadowAttributes = me.shadowAttributes,
52869             lnsh = shadowGroups.length,
52870             rad = me.rad,
52871             layers = lenField.length,
52872             rhoAcum = 0,
52873             donut = +me.donut,
52874             layerTotals = [],
52875             values = {},
52876             fieldLength,
52877             items = [],
52878             passed = false,
52879             totalField = 0,
52880             maxLenField = 0,
52881             cut = 9,
52882             defcut = true,
52883             angle = 0,
52884             seriesStyle = me.seriesStyle,
52885             seriesLabelStyle = me.seriesLabelStyle,
52886             colorArrayStyle = me.colorArrayStyle,
52887             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
52888             gutterX = chart.maxGutter[0],
52889             gutterY = chart.maxGutter[1],
52890             abs = Math.abs,
52891             rendererAttributes,
52892             shadowGroup,
52893             shadowAttr,
52894             shadows,
52895             shadow,
52896             shindex,
52897             centerX,
52898             centerY,
52899             deltaRho,
52900             first = 0,
52901             slice,
52902             slices,
52903             sprite,
52904             value,
52905             item,
52906             lenValue,
52907             ln,
52908             record,
52909             i,
52910             j,
52911             startAngle,
52912             endAngle,
52913             middleAngle,
52914             sliceLength,
52915             path,
52916             p,
52917             spriteOptions, bbox;
52918
52919         Ext.apply(seriesStyle, me.style || {});
52920
52921         me.setBBox();
52922         bbox = me.bbox;
52923
52924         //override theme colors
52925         if (me.colorSet) {
52926             colorArrayStyle = me.colorSet;
52927             colorArrayLength = colorArrayStyle.length;
52928         }
52929
52930         //if not store or store is empty then there's nothing to draw
52931         if (!store || !store.getCount()) {
52932             return;
52933         }
52934
52935         me.unHighlightItem();
52936         me.cleanHighlights();
52937
52938         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
52939         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
52940         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
52941         me.slices = slices = [];
52942         me.items = items = [];
52943
52944         store.each(function(record, i) {
52945             if (this.__excludes && this.__excludes[i]) {
52946                 //hidden series
52947                 return;
52948             }
52949             totalField += +record.get(field);
52950             if (lenField[0]) {
52951                 for (j = 0, totalLenField = 0; j < layers; j++) {
52952                     totalLenField += +record.get(lenField[j]);
52953                 }
52954                 layerTotals[i] = totalLenField;
52955                 maxLenField = Math.max(maxLenField, totalLenField);
52956             }
52957         }, this);
52958
52959         totalField = totalField || 1;
52960         store.each(function(record, i) {
52961             if (this.__excludes && this.__excludes[i]) {
52962                 value = 0;
52963             } else {
52964                 value = record.get(field);
52965                 if (first == 0) {
52966                     first = 1;
52967                 }
52968             }
52969
52970             // First slice
52971             if (first == 1) {
52972                 first = 2;
52973                 me.firstAngle = angle = 360 * value / totalField / 2;
52974                 for (j = 0; j < i; j++) {
52975                     slices[j].startAngle = slices[j].endAngle = me.firstAngle;
52976                 }
52977             }
52978             
52979             endAngle = angle - 360 * value / totalField;
52980             slice = {
52981                 series: me,
52982                 value: value,
52983                 startAngle: angle,
52984                 endAngle: endAngle,
52985                 storeItem: record
52986             };
52987             if (lenField[0]) {
52988                 lenValue = layerTotals[i];
52989                 slice.rho = me.radius * (lenValue / maxLenField);
52990             } else {
52991                 slice.rho = me.radius;
52992             }
52993             slices[i] = slice;
52994             angle = endAngle;
52995         }, me);
52996         //do all shadows first.
52997         if (enableShadows) {
52998             for (i = 0, ln = slices.length; i < ln; i++) {
52999                 slice = slices[i];
53000                 slice.shadowAttrs = [];
53001                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
53002                     sprite = group.getAt(i * layers + j);
53003                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
53004                     //set pie slice properties
53005                     rendererAttributes = {
53006                         segment: {
53007                             startAngle: slice.startAngle,
53008                             endAngle: slice.endAngle,
53009                             margin: 0,
53010                             rho: slice.rho,
53011                             startRho: rhoAcum + (deltaRho * donut / 100),
53012                             endRho: rhoAcum + deltaRho
53013                         },
53014                         hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
53015                     };
53016                     //create shadows
53017                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
53018                         shadowAttr = shadowAttributes[shindex];
53019                         shadow = shadowGroups[shindex].getAt(i);
53020                         if (!shadow) {
53021                             shadow = chart.surface.add(Ext.apply({}, {
53022                                 type: 'path',
53023                                 group: shadowGroups[shindex],
53024                                 strokeLinejoin: "round"
53025                             }, rendererAttributes, shadowAttr));
53026                         }
53027                         if (animate) {
53028                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
53029                             me.onAnimate(shadow, {
53030                                 to: shadowAttr
53031                             });
53032                         } else {
53033                             shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
53034                             shadow.setAttributes(shadowAttr, true);
53035                         }
53036                         shadows.push(shadow);
53037                     }
53038                     slice.shadowAttrs[j] = shadows;
53039                 }
53040             }
53041         }
53042         //do pie slices after.
53043         for (i = 0, ln = slices.length; i < ln; i++) {
53044             slice = slices[i];
53045             for (j = 0, rhoAcum = 0; j < layers; j++) {
53046                 sprite = group.getAt(i * layers + j);
53047                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
53048                 //set pie slice properties
53049                 rendererAttributes = Ext.apply({
53050                     segment: {
53051                         startAngle: slice.startAngle,
53052                         endAngle: slice.endAngle,
53053                         margin: 0,
53054                         rho: slice.rho,
53055                         startRho: rhoAcum + (deltaRho * donut / 100),
53056                         endRho: rhoAcum + deltaRho
53057                     },
53058                     hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
53059                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
53060                 item = Ext.apply({},
53061                 rendererAttributes.segment, {
53062                     slice: slice,
53063                     series: me,
53064                     storeItem: slice.storeItem,
53065                     index: i
53066                 });
53067                 me.calcMiddle(item);
53068                 if (enableShadows) {
53069                     item.shadows = slice.shadowAttrs[j];
53070                 }
53071                 items[i] = item;
53072                 // Create a new sprite if needed (no height)
53073                 if (!sprite) {
53074                     spriteOptions = Ext.apply({
53075                         type: "path",
53076                         group: group,
53077                         middle: item.middle
53078                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
53079                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
53080                 }
53081                 slice.sprite = slice.sprite || [];
53082                 item.sprite = sprite;
53083                 slice.sprite.push(sprite);
53084                 slice.point = [item.middle.x, item.middle.y];
53085                 if (animate) {
53086                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
53087                     sprite._to = rendererAttributes;
53088                     sprite._animating = true;
53089                     me.onAnimate(sprite, {
53090                         to: rendererAttributes,
53091                         listeners: {
53092                             afteranimate: {
53093                                 fn: function() {
53094                                     this._animating = false;
53095                                 },
53096                                 scope: sprite
53097                             }
53098                         }
53099                     });
53100                 } else {
53101                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
53102                         hidden: false
53103                     }), i, store);
53104                     sprite.setAttributes(rendererAttributes, true);
53105                 }
53106                 rhoAcum += deltaRho;
53107             }
53108         }
53109
53110         // Hide unused bars
53111         ln = group.getCount();
53112         for (i = 0; i < ln; i++) {
53113             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
53114                 group.getAt(i).hide(true);
53115             }
53116         }
53117         if (enableShadows) {
53118             lnsh = shadowGroups.length;
53119             for (shindex = 0; shindex < ln; shindex++) {
53120                 if (!slices[(shindex / layers) >> 0]) {
53121                     for (j = 0; j < lnsh; j++) {
53122                         if (shadowGroups[j].getAt(shindex)) {
53123                             shadowGroups[j].getAt(shindex).hide(true);
53124                         }
53125                     }
53126                 }
53127             }
53128         }
53129         me.renderLabels();
53130         me.renderCallouts();
53131     },
53132
53133     // @private callback for when creating a label sprite.
53134     onCreateLabel: function(storeItem, item, i, display) {
53135         var me = this,
53136             group = me.labelsGroup,
53137             config = me.label,
53138             centerX = me.centerX,
53139             centerY = me.centerY,
53140             middle = item.middle,
53141             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
53142
53143         return me.chart.surface.add(Ext.apply({
53144             'type': 'text',
53145             'text-anchor': 'middle',
53146             'group': group,
53147             'x': middle.x,
53148             'y': middle.y
53149         }, endLabelStyle));
53150     },
53151
53152     // @private callback for when placing a label sprite.
53153     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
53154         var me = this,
53155             chart = me.chart,
53156             resizing = chart.resizing,
53157             config = me.label,
53158             format = config.renderer,
53159             field = [].concat(config.field),
53160             centerX = me.centerX,
53161             centerY = me.centerY,
53162             middle = item.middle,
53163             opt = {
53164                 x: middle.x,
53165                 y: middle.y
53166             },
53167             x = middle.x - centerX,
53168             y = middle.y - centerY,
53169             from = {},
53170             rho = 1,
53171             theta = Math.atan2(y, x || 1),
53172             dg = theta * 180 / Math.PI,
53173             prevDg;
53174         if (this.__excludes && this.__excludes[i]) {
53175             opt.hidden = true;
53176         }
53177         function fixAngle(a) {
53178             if (a < 0) {
53179                 a += 360;
53180             }
53181             return a % 360;
53182         }
53183
53184         label.setAttributes({
53185             text: format(storeItem.get(field[index]))
53186         }, true);
53187
53188         switch (display) {
53189         case 'outside':
53190             rho = Math.sqrt(x * x + y * y) * 2;
53191             //update positions
53192             opt.x = rho * Math.cos(theta) + centerX;
53193             opt.y = rho * Math.sin(theta) + centerY;
53194             break;
53195
53196         case 'rotate':
53197             dg = fixAngle(dg);
53198             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
53199
53200             prevDg = label.attr.rotation.degrees;
53201             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
53202                 if (dg > prevDg) {
53203                     dg -= 360;
53204                 } else {
53205                     dg += 360;
53206                 }
53207                 dg = dg % 360;
53208             } else {
53209                 dg = fixAngle(dg);
53210             }
53211             //update rotation angle
53212             opt.rotate = {
53213                 degrees: dg,
53214                 x: opt.x,
53215                 y: opt.y
53216             };
53217             break;
53218
53219         default:
53220             break;
53221         }
53222         //ensure the object has zero translation
53223         opt.translate = {
53224             x: 0, y: 0
53225         };
53226         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
53227             me.onAnimate(label, {
53228                 to: opt
53229             });
53230         } else {
53231             label.setAttributes(opt, true);
53232         }
53233         label._from = from;
53234     },
53235
53236     // @private callback for when placing a callout sprite.
53237     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
53238         var me = this,
53239             chart = me.chart,
53240             resizing = chart.resizing,
53241             config = me.callouts,
53242             centerX = me.centerX,
53243             centerY = me.centerY,
53244             middle = item.middle,
53245             opt = {
53246                 x: middle.x,
53247                 y: middle.y
53248             },
53249             x = middle.x - centerX,
53250             y = middle.y - centerY,
53251             rho = 1,
53252             rhoCenter,
53253             theta = Math.atan2(y, x || 1),
53254             bbox = callout.label.getBBox(),
53255             offsetFromViz = 20,
53256             offsetToSide = 10,
53257             offsetBox = 10,
53258             p;
53259
53260         //should be able to config this.
53261         rho = item.endRho + offsetFromViz;
53262         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
53263         //update positions
53264         opt.x = rho * Math.cos(theta) + centerX;
53265         opt.y = rho * Math.sin(theta) + centerY;
53266
53267         x = rhoCenter * Math.cos(theta);
53268         y = rhoCenter * Math.sin(theta);
53269
53270         if (chart.animate) {
53271             //set the line from the middle of the pie to the box.
53272             me.onAnimate(callout.lines, {
53273                 to: {
53274                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
53275                 }
53276             });
53277             //set box position
53278             me.onAnimate(callout.box, {
53279                 to: {
53280                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
53281                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
53282                     width: bbox.width + 2 * offsetBox,
53283                     height: bbox.height + 2 * offsetBox
53284                 }
53285             });
53286             //set text position
53287             me.onAnimate(callout.label, {
53288                 to: {
53289                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
53290                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
53291                 }
53292             });
53293         } else {
53294             //set the line from the middle of the pie to the box.
53295             callout.lines.setAttributes({
53296                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
53297             },
53298             true);
53299             //set box position
53300             callout.box.setAttributes({
53301                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
53302                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
53303                 width: bbox.width + 2 * offsetBox,
53304                 height: bbox.height + 2 * offsetBox
53305             },
53306             true);
53307             //set text position
53308             callout.label.setAttributes({
53309                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
53310                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
53311             },
53312             true);
53313         }
53314         for (p in callout) {
53315             callout[p].show(true);
53316         }
53317     },
53318
53319     // @private handles sprite animation for the series.
53320     onAnimate: function(sprite, attr) {
53321         sprite.show();
53322         return this.callParent(arguments);
53323     },
53324
53325     isItemInPoint: function(x, y, item, i) {
53326         var me = this,
53327             cx = me.centerX,
53328             cy = me.centerY,
53329             abs = Math.abs,
53330             dx = abs(x - cx),
53331             dy = abs(y - cy),
53332             startAngle = item.startAngle,
53333             endAngle = item.endAngle,
53334             rho = Math.sqrt(dx * dx + dy * dy),
53335             angle = Math.atan2(y - cy, x - cx) / me.rad;
53336
53337         // normalize to the same range of angles created by drawSeries
53338         if (angle > me.firstAngle) {
53339             angle -= 360;
53340         }
53341         return (angle <= startAngle && angle > endAngle
53342                 && rho >= item.startRho && rho <= item.endRho);
53343     },
53344
53345     // @private hides all elements in the series.
53346     hideAll: function() {
53347         var i, l, shadow, shadows, sh, lsh, sprite;
53348         if (!isNaN(this._index)) {
53349             this.__excludes = this.__excludes || [];
53350             this.__excludes[this._index] = true;
53351             sprite = this.slices[this._index].sprite;
53352             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
53353                 sprite[sh].setAttributes({
53354                     hidden: true
53355                 }, true);
53356             }
53357             if (this.slices[this._index].shadowAttrs) {
53358                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
53359                     shadow = shadows[i];
53360                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
53361                         shadow[sh].setAttributes({
53362                             hidden: true
53363                         }, true);
53364                     }
53365                 }
53366             }
53367             this.drawSeries();
53368         }
53369     },
53370
53371     // @private shows all elements in the series.
53372     showAll: function() {
53373         if (!isNaN(this._index)) {
53374             this.__excludes[this._index] = false;
53375             this.drawSeries();
53376         }
53377     },
53378
53379     /**
53380      * Highlight the specified item. If no item is provided the whole series will be highlighted.
53381      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
53382      */
53383     highlightItem: function(item) {
53384         var me = this,
53385             rad = me.rad;
53386         item = item || this.items[this._index];
53387
53388         //TODO(nico): sometimes in IE itemmouseover is triggered
53389         //twice without triggering itemmouseout in between. This
53390         //fixes the highlighting bug. Eventually, events should be
53391         //changed to trigger one itemmouseout between two itemmouseovers.
53392         this.unHighlightItem();
53393
53394         if (!item || item.sprite && item.sprite._animating) {
53395             return;
53396         }
53397         me.callParent([item]);
53398         if (!me.highlight) {
53399             return;
53400         }
53401         if ('segment' in me.highlightCfg) {
53402             var highlightSegment = me.highlightCfg.segment,
53403                 animate = me.chart.animate,
53404                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
53405             //animate labels
53406             if (me.labelsGroup) {
53407                 var group = me.labelsGroup,
53408                     display = me.label.display,
53409                     label = group.getAt(item.index),
53410                     middle = (item.startAngle + item.endAngle) / 2 * rad,
53411                     r = highlightSegment.margin || 0,
53412                     x = r * Math.cos(middle),
53413                     y = r * Math.sin(middle);
53414
53415                 //TODO(nico): rounding to 1e-10
53416                 //gives the right translation. Translation
53417                 //was buggy for very small numbers. In this
53418                 //case we're not looking to translate to very small
53419                 //numbers but not to translate at all.
53420                 if (Math.abs(x) < 1e-10) {
53421                     x = 0;
53422                 }
53423                 if (Math.abs(y) < 1e-10) {
53424                     y = 0;
53425                 }
53426
53427                 if (animate) {
53428                     label.stopAnimation();
53429                     label.animate({
53430                         to: {
53431                             translate: {
53432                                 x: x,
53433                                 y: y
53434                             }
53435                         },
53436                         duration: me.highlightDuration
53437                     });
53438                 }
53439                 else {
53440                     label.setAttributes({
53441                         translate: {
53442                             x: x,
53443                             y: y
53444                         }
53445                     }, true);
53446                 }
53447             }
53448             //animate shadows
53449             if (me.chart.shadow && item.shadows) {
53450                 i = 0;
53451                 shadows = item.shadows;
53452                 ln = shadows.length;
53453                 for (; i < ln; i++) {
53454                     shadow = shadows[i];
53455                     to = {};
53456                     itemHighlightSegment = item.sprite._from.segment;
53457                     for (prop in itemHighlightSegment) {
53458                         if (! (prop in highlightSegment)) {
53459                             to[prop] = itemHighlightSegment[prop];
53460                         }
53461                     }
53462                     attrs = {
53463                         segment: Ext.applyIf(to, me.highlightCfg.segment)
53464                     };
53465                     if (animate) {
53466                         shadow.stopAnimation();
53467                         shadow.animate({
53468                             to: attrs,
53469                             duration: me.highlightDuration
53470                         });
53471                     }
53472                     else {
53473                         shadow.setAttributes(attrs, true);
53474                     }
53475                 }
53476             }
53477         }
53478     },
53479
53480     /**
53481      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
53482      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
53483      */
53484     unHighlightItem: function() {
53485         var me = this;
53486         if (!me.highlight) {
53487             return;
53488         }
53489
53490         if (('segment' in me.highlightCfg) && me.items) {
53491             var items = me.items,
53492                 animate = me.chart.animate,
53493                 shadowsEnabled = !!me.chart.shadow,
53494                 group = me.labelsGroup,
53495                 len = items.length,
53496                 i = 0,
53497                 j = 0,
53498                 display = me.label.display,
53499                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
53500
53501             for (; i < len; i++) {
53502                 item = items[i];
53503                 if (!item) {
53504                     continue;
53505                 }
53506                 sprite = item.sprite;
53507                 if (sprite && sprite._highlighted) {
53508                     //animate labels
53509                     if (group) {
53510                         label = group.getAt(item.index);
53511                         attrs = Ext.apply({
53512                             translate: {
53513                                 x: 0,
53514                                 y: 0
53515                             }
53516                         },
53517                         display == 'rotate' ? {
53518                             rotate: {
53519                                 x: label.attr.x,
53520                                 y: label.attr.y,
53521                                 degrees: label.attr.rotation.degrees
53522                             }
53523                         }: {});
53524                         if (animate) {
53525                             label.stopAnimation();
53526                             label.animate({
53527                                 to: attrs,
53528                                 duration: me.highlightDuration
53529                             });
53530                         }
53531                         else {
53532                             label.setAttributes(attrs, true);
53533                         }
53534                     }
53535                     if (shadowsEnabled) {
53536                         shadows = item.shadows;
53537                         shadowLen = shadows.length;
53538                         for (; j < shadowLen; j++) {
53539                             to = {};
53540                             ihs = item.sprite._to.segment;
53541                             hs = item.sprite._from.segment;
53542                             Ext.apply(to, hs);
53543                             for (p in ihs) {
53544                                 if (! (p in hs)) {
53545                                     to[p] = ihs[p];
53546                                 }
53547                             }
53548                             shadow = shadows[j];
53549                             if (animate) {
53550                                 shadow.stopAnimation();
53551                                 shadow.animate({
53552                                     to: {
53553                                         segment: to
53554                                     },
53555                                     duration: me.highlightDuration
53556                                 });
53557                             }
53558                             else {
53559                                 shadow.setAttributes({ segment: to }, true);
53560                             }
53561                         }
53562                     }
53563                 }
53564             }
53565         }
53566         me.callParent(arguments);
53567     },
53568
53569     /**
53570      * Returns the color of the series (to be displayed as color for the series legend item).
53571      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
53572      */
53573     getLegendColor: function(index) {
53574         var me = this;
53575         return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
53576     }
53577 });
53578
53579
53580 /**
53581  * @class Ext.chart.series.Radar
53582  * @extends Ext.chart.series.Series
53583  *
53584  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
53585  * a constrained number of categories.
53586  *
53587  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
53588  * documentation for more information. A typical configuration object for the radar series could be:
53589  *
53590  *     @example
53591  *     var store = Ext.create('Ext.data.JsonStore', {
53592  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
53593  *         data: [
53594  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
53595  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
53596  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
53597  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
53598  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
53599  *         ]
53600  *     });
53601  *
53602  *     Ext.create('Ext.chart.Chart', {
53603  *         renderTo: Ext.getBody(),
53604  *         width: 500,
53605  *         height: 300,
53606  *         animate: true,
53607  *         theme:'Category2',
53608  *         store: store,
53609  *         axes: [{
53610  *             type: 'Radial',
53611  *             position: 'radial',
53612  *             label: {
53613  *                 display: true
53614  *             }
53615  *         }],
53616  *         series: [{
53617  *             type: 'radar',
53618  *             xField: 'name',
53619  *             yField: 'data3',
53620  *             showInLegend: true,
53621  *             showMarkers: true,
53622  *             markerConfig: {
53623  *                 radius: 5,
53624  *                 size: 5
53625  *             },
53626  *             style: {
53627  *                 'stroke-width': 2,
53628  *                 fill: 'none'
53629  *             }
53630  *         },{
53631  *             type: 'radar',
53632  *             xField: 'name',
53633  *             yField: 'data2',
53634  *             showMarkers: true,
53635  *             showInLegend: true,
53636  *             markerConfig: {
53637  *                 radius: 5,
53638  *                 size: 5
53639  *             },
53640  *             style: {
53641  *                 'stroke-width': 2,
53642  *                 fill: 'none'
53643  *             }
53644  *         },{
53645  *             type: 'radar',
53646  *             xField: 'name',
53647  *             yField: 'data5',
53648  *             showMarkers: true,
53649  *             showInLegend: true,
53650  *             markerConfig: {
53651  *                 radius: 5,
53652  *                 size: 5
53653  *             },
53654  *             style: {
53655  *                 'stroke-width': 2,
53656  *                 fill: 'none'
53657  *             }
53658  *         }]
53659  *     });
53660  *
53661  * In this configuration we add three series to the chart. Each of these series is bound to the same
53662  * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
53663  * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
53664  * for the markers of each series can be set by adding properties onto the markerConfig object.
53665  * Finally we override some theme styling properties by adding properties to the `style` object.
53666  *
53667  * @xtype radar
53668  */
53669 Ext.define('Ext.chart.series.Radar', {
53670
53671     /* Begin Definitions */
53672
53673     extend: 'Ext.chart.series.Series',
53674
53675     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
53676
53677     /* End Definitions */
53678
53679     type: "radar",
53680     alias: 'series.radar',
53681
53682
53683     rad: Math.PI / 180,
53684
53685     showInLegend: false,
53686
53687     /**
53688      * @cfg {Object} style
53689      * An object containing styles for overriding series styles from Theming.
53690      */
53691     style: {},
53692
53693     constructor: function(config) {
53694         this.callParent(arguments);
53695         var me = this,
53696             surface = me.chart.surface, i, l;
53697         me.group = surface.getGroup(me.seriesId);
53698         if (me.showMarkers) {
53699             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
53700         }
53701     },
53702
53703     /**
53704      * Draws the series for the current chart.
53705      */
53706     drawSeries: function() {
53707         var me = this,
53708             store = me.chart.getChartStore(),
53709             group = me.group,
53710             sprite,
53711             chart = me.chart,
53712             animate = chart.animate,
53713             field = me.field || me.yField,
53714             surface = chart.surface,
53715             chartBBox = chart.chartBBox,
53716             rendererAttributes,
53717             centerX, centerY,
53718             items,
53719             radius,
53720             maxValue = 0,
53721             fields = [],
53722             max = Math.max,
53723             cos = Math.cos,
53724             sin = Math.sin,
53725             pi2 = Math.PI * 2,
53726             l = store.getCount(),
53727             startPath, path, x, y, rho,
53728             i, nfields,
53729             seriesStyle = me.seriesStyle,
53730             seriesLabelStyle = me.seriesLabelStyle,
53731             first = chart.resizing || !me.radar,
53732             axis = chart.axes && chart.axes.get(0),
53733             aggregate = !(axis && axis.maximum);
53734
53735         me.setBBox();
53736
53737         maxValue = aggregate? 0 : (axis.maximum || 0);
53738
53739         Ext.apply(seriesStyle, me.style || {});
53740
53741         //if the store is empty then there's nothing to draw
53742         if (!store || !store.getCount()) {
53743             return;
53744         }
53745
53746         me.unHighlightItem();
53747         me.cleanHighlights();
53748
53749         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
53750         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
53751         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
53752         me.items = items = [];
53753
53754         if (aggregate) {
53755             //get all renderer fields
53756             chart.series.each(function(series) {
53757                 fields.push(series.yField);
53758             });
53759             //get maxValue to interpolate
53760             store.each(function(record, i) {
53761                 for (i = 0, nfields = fields.length; i < nfields; i++) {
53762                     maxValue = max(+record.get(fields[i]), maxValue);
53763                 }
53764             });
53765         }
53766         //ensure non-zero value.
53767         maxValue = maxValue || 1;
53768         //create path and items
53769         startPath = []; path = [];
53770         store.each(function(record, i) {
53771             rho = radius * record.get(field) / maxValue;
53772             x = rho * cos(i / l * pi2);
53773             y = rho * sin(i / l * pi2);
53774             if (i == 0) {
53775                 path.push('M', x + centerX, y + centerY);
53776                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
53777             } else {
53778                 path.push('L', x + centerX, y + centerY);
53779                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
53780             }
53781             items.push({
53782                 sprite: false, //TODO(nico): add markers
53783                 point: [centerX + x, centerY + y],
53784                 series: me
53785             });
53786         });
53787         path.push('Z');
53788         //create path sprite
53789         if (!me.radar) {
53790             me.radar = surface.add(Ext.apply({
53791                 type: 'path',
53792                 group: group,
53793                 path: startPath
53794             }, seriesStyle || {}));
53795         }
53796         //reset on resizing
53797         if (chart.resizing) {
53798             me.radar.setAttributes({
53799                 path: startPath
53800             }, true);
53801         }
53802         //render/animate
53803         if (chart.animate) {
53804             me.onAnimate(me.radar, {
53805                 to: Ext.apply({
53806                     path: path
53807                 }, seriesStyle || {})
53808             });
53809         } else {
53810             me.radar.setAttributes(Ext.apply({
53811                 path: path
53812             }, seriesStyle || {}), true);
53813         }
53814         //render markers, labels and callouts
53815         if (me.showMarkers) {
53816             me.drawMarkers();
53817         }
53818         me.renderLabels();
53819         me.renderCallouts();
53820     },
53821
53822     // @private draws the markers for the lines (if any).
53823     drawMarkers: function() {
53824         var me = this,
53825             chart = me.chart,
53826             surface = chart.surface,
53827             markerStyle = Ext.apply({}, me.markerStyle || {}),
53828             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
53829             items = me.items,
53830             type = endMarkerStyle.type,
53831             markerGroup = me.markerGroup,
53832             centerX = me.centerX,
53833             centerY = me.centerY,
53834             item, i, l, marker;
53835
53836         delete endMarkerStyle.type;
53837
53838         for (i = 0, l = items.length; i < l; i++) {
53839             item = items[i];
53840             marker = markerGroup.getAt(i);
53841             if (!marker) {
53842                 marker = Ext.chart.Shape[type](surface, Ext.apply({
53843                     group: markerGroup,
53844                     x: 0,
53845                     y: 0,
53846                     translate: {
53847                         x: centerX,
53848                         y: centerY
53849                     }
53850                 }, endMarkerStyle));
53851             }
53852             else {
53853                 marker.show();
53854             }
53855             if (chart.resizing) {
53856                 marker.setAttributes({
53857                     x: 0,
53858                     y: 0,
53859                     translate: {
53860                         x: centerX,
53861                         y: centerY
53862                     }
53863                 }, true);
53864             }
53865             marker._to = {
53866                 translate: {
53867                     x: item.point[0],
53868                     y: item.point[1]
53869                 }
53870             };
53871             //render/animate
53872             if (chart.animate) {
53873                 me.onAnimate(marker, {
53874                     to: marker._to
53875                 });
53876             }
53877             else {
53878                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
53879             }
53880         }
53881     },
53882
53883     isItemInPoint: function(x, y, item) {
53884         var point,
53885             tolerance = 10,
53886             abs = Math.abs;
53887         point = item.point;
53888         return (abs(point[0] - x) <= tolerance &&
53889                 abs(point[1] - y) <= tolerance);
53890     },
53891
53892     // @private callback for when creating a label sprite.
53893     onCreateLabel: function(storeItem, item, i, display) {
53894         var me = this,
53895             group = me.labelsGroup,
53896             config = me.label,
53897             centerX = me.centerX,
53898             centerY = me.centerY,
53899             point = item.point,
53900             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
53901
53902         return me.chart.surface.add(Ext.apply({
53903             'type': 'text',
53904             'text-anchor': 'middle',
53905             'group': group,
53906             'x': centerX,
53907             'y': centerY
53908         }, config || {}));
53909     },
53910
53911     // @private callback for when placing a label sprite.
53912     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
53913         var me = this,
53914             chart = me.chart,
53915             resizing = chart.resizing,
53916             config = me.label,
53917             format = config.renderer,
53918             field = config.field,
53919             centerX = me.centerX,
53920             centerY = me.centerY,
53921             opt = {
53922                 x: item.point[0],
53923                 y: item.point[1]
53924             },
53925             x = opt.x - centerX,
53926             y = opt.y - centerY;
53927
53928         label.setAttributes({
53929             text: format(storeItem.get(field)),
53930             hidden: true
53931         },
53932         true);
53933
53934         if (resizing) {
53935             label.setAttributes({
53936                 x: centerX,
53937                 y: centerY
53938             }, true);
53939         }
53940
53941         if (animate) {
53942             label.show(true);
53943             me.onAnimate(label, {
53944                 to: opt
53945             });
53946         } else {
53947             label.setAttributes(opt, true);
53948             label.show(true);
53949         }
53950     },
53951
53952     // @private for toggling (show/hide) series.
53953     toggleAll: function(show) {
53954         var me = this,
53955             i, ln, shadow, shadows;
53956         if (!show) {
53957             Ext.chart.series.Radar.superclass.hideAll.call(me);
53958         }
53959         else {
53960             Ext.chart.series.Radar.superclass.showAll.call(me);
53961         }
53962         if (me.radar) {
53963             me.radar.setAttributes({
53964                 hidden: !show
53965             }, true);
53966             //hide shadows too
53967             if (me.radar.shadows) {
53968                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
53969                     shadow = shadows[i];
53970                     shadow.setAttributes({
53971                         hidden: !show
53972                     }, true);
53973                 }
53974             }
53975         }
53976     },
53977
53978     // @private hide all elements in the series.
53979     hideAll: function() {
53980         this.toggleAll(false);
53981         this.hideMarkers(0);
53982     },
53983
53984     // @private show all elements in the series.
53985     showAll: function() {
53986         this.toggleAll(true);
53987     },
53988
53989     // @private hide all markers that belong to `markerGroup`
53990     hideMarkers: function(index) {
53991         var me = this,
53992             count = me.markerGroup && me.markerGroup.getCount() || 0,
53993             i = index || 0;
53994         for (; i < count; i++) {
53995             me.markerGroup.getAt(i).hide(true);
53996         }
53997     }
53998 });
53999
54000
54001 /**
54002  * @class Ext.chart.series.Scatter
54003  * @extends Ext.chart.series.Cartesian
54004  *
54005  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
54006  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
54007  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
54008  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
54009  *
54010  *     @example
54011  *     var store = Ext.create('Ext.data.JsonStore', {
54012  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
54013  *         data: [
54014  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
54015  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
54016  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
54017  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
54018  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
54019  *         ]
54020  *     });
54021  *
54022  *     Ext.create('Ext.chart.Chart', {
54023  *         renderTo: Ext.getBody(),
54024  *         width: 500,
54025  *         height: 300,
54026  *         animate: true,
54027  *         theme:'Category2',
54028  *         store: store,
54029  *         axes: [{
54030  *             type: 'Numeric',
54031  *             position: 'left',
54032  *             fields: ['data2', 'data3'],
54033  *             title: 'Sample Values',
54034  *             grid: true,
54035  *             minimum: 0
54036  *         }, {
54037  *             type: 'Category',
54038  *             position: 'bottom',
54039  *             fields: ['name'],
54040  *             title: 'Sample Metrics'
54041  *         }],
54042  *         series: [{
54043  *             type: 'scatter',
54044  *             markerConfig: {
54045  *                 radius: 5,
54046  *                 size: 5
54047  *             },
54048  *             axis: 'left',
54049  *             xField: 'name',
54050  *             yField: 'data2'
54051  *         }, {
54052  *             type: 'scatter',
54053  *             markerConfig: {
54054  *                 radius: 5,
54055  *                 size: 5
54056  *             },
54057  *             axis: 'left',
54058  *             xField: 'name',
54059  *             yField: 'data3'
54060  *         }]
54061  *     });
54062  *
54063  * 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,
54064  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
54065  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
54066  * axis to show the current values of the elements.
54067  *
54068  * @xtype scatter
54069  */
54070 Ext.define('Ext.chart.series.Scatter', {
54071
54072     /* Begin Definitions */
54073
54074     extend: 'Ext.chart.series.Cartesian',
54075
54076     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
54077
54078     /* End Definitions */
54079
54080     type: 'scatter',
54081     alias: 'series.scatter',
54082
54083     /**
54084      * @cfg {Object} markerConfig
54085      * The display style for the scatter series markers.
54086      */
54087
54088     /**
54089      * @cfg {Object} style
54090      * Append styling properties to this object for it to override theme properties.
54091      */
54092     
54093     /**
54094      * @cfg {String/Array} axis
54095      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
54096      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
54097      * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
54098      */
54099
54100     constructor: function(config) {
54101         this.callParent(arguments);
54102         var me = this,
54103             shadow = me.chart.shadow,
54104             surface = me.chart.surface, i, l;
54105         Ext.apply(me, config, {
54106             style: {},
54107             markerConfig: {},
54108             shadowAttributes: [{
54109                 "stroke-width": 6,
54110                 "stroke-opacity": 0.05,
54111                 stroke: 'rgb(0, 0, 0)'
54112             }, {
54113                 "stroke-width": 4,
54114                 "stroke-opacity": 0.1,
54115                 stroke: 'rgb(0, 0, 0)'
54116             }, {
54117                 "stroke-width": 2,
54118                 "stroke-opacity": 0.15,
54119                 stroke: 'rgb(0, 0, 0)'
54120             }]
54121         });
54122         me.group = surface.getGroup(me.seriesId);
54123         if (shadow) {
54124             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
54125                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
54126             }
54127         }
54128     },
54129
54130     // @private Get chart and data boundaries
54131     getBounds: function() {
54132         var me = this,
54133             chart = me.chart,
54134             store = chart.getChartStore(),
54135             axes = [].concat(me.axis),
54136             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
54137
54138         me.setBBox();
54139         bbox = me.bbox;
54140
54141         for (i = 0, ln = axes.length; i < ln; i++) {
54142             axis = chart.axes.get(axes[i]);
54143             if (axis) {
54144                 ends = axis.calcEnds();
54145                 if (axis.position == 'top' || axis.position == 'bottom') {
54146                     minX = ends.from;
54147                     maxX = ends.to;
54148                 }
54149                 else {
54150                     minY = ends.from;
54151                     maxY = ends.to;
54152                 }
54153             }
54154         }
54155         // If a field was specified without a corresponding axis, create one to get bounds
54156         if (me.xField && !Ext.isNumber(minX)) {
54157             axis = Ext.create('Ext.chart.axis.Axis', {
54158                 chart: chart,
54159                 fields: [].concat(me.xField)
54160             }).calcEnds();
54161             minX = axis.from;
54162             maxX = axis.to;
54163         }
54164         if (me.yField && !Ext.isNumber(minY)) {
54165             axis = Ext.create('Ext.chart.axis.Axis', {
54166                 chart: chart,
54167                 fields: [].concat(me.yField)
54168             }).calcEnds();
54169             minY = axis.from;
54170             maxY = axis.to;
54171         }
54172
54173         if (isNaN(minX)) {
54174             minX = 0;
54175             maxX = store.getCount() - 1;
54176             xScale = bbox.width / (store.getCount() - 1);
54177         }
54178         else {
54179             xScale = bbox.width / (maxX - minX);
54180         }
54181
54182         if (isNaN(minY)) {
54183             minY = 0;
54184             maxY = store.getCount() - 1;
54185             yScale = bbox.height / (store.getCount() - 1);
54186         }
54187         else {
54188             yScale = bbox.height / (maxY - minY);
54189         }
54190
54191         return {
54192             bbox: bbox,
54193             minX: minX,
54194             minY: minY,
54195             xScale: xScale,
54196             yScale: yScale
54197         };
54198     },
54199
54200     // @private Build an array of paths for the chart
54201     getPaths: function() {
54202         var me = this,
54203             chart = me.chart,
54204             enableShadows = chart.shadow,
54205             store = chart.getChartStore(),
54206             group = me.group,
54207             bounds = me.bounds = me.getBounds(),
54208             bbox = me.bbox,
54209             xScale = bounds.xScale,
54210             yScale = bounds.yScale,
54211             minX = bounds.minX,
54212             minY = bounds.minY,
54213             boxX = bbox.x,
54214             boxY = bbox.y,
54215             boxHeight = bbox.height,
54216             items = me.items = [],
54217             attrs = [],
54218             x, y, xValue, yValue, sprite;
54219
54220         store.each(function(record, i) {
54221             xValue = record.get(me.xField);
54222             yValue = record.get(me.yField);
54223             //skip undefined values
54224             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
54225                 //<debug warn>
54226                 if (Ext.isDefined(Ext.global.console)) {
54227                     Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with an undefined value at ", record, xValue, yValue);
54228                 }
54229                 //</debug>
54230                 return;
54231             }
54232             // Ensure a value
54233             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
54234                 xValue = i;
54235             }
54236             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
54237                 yValue = i;
54238             }
54239             x = boxX + (xValue - minX) * xScale;
54240             y = boxY + boxHeight - (yValue - minY) * yScale;
54241             attrs.push({
54242                 x: x,
54243                 y: y
54244             });
54245
54246             me.items.push({
54247                 series: me,
54248                 value: [xValue, yValue],
54249                 point: [x, y],
54250                 storeItem: record
54251             });
54252
54253             // When resizing, reset before animating
54254             if (chart.animate && chart.resizing) {
54255                 sprite = group.getAt(i);
54256                 if (sprite) {
54257                     me.resetPoint(sprite);
54258                     if (enableShadows) {
54259                         me.resetShadow(sprite);
54260                     }
54261                 }
54262             }
54263         });
54264         return attrs;
54265     },
54266
54267     // @private translate point to the center
54268     resetPoint: function(sprite) {
54269         var bbox = this.bbox;
54270         sprite.setAttributes({
54271             translate: {
54272                 x: (bbox.x + bbox.width) / 2,
54273                 y: (bbox.y + bbox.height) / 2
54274             }
54275         }, true);
54276     },
54277
54278     // @private translate shadows of a sprite to the center
54279     resetShadow: function(sprite) {
54280         var me = this,
54281             shadows = sprite.shadows,
54282             shadowAttributes = me.shadowAttributes,
54283             ln = me.shadowGroups.length,
54284             bbox = me.bbox,
54285             i, attr;
54286         for (i = 0; i < ln; i++) {
54287             attr = Ext.apply({}, shadowAttributes[i]);
54288             if (attr.translate) {
54289                 attr.translate.x += (bbox.x + bbox.width) / 2;
54290                 attr.translate.y += (bbox.y + bbox.height) / 2;
54291             }
54292             else {
54293                 attr.translate = {
54294                     x: (bbox.x + bbox.width) / 2,
54295                     y: (bbox.y + bbox.height) / 2
54296                 };
54297             }
54298             shadows[i].setAttributes(attr, true);
54299         }
54300     },
54301
54302     // @private create a new point
54303     createPoint: function(attr, type) {
54304         var me = this,
54305             chart = me.chart,
54306             group = me.group,
54307             bbox = me.bbox;
54308
54309         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
54310             x: 0,
54311             y: 0,
54312             group: group,
54313             translate: {
54314                 x: (bbox.x + bbox.width) / 2,
54315                 y: (bbox.y + bbox.height) / 2
54316             }
54317         }, attr));
54318     },
54319
54320     // @private create a new set of shadows for a sprite
54321     createShadow: function(sprite, endMarkerStyle, type) {
54322         var me = this,
54323             chart = me.chart,
54324             shadowGroups = me.shadowGroups,
54325             shadowAttributes = me.shadowAttributes,
54326             lnsh = shadowGroups.length,
54327             bbox = me.bbox,
54328             i, shadow, shadows, attr;
54329
54330         sprite.shadows = shadows = [];
54331
54332         for (i = 0; i < lnsh; i++) {
54333             attr = Ext.apply({}, shadowAttributes[i]);
54334             if (attr.translate) {
54335                 attr.translate.x += (bbox.x + bbox.width) / 2;
54336                 attr.translate.y += (bbox.y + bbox.height) / 2;
54337             }
54338             else {
54339                 Ext.apply(attr, {
54340                     translate: {
54341                         x: (bbox.x + bbox.width) / 2,
54342                         y: (bbox.y + bbox.height) / 2
54343                     }
54344                 });
54345             }
54346             Ext.apply(attr, endMarkerStyle);
54347             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
54348                 x: 0,
54349                 y: 0,
54350                 group: shadowGroups[i]
54351             }, attr));
54352             shadows.push(shadow);
54353         }
54354     },
54355
54356     /**
54357      * Draws the series for the current chart.
54358      */
54359     drawSeries: function() {
54360         var me = this,
54361             chart = me.chart,
54362             store = chart.getChartStore(),
54363             group = me.group,
54364             enableShadows = chart.shadow,
54365             shadowGroups = me.shadowGroups,
54366             shadowAttributes = me.shadowAttributes,
54367             lnsh = shadowGroups.length,
54368             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
54369             rendererAttributes, shadowAttribute;
54370
54371         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
54372         type = endMarkerStyle.type;
54373         delete endMarkerStyle.type;
54374
54375         //if the store is empty then there's nothing to be rendered
54376         if (!store || !store.getCount()) {
54377             return;
54378         }
54379
54380         me.unHighlightItem();
54381         me.cleanHighlights();
54382
54383         attrs = me.getPaths();
54384         ln = attrs.length;
54385         for (i = 0; i < ln; i++) {
54386             attr = attrs[i];
54387             sprite = group.getAt(i);
54388             Ext.apply(attr, endMarkerStyle);
54389
54390             // Create a new sprite if needed (no height)
54391             if (!sprite) {
54392                 sprite = me.createPoint(attr, type);
54393                 if (enableShadows) {
54394                     me.createShadow(sprite, endMarkerStyle, type);
54395                 }
54396             }
54397
54398             shadows = sprite.shadows;
54399             if (chart.animate) {
54400                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
54401                 sprite._to = rendererAttributes;
54402                 me.onAnimate(sprite, {
54403                     to: rendererAttributes
54404                 });
54405                 //animate shadows
54406                 for (shindex = 0; shindex < lnsh; shindex++) {
54407                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
54408                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
54409                         hidden: false,
54410                         translate: {
54411                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
54412                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
54413                         }
54414                     }, shadowAttribute), i, store);
54415                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
54416                 }
54417             }
54418             else {
54419                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
54420                 sprite._to = rendererAttributes;
54421                 sprite.setAttributes(rendererAttributes, true);
54422                 //animate shadows
54423                 for (shindex = 0; shindex < lnsh; shindex++) {
54424                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
54425                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
54426                         hidden: false,
54427                         translate: {
54428                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
54429                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
54430                         } 
54431                     }, shadowAttribute), i, store);
54432                     shadows[shindex].setAttributes(rendererAttributes, true);
54433                 }
54434             }
54435             me.items[i].sprite = sprite;
54436         }
54437
54438         // Hide unused sprites
54439         ln = group.getCount();
54440         for (i = attrs.length; i < ln; i++) {
54441             group.getAt(i).hide(true);
54442         }
54443         me.renderLabels();
54444         me.renderCallouts();
54445     },
54446
54447     // @private callback for when creating a label sprite.
54448     onCreateLabel: function(storeItem, item, i, display) {
54449         var me = this,
54450             group = me.labelsGroup,
54451             config = me.label,
54452             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
54453             bbox = me.bbox;
54454
54455         return me.chart.surface.add(Ext.apply({
54456             type: 'text',
54457             group: group,
54458             x: item.point[0],
54459             y: bbox.y + bbox.height / 2
54460         }, endLabelStyle));
54461     },
54462
54463     // @private callback for when placing a label sprite.
54464     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
54465         var me = this,
54466             chart = me.chart,
54467             resizing = chart.resizing,
54468             config = me.label,
54469             format = config.renderer,
54470             field = config.field,
54471             bbox = me.bbox,
54472             x = item.point[0],
54473             y = item.point[1],
54474             radius = item.sprite.attr.radius,
54475             bb, width, height, anim;
54476
54477         label.setAttributes({
54478             text: format(storeItem.get(field)),
54479             hidden: true
54480         }, true);
54481
54482         if (display == 'rotate') {
54483             label.setAttributes({
54484                 'text-anchor': 'start',
54485                 'rotation': {
54486                     x: x,
54487                     y: y,
54488                     degrees: -45
54489                 }
54490             }, true);
54491             //correct label position to fit into the box
54492             bb = label.getBBox();
54493             width = bb.width;
54494             height = bb.height;
54495             x = x < bbox.x? bbox.x : x;
54496             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
54497             y = (y - height < bbox.y)? bbox.y + height : y;
54498
54499         } else if (display == 'under' || display == 'over') {
54500             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
54501             bb = item.sprite.getBBox();
54502             bb.width = bb.width || (radius * 2);
54503             bb.height = bb.height || (radius * 2);
54504             y = y + (display == 'over'? -bb.height : bb.height);
54505             //correct label position to fit into the box
54506             bb = label.getBBox();
54507             width = bb.width/2;
54508             height = bb.height/2;
54509             x = x - width < bbox.x ? bbox.x + width : x;
54510             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
54511             y = y - height < bbox.y? bbox.y + height : y;
54512             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
54513         }
54514
54515         if (!chart.animate) {
54516             label.setAttributes({
54517                 x: x,
54518                 y: y
54519             }, true);
54520             label.show(true);
54521         }
54522         else {
54523             if (resizing) {
54524                 anim = item.sprite.getActiveAnimation();
54525                 if (anim) {
54526                     anim.on('afteranimate', function() {
54527                         label.setAttributes({
54528                             x: x,
54529                             y: y
54530                         }, true);
54531                         label.show(true);
54532                     });
54533                 }
54534                 else {
54535                     label.show(true);
54536                 }
54537             }
54538             else {
54539                 me.onAnimate(label, {
54540                     to: {
54541                         x: x,
54542                         y: y
54543                     }
54544                 });
54545             }
54546         }
54547     },
54548
54549     // @private callback for when placing a callout sprite.
54550     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
54551         var me = this,
54552             chart = me.chart,
54553             surface = chart.surface,
54554             resizing = chart.resizing,
54555             config = me.callouts,
54556             items = me.items,
54557             cur = item.point,
54558             normal,
54559             bbox = callout.label.getBBox(),
54560             offsetFromViz = 30,
54561             offsetToSide = 10,
54562             offsetBox = 3,
54563             boxx, boxy, boxw, boxh,
54564             p, clipRect = me.bbox,
54565             x, y;
54566
54567         //position
54568         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
54569         x = cur[0] + normal[0] * offsetFromViz;
54570         y = cur[1] + normal[1] * offsetFromViz;
54571
54572         //box position and dimensions
54573         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
54574         boxy = y - bbox.height /2 - offsetBox;
54575         boxw = bbox.width + 2 * offsetBox;
54576         boxh = bbox.height + 2 * offsetBox;
54577
54578         //now check if we're out of bounds and invert the normal vector correspondingly
54579         //this may add new overlaps between labels (but labels won't be out of bounds).
54580         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
54581             normal[0] *= -1;
54582         }
54583         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
54584             normal[1] *= -1;
54585         }
54586
54587         //update positions
54588         x = cur[0] + normal[0] * offsetFromViz;
54589         y = cur[1] + normal[1] * offsetFromViz;
54590
54591         //update box position and dimensions
54592         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
54593         boxy = y - bbox.height /2 - offsetBox;
54594         boxw = bbox.width + 2 * offsetBox;
54595         boxh = bbox.height + 2 * offsetBox;
54596
54597         if (chart.animate) {
54598             //set the line from the middle of the pie to the box.
54599             me.onAnimate(callout.lines, {
54600                 to: {
54601                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
54602                 }
54603             }, true);
54604             //set box position
54605             me.onAnimate(callout.box, {
54606                 to: {
54607                     x: boxx,
54608                     y: boxy,
54609                     width: boxw,
54610                     height: boxh
54611                 }
54612             }, true);
54613             //set text position
54614             me.onAnimate(callout.label, {
54615                 to: {
54616                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
54617                     y: y
54618                 }
54619             }, true);
54620         } else {
54621             //set the line from the middle of the pie to the box.
54622             callout.lines.setAttributes({
54623                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
54624             }, true);
54625             //set box position
54626             callout.box.setAttributes({
54627                 x: boxx,
54628                 y: boxy,
54629                 width: boxw,
54630                 height: boxh
54631             }, true);
54632             //set text position
54633             callout.label.setAttributes({
54634                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
54635                 y: y
54636             }, true);
54637         }
54638         for (p in callout) {
54639             callout[p].show(true);
54640         }
54641     },
54642
54643     // @private handles sprite animation for the series.
54644     onAnimate: function(sprite, attr) {
54645         sprite.show();
54646         return this.callParent(arguments);
54647     },
54648
54649     isItemInPoint: function(x, y, item) {
54650         var point,
54651             tolerance = 10,
54652             abs = Math.abs;
54653
54654         function dist(point) {
54655             var dx = abs(point[0] - x),
54656                 dy = abs(point[1] - y);
54657             return Math.sqrt(dx * dx + dy * dy);
54658         }
54659         point = item.point;
54660         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
54661             point[1] - tolerance <= y && point[1] + tolerance >= y);
54662     }
54663 });
54664
54665
54666 /**
54667  * @class Ext.chart.theme.Base
54668  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
54669  * @ignore
54670  */
54671 Ext.define('Ext.chart.theme.Base', {
54672
54673     /* Begin Definitions */
54674
54675     requires: ['Ext.chart.theme.Theme'],
54676
54677     /* End Definitions */
54678
54679     constructor: function(config) {
54680         Ext.chart.theme.call(this, config, {
54681             background: false,
54682             axis: {
54683                 stroke: '#444',
54684                 'stroke-width': 1
54685             },
54686             axisLabelTop: {
54687                 fill: '#444',
54688                 font: '12px Arial, Helvetica, sans-serif',
54689                 spacing: 2,
54690                 padding: 5,
54691                 renderer: function(v) { return v; }
54692             },
54693             axisLabelRight: {
54694                 fill: '#444',
54695                 font: '12px Arial, Helvetica, sans-serif',
54696                 spacing: 2,
54697                 padding: 5,
54698                 renderer: function(v) { return v; }
54699             },
54700             axisLabelBottom: {
54701                 fill: '#444',
54702                 font: '12px Arial, Helvetica, sans-serif',
54703                 spacing: 2,
54704                 padding: 5,
54705                 renderer: function(v) { return v; }
54706             },
54707             axisLabelLeft: {
54708                 fill: '#444',
54709                 font: '12px Arial, Helvetica, sans-serif',
54710                 spacing: 2,
54711                 padding: 5,
54712                 renderer: function(v) { return v; }
54713             },
54714             axisTitleTop: {
54715                 font: 'bold 18px Arial',
54716                 fill: '#444'
54717             },
54718             axisTitleRight: {
54719                 font: 'bold 18px Arial',
54720                 fill: '#444',
54721                 rotate: {
54722                     x:0, y:0,
54723                     degrees: 270
54724                 }
54725             },
54726             axisTitleBottom: {
54727                 font: 'bold 18px Arial',
54728                 fill: '#444'
54729             },
54730             axisTitleLeft: {
54731                 font: 'bold 18px Arial',
54732                 fill: '#444',
54733                 rotate: {
54734                     x:0, y:0,
54735                     degrees: 270
54736                 }
54737             },
54738             series: {
54739                 'stroke-width': 0
54740             },
54741             seriesLabel: {
54742                 font: '12px Arial',
54743                 fill: '#333'
54744             },
54745             marker: {
54746                 stroke: '#555',
54747                 fill: '#000',
54748                 radius: 3,
54749                 size: 3
54750             },
54751             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
54752             seriesThemes: [{
54753                 fill: "#115fa6"
54754             }, {
54755                 fill: "#94ae0a"
54756             }, {
54757                 fill: "#a61120"
54758             }, {
54759                 fill: "#ff8809"
54760             }, {
54761                 fill: "#ffd13e"
54762             }, {
54763                 fill: "#a61187"
54764             }, {
54765                 fill: "#24ad9a"
54766             }, {
54767                 fill: "#7c7474"
54768             }, {
54769                 fill: "#a66111"
54770             }],
54771             markerThemes: [{
54772                 fill: "#115fa6",
54773                 type: 'circle' 
54774             }, {
54775                 fill: "#94ae0a",
54776                 type: 'cross'
54777             }, {
54778                 fill: "#a61120",
54779                 type: 'plus'
54780             }]
54781         });
54782     }
54783 }, function() {
54784     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
54785         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
54786         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
54787         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
54788                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
54789                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
54790                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
54791                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
54792                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
54793         cats = categories.length;
54794     
54795     //Create themes from base colors
54796     for (; i < l; i++) {
54797         themes[names[i]] = (function(color) {
54798             return Ext.extend(themes.Base, {
54799                 constructor: function(config) {
54800                     themes.Base.prototype.constructor.call(this, Ext.apply({
54801                         baseColor: color
54802                     }, config));
54803                 }
54804             });
54805         })(palette[i]);
54806     }
54807     
54808     //Create theme from color array
54809     for (i = 0; i < cats; i++) {
54810         themes['Category' + (i + 1)] = (function(category) {
54811             return Ext.extend(themes.Base, {
54812                 constructor: function(config) {
54813                     themes.Base.prototype.constructor.call(this, Ext.apply({
54814                         colors: category
54815                     }, config));
54816                 }
54817             });
54818         })(categories[i]);
54819     }
54820 });
54821
54822 /**
54823  * @author Ed Spencer
54824  *
54825  * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
54826  * automatically configured with a {@link Ext.data.reader.Array}.
54827  *
54828  * A store configuration would be something like:
54829  *
54830  *     var store = Ext.create('Ext.data.ArrayStore', {
54831  *         // store configs
54832  *         autoDestroy: true,
54833  *         storeId: 'myStore',
54834  *         // reader configs
54835  *         idIndex: 0,
54836  *         fields: [
54837  *            'company',
54838  *            {name: 'price', type: 'float'},
54839  *            {name: 'change', type: 'float'},
54840  *            {name: 'pctChange', type: 'float'},
54841  *            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
54842  *         ]
54843  *     });
54844  *
54845  * This store is configured to consume a returned object of the form:
54846  *
54847  *     var myData = [
54848  *         ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
54849  *         ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
54850  *         ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
54851  *         ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
54852  *         ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
54853  *     ];
54854  *
54855  * An object literal of this form could also be used as the {@link #data} config option.
54856  *
54857  * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
54858  */
54859 Ext.define('Ext.data.ArrayStore', {
54860     extend: 'Ext.data.Store',
54861     alias: 'store.array',
54862     uses: ['Ext.data.reader.Array'],
54863
54864     constructor: function(config) {
54865         config = config || {};
54866
54867         Ext.applyIf(config, {
54868             proxy: {
54869                 type: 'memory',
54870                 reader: 'array'
54871             }
54872         });
54873
54874         this.callParent([config]);
54875     },
54876
54877     loadData: function(data, append) {
54878         if (this.expandData === true) {
54879             var r = [],
54880                 i = 0,
54881                 ln = data.length;
54882
54883             for (; i < ln; i++) {
54884                 r[r.length] = [data[i]];
54885             }
54886
54887             data = r;
54888         }
54889
54890         this.callParent([data, append]);
54891     }
54892 }, function() {
54893     // backwards compat
54894     Ext.data.SimpleStore = Ext.data.ArrayStore;
54895     // Ext.reg('simplestore', Ext.data.SimpleStore);
54896 });
54897
54898 /**
54899  * @author Ed Spencer
54900  * @class Ext.data.Batch
54901  *
54902  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
54903  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
54904  * event if any of the Operations encounter an exception.</p>
54905  *
54906  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
54907  *
54908  */
54909 Ext.define('Ext.data.Batch', {
54910     mixins: {
54911         observable: 'Ext.util.Observable'
54912     },
54913
54914     /**
54915      * @property {Boolean} autoStart
54916      * True to immediately start processing the batch as soon as it is constructed.
54917      */
54918     autoStart: false,
54919
54920     /**
54921      * @property {Number} current
54922      * The index of the current operation being executed
54923      */
54924     current: -1,
54925
54926     /**
54927      * @property {Number} total
54928      * The total number of operations in this batch. Read only
54929      */
54930     total: 0,
54931
54932     /**
54933      * @property {Boolean} isRunning
54934      * True if the batch is currently running
54935      */
54936     isRunning: false,
54937
54938     /**
54939      * @property {Boolean} isComplete
54940      * True if this batch has been executed completely
54941      */
54942     isComplete: false,
54943
54944     /**
54945      * @property {Boolean} hasException
54946      * True if this batch has encountered an exception. This is cleared at the start of each operation
54947      */
54948     hasException: false,
54949
54950     /**
54951      * @property {Boolean} pauseOnException
54952      * True to automatically pause the execution of the batch if any operation encounters an exception
54953      */
54954     pauseOnException: true,
54955
54956     /**
54957      * Creates new Batch object.
54958      * @param {Object} [config] Config object
54959      */
54960     constructor: function(config) {
54961         var me = this;
54962
54963         me.addEvents(
54964           /**
54965            * @event complete
54966            * Fired when all operations of this batch have been completed
54967            * @param {Ext.data.Batch} batch The batch object
54968            * @param {Object} operation The last operation that was executed
54969            */
54970           'complete',
54971
54972           /**
54973            * @event exception
54974            * Fired when a operation encountered an exception
54975            * @param {Ext.data.Batch} batch The batch object
54976            * @param {Object} operation The operation that encountered the exception
54977            */
54978           'exception',
54979
54980           /**
54981            * @event operationcomplete
54982            * Fired when each operation of the batch completes
54983            * @param {Ext.data.Batch} batch The batch object
54984            * @param {Object} operation The operation that just completed
54985            */
54986           'operationcomplete'
54987         );
54988
54989         me.mixins.observable.constructor.call(me, config);
54990
54991         /**
54992          * Ordered array of operations that will be executed by this batch
54993          * @property {Ext.data.Operation[]} operations
54994          */
54995         me.operations = [];
54996     },
54997
54998     /**
54999      * Adds a new operation to this batch
55000      * @param {Object} operation The {@link Ext.data.Operation Operation} object
55001      */
55002     add: function(operation) {
55003         this.total++;
55004
55005         operation.setBatch(this);
55006
55007         this.operations.push(operation);
55008     },
55009
55010     /**
55011      * Kicks off the execution of the batch, continuing from the next operation if the previous
55012      * operation encountered an exception, or if execution was paused
55013      */
55014     start: function() {
55015         this.hasException = false;
55016         this.isRunning = true;
55017
55018         this.runNextOperation();
55019     },
55020
55021     /**
55022      * @private
55023      * Runs the next operation, relative to this.current.
55024      */
55025     runNextOperation: function() {
55026         this.runOperation(this.current + 1);
55027     },
55028
55029     /**
55030      * Pauses execution of the batch, but does not cancel the current operation
55031      */
55032     pause: function() {
55033         this.isRunning = false;
55034     },
55035
55036     /**
55037      * Executes a operation by its numeric index
55038      * @param {Number} index The operation index to run
55039      */
55040     runOperation: function(index) {
55041         var me = this,
55042             operations = me.operations,
55043             operation  = operations[index],
55044             onProxyReturn;
55045
55046         if (operation === undefined) {
55047             me.isRunning  = false;
55048             me.isComplete = true;
55049             me.fireEvent('complete', me, operations[operations.length - 1]);
55050         } else {
55051             me.current = index;
55052
55053             onProxyReturn = function(operation) {
55054                 var hasException = operation.hasException();
55055
55056                 if (hasException) {
55057                     me.hasException = true;
55058                     me.fireEvent('exception', me, operation);
55059                 } else {
55060                     me.fireEvent('operationcomplete', me, operation);
55061                 }
55062
55063                 if (hasException && me.pauseOnException) {
55064                     me.pause();
55065                 } else {
55066                     operation.setCompleted();
55067                     me.runNextOperation();
55068                 }
55069             };
55070
55071             operation.setStarted();
55072
55073             me.proxy[operation.action](operation, onProxyReturn, me);
55074         }
55075     }
55076 });
55077 /**
55078  * @author Ed Spencer
55079  * @class Ext.data.BelongsToAssociation
55080  * @extends Ext.data.Association
55081  *
55082  * Represents a many to one association with another model. The owner model is expected to have
55083  * a foreign key which references the primary key of the associated model:
55084  *
55085  *     Ext.define('Category', {
55086  *         extend: 'Ext.data.Model',
55087  *         fields: [
55088  *             { name: 'id',   type: 'int' },
55089  *             { name: 'name', type: 'string' }
55090  *         ]
55091  *     });
55092  *
55093  *     Ext.define('Product', {
55094  *         extend: 'Ext.data.Model',
55095  *         fields: [
55096  *             { name: 'id',          type: 'int' },
55097  *             { name: 'category_id', type: 'int' },
55098  *             { name: 'name',        type: 'string' }
55099  *         ],
55100  *         // we can use the belongsTo shortcut on the model to create a belongsTo association
55101  *         associations: [
55102  *             { type: 'belongsTo', model: 'Category' }
55103  *         ]
55104  *     });
55105  *
55106  * In the example above we have created models for Products and Categories, and linked them together
55107  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
55108  * based on the Product's category_id, and provides new functions on the Product model:
55109  *
55110  * ## Generated getter function
55111  *
55112  * The first function that is added to the owner model is a getter function:
55113  *
55114  *     var product = new Product({
55115  *         id: 100,
55116  *         category_id: 20,
55117  *         name: 'Sneakers'
55118  *     });
55119  *
55120  *     product.getCategory(function(category, operation) {
55121  *         // do something with the category object
55122  *         alert(category.get('id')); // alerts 20
55123  *     }, this);
55124  *
55125  * The getCategory function was created on the Product model when we defined the association. This uses the
55126  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
55127  * callback when it has loaded.
55128  *
55129  * The new getCategory function will also accept an object containing success, failure and callback properties
55130  * - callback will always be called, success will only be called if the associated model was loaded successfully
55131  * and failure will only be called if the associatied model could not be loaded:
55132  *
55133  *     product.getCategory({
55134  *         callback: function(category, operation) {}, // a function that will always be called
55135  *         success : function(category, operation) {}, // a function that will only be called if the load succeeded
55136  *         failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
55137  *         scope   : this // optionally pass in a scope object to execute the callbacks in
55138  *     });
55139  *
55140  * In each case above the callbacks are called with two arguments - the associated model instance and the
55141  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
55142  * useful when the instance could not be loaded.
55143  *
55144  * ## Generated setter function
55145  *
55146  * The second generated function sets the associated model instance - if only a single argument is passed to
55147  * the setter then the following two calls are identical:
55148  *
55149  *     // this call...
55150  *     product.setCategory(10);
55151  *
55152  *     // is equivalent to this call:
55153  *     product.set('category_id', 10);
55154  *
55155  * If we pass in a second argument, the model will be automatically saved and the second argument passed to
55156  * the owner model's {@link Ext.data.Model#save save} method:
55157  *
55158  *     product.setCategory(10, function(product, operation) {
55159  *         // the product has been saved
55160  *         alert(product.get('category_id')); //now alerts 10
55161  *     });
55162  *
55163  *     //alternative syntax:
55164  *     product.setCategory(10, {
55165  *         callback: function(product, operation), // a function that will always be called
55166  *         success : function(product, operation), // a function that will only be called if the load succeeded
55167  *         failure : function(product, operation), // a function that will only be called if the load did not succeed
55168  *         scope   : this //optionally pass in a scope object to execute the callbacks in
55169  *     })
55170  *
55171  * ## Customisation
55172  *
55173  * Associations reflect on the models they are linking to automatically set up properties such as the
55174  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
55175  *
55176  *     Ext.define('Product', {
55177  *         fields: [...],
55178  *
55179  *         associations: [
55180  *             { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
55181  *         ]
55182  *     });
55183  *
55184  * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
55185  * with our own settings. Usually this will not be needed.
55186  */
55187 Ext.define('Ext.data.BelongsToAssociation', {
55188     extend: 'Ext.data.Association',
55189
55190     alias: 'association.belongsto',
55191
55192     /**
55193      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
55194      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
55195      * model called Product would set up a product_id foreign key.
55196      *
55197      *     Ext.define('Order', {
55198      *         extend: 'Ext.data.Model',
55199      *         fields: ['id', 'date'],
55200      *         hasMany: 'Product'
55201      *     });
55202      *
55203      *     Ext.define('Product', {
55204      *         extend: 'Ext.data.Model',
55205      *         fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
55206      *         belongsTo: 'Group'
55207      *     });
55208      *     var product = new Product({
55209      *         id: 1,
55210      *         name: 'Product 1',
55211      *         order_id: 22
55212      *     }, 1);
55213      *     product.getOrder(); // Will make a call to the server asking for order_id 22
55214      *
55215      */
55216
55217     /**
55218      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
55219      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
55220      */
55221
55222     /**
55223      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
55224      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
55225      */
55226
55227     /**
55228      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
55229      * Use 'belongsTo' to create a HasManyAssocation
55230      *
55231      *     associations: [{
55232      *         type: 'belongsTo',
55233      *         model: 'User'
55234      *     }]
55235      */
55236     constructor: function(config) {
55237         this.callParent(arguments);
55238
55239         var me             = this,
55240             ownerProto     = me.ownerModel.prototype,
55241             associatedName = me.associatedName,
55242             getterName     = me.getterName || 'get' + associatedName,
55243             setterName     = me.setterName || 'set' + associatedName;
55244
55245         Ext.applyIf(me, {
55246             name        : associatedName,
55247             foreignKey  : associatedName.toLowerCase() + "_id",
55248             instanceName: associatedName + 'BelongsToInstance',
55249             associationKey: associatedName.toLowerCase()
55250         });
55251
55252         ownerProto[getterName] = me.createGetter();
55253         ownerProto[setterName] = me.createSetter();
55254     },
55255
55256     /**
55257      * @private
55258      * Returns a setter function to be placed on the owner model's prototype
55259      * @return {Function} The setter function
55260      */
55261     createSetter: function() {
55262         var me              = this,
55263             ownerModel      = me.ownerModel,
55264             associatedModel = me.associatedModel,
55265             foreignKey      = me.foreignKey,
55266             primaryKey      = me.primaryKey;
55267
55268         //'this' refers to the Model instance inside this function
55269         return function(value, options, scope) {
55270             this.set(foreignKey, value);
55271
55272             if (typeof options == 'function') {
55273                 options = {
55274                     callback: options,
55275                     scope: scope || this
55276                 };
55277             }
55278
55279             if (Ext.isObject(options)) {
55280                 return this.save(options);
55281             }
55282         };
55283     },
55284
55285     /**
55286      * @private
55287      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
55288      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
55289      * @return {Function} The getter function
55290      */
55291     createGetter: function() {
55292         var me              = this,
55293             ownerModel      = me.ownerModel,
55294             associatedName  = me.associatedName,
55295             associatedModel = me.associatedModel,
55296             foreignKey      = me.foreignKey,
55297             primaryKey      = me.primaryKey,
55298             instanceName    = me.instanceName;
55299
55300         //'this' refers to the Model instance inside this function
55301         return function(options, scope) {
55302             options = options || {};
55303
55304             var model = this,
55305                 foreignKeyId = model.get(foreignKey),
55306                 instance,
55307                 args;
55308
55309             if (model[instanceName] === undefined) {
55310                 instance = Ext.ModelManager.create({}, associatedName);
55311                 instance.set(primaryKey, foreignKeyId);
55312
55313                 if (typeof options == 'function') {
55314                     options = {
55315                         callback: options,
55316                         scope: scope || model
55317                     };
55318                 }
55319
55320                 associatedModel.load(foreignKeyId, options);
55321                 model[instanceName] = associatedModel;
55322                 return associatedModel;
55323             } else {
55324                 instance = model[instanceName];
55325                 args = [instance];
55326                 scope = scope || model;
55327
55328                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
55329                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
55330                 //instead of the association layer.
55331                 Ext.callback(options, scope, args);
55332                 Ext.callback(options.success, scope, args);
55333                 Ext.callback(options.failure, scope, args);
55334                 Ext.callback(options.callback, scope, args);
55335
55336                 return instance;
55337             }
55338         };
55339     },
55340
55341     /**
55342      * Read associated data
55343      * @private
55344      * @param {Ext.data.Model} record The record we're writing to
55345      * @param {Ext.data.reader.Reader} reader The reader for the associated model
55346      * @param {Object} associationData The raw associated data
55347      */
55348     read: function(record, reader, associationData){
55349         record[this.instanceName] = reader.read([associationData]).records[0];
55350     }
55351 });
55352
55353 /**
55354  * @class Ext.data.BufferStore
55355  * @extends Ext.data.Store
55356  * @ignore
55357  */
55358 Ext.define('Ext.data.BufferStore', {
55359     extend: 'Ext.data.Store',
55360     alias: 'store.buffer',
55361     sortOnLoad: false,
55362     filterOnLoad: false,
55363     
55364     constructor: function() {
55365         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
55366     }
55367 });
55368 /**
55369  * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
55370  * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
55371  * error conditions, etc).
55372  *
55373  * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
55374  * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
55375  *
55376  * # Specification
55377  *
55378  * For additional information consult the [Ext.Direct Specification][1].
55379  *
55380  * # Providers
55381  *
55382  * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
55383  * server. There are several providers that exist in the core at the moment:
55384  *
55385  * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
55386  * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
55387  * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
55388  *
55389  * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
55390  *
55391  * # Router
55392  *
55393  * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
55394  * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
55395  * solution and replace it with one that uses C# without changing the client side JavaScript at all.
55396  *
55397  * # Server side events
55398  *
55399  * Custom events from the server may be handled by the client by adding listeners, for example:
55400  *
55401  *     {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
55402  *
55403  *     // add a handler for a 'message' event sent by the server
55404  *     Ext.direct.Manager.on('message', function(e){
55405  *         out.append(String.format('<p><i>{0}</i></p>', e.data));
55406  *         out.el.scrollTo('t', 100000, true);
55407  *     });
55408  *
55409  *    [1]: http://sencha.com/products/extjs/extdirect
55410  *
55411  * @singleton
55412  * @alternateClassName Ext.Direct
55413  */
55414 Ext.define('Ext.direct.Manager', {
55415
55416     /* Begin Definitions */
55417     singleton: true,
55418
55419     mixins: {
55420         observable: 'Ext.util.Observable'
55421     },
55422
55423     requires: ['Ext.util.MixedCollection'],
55424
55425     statics: {
55426         exceptions: {
55427             TRANSPORT: 'xhr',
55428             PARSE: 'parse',
55429             LOGIN: 'login',
55430             SERVER: 'exception'
55431         }
55432     },
55433
55434     /* End Definitions */
55435
55436     constructor: function(){
55437         var me = this;
55438
55439         me.addEvents(
55440             /**
55441              * @event event
55442              * Fires after an event.
55443              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
55444              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
55445              */
55446             'event',
55447             /**
55448              * @event exception
55449              * Fires after an event exception.
55450              * @param {Ext.direct.Event} e The event type that occurred.
55451              */
55452             'exception'
55453         );
55454         me.transactions = Ext.create('Ext.util.MixedCollection');
55455         me.providers = Ext.create('Ext.util.MixedCollection');
55456
55457         me.mixins.observable.constructor.call(me);
55458     },
55459
55460     /**
55461      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
55462      * is not already connected, it will auto-connect.
55463      *
55464      *     var pollProv = new Ext.direct.PollingProvider({
55465      *         url: 'php/poll2.php'
55466      *     });
55467      *
55468      *     Ext.direct.Manager.addProvider({
55469      *         "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
55470      *         "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
55471      *         "actions":{              // each property within the actions object represents a Class
55472      *             "TestAction":[       // array of methods within each server side Class
55473      *             {
55474      *                 "name":"doEcho", // name of method
55475      *                 "len":1
55476      *             },{
55477      *                 "name":"multiply",
55478      *                 "len":1
55479      *             },{
55480      *                 "name":"doForm",
55481      *                 "formHandler":true, // handle form on server with Ext.Direct.Transaction
55482      *                 "len":1
55483      *             }]
55484      *         },
55485      *         "namespace":"myApplication",// namespace to create the Remoting Provider in
55486      *     },{
55487      *         type: 'polling', // create a {@link Ext.direct.PollingProvider}
55488      *         url:  'php/poll.php'
55489      *     }, pollProv); // reference to previously created instance
55490      *
55491      * @param {Ext.direct.Provider/Object...} provider
55492      * Accepts any number of Provider descriptions (an instance or config object for
55493      * a Provider). Each Provider description instructs Ext.Directhow to create
55494      * client-side stub methods.
55495      */
55496     addProvider : function(provider){
55497         var me = this,
55498             args = arguments,
55499             i = 0,
55500             len;
55501
55502         if (args.length > 1) {
55503             for (len = args.length; i < len; ++i) {
55504                 me.addProvider(args[i]);
55505             }
55506             return;
55507         }
55508
55509         // if provider has not already been instantiated
55510         if (!provider.isProvider) {
55511             provider = Ext.create('direct.' + provider.type + 'provider', provider);
55512         }
55513         me.providers.add(provider);
55514         provider.on('data', me.onProviderData, me);
55515
55516
55517         if (!provider.isConnected()) {
55518             provider.connect();
55519         }
55520
55521         return provider;
55522     },
55523
55524     /**
55525      * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
55526      * provider is {@link #addProvider added}.
55527      * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
55528      */
55529     getProvider : function(id){
55530         return id.isProvider ? id : this.providers.get(id);
55531     },
55532
55533     /**
55534      * Removes the provider.
55535      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
55536      * @return {Ext.direct.Provider} The provider, null if not found.
55537      */
55538     removeProvider : function(provider){
55539         var me = this,
55540             providers = me.providers;
55541
55542         provider = provider.isProvider ? provider : providers.get(provider);
55543
55544         if (provider) {
55545             provider.un('data', me.onProviderData, me);
55546             providers.remove(provider);
55547             return provider;
55548         }
55549         return null;
55550     },
55551
55552     /**
55553      * Adds a transaction to the manager.
55554      * @private
55555      * @param {Ext.direct.Transaction} transaction The transaction to add
55556      * @return {Ext.direct.Transaction} transaction
55557      */
55558     addTransaction: function(transaction){
55559         this.transactions.add(transaction);
55560         return transaction;
55561     },
55562
55563     /**
55564      * Removes a transaction from the manager.
55565      * @private
55566      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
55567      * @return {Ext.direct.Transaction} transaction
55568      */
55569     removeTransaction: function(transaction){
55570         transaction = this.getTransaction(transaction);
55571         this.transactions.remove(transaction);
55572         return transaction;
55573     },
55574
55575     /**
55576      * Gets a transaction
55577      * @private
55578      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
55579      * @return {Ext.direct.Transaction}
55580      */
55581     getTransaction: function(transaction){
55582         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
55583     },
55584
55585     onProviderData : function(provider, event){
55586         var me = this,
55587             i = 0,
55588             len;
55589
55590         if (Ext.isArray(event)) {
55591             for (len = event.length; i < len; ++i) {
55592                 me.onProviderData(provider, event[i]);
55593             }
55594             return;
55595         }
55596         if (event.name && event.name != 'event' && event.name != 'exception') {
55597             me.fireEvent(event.name, event);
55598         } else if (event.status === false) {
55599             me.fireEvent('exception', event);
55600         }
55601         me.fireEvent('event', event, provider);
55602     }
55603 }, function(){
55604     // Backwards compatibility
55605     Ext.Direct = Ext.direct.Manager;
55606 });
55607
55608 /**
55609  * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
55610  * request is made, the transport mechanism is handed off to the appropriate
55611  * {@link Ext.direct.RemotingProvider Provider} to complete the call.
55612  *
55613  * # Specifying the function
55614  *
55615  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
55616  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
55617  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
55618  * allows you to specify a different remoting method for each CRUD action.
55619  *
55620  * # Parameters
55621  *
55622  * This proxy provides options to help configure which parameters will be sent to the server.
55623  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
55624  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
55625  * the remoting method parameters are passed.
55626  *
55627  * # Example Usage
55628  *
55629  *     Ext.define('User', {
55630  *         extend: 'Ext.data.Model',
55631  *         fields: ['firstName', 'lastName'],
55632  *         proxy: {
55633  *             type: 'direct',
55634  *             directFn: MyApp.getUsers,
55635  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
55636  *         }
55637  *     });
55638  *     User.load(1);
55639  */
55640 Ext.define('Ext.data.proxy.Direct', {
55641     /* Begin Definitions */
55642
55643     extend: 'Ext.data.proxy.Server',
55644     alternateClassName: 'Ext.data.DirectProxy',
55645
55646     alias: 'proxy.direct',
55647
55648     requires: ['Ext.direct.Manager'],
55649
55650     /* End Definitions */
55651
55652     /**
55653      * @cfg {String/String[]} paramOrder
55654      * Defaults to undefined. A list of params to be executed server side.  Specify the params in the order in
55655      * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
55656      * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
55657      * acceptable:
55658      *
55659      *     paramOrder: ['param1','param2','param3']
55660      *     paramOrder: 'param1 param2 param3'
55661      *     paramOrder: 'param1,param2,param3'
55662      *     paramOrder: 'param1|param2|param'
55663      */
55664     paramOrder: undefined,
55665
55666     /**
55667      * @cfg {Boolean} paramsAsHash
55668      * Send parameters as a collection of named arguments.
55669      * Providing a {@link #paramOrder} nullifies this configuration.
55670      */
55671     paramsAsHash: true,
55672
55673     /**
55674      * @cfg {Function} directFn
55675      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
55676      * for Store's which will not implement a full CRUD api.
55677      */
55678     directFn : undefined,
55679
55680     /**
55681      * @cfg {Object} api
55682      * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
55683      * function call.
55684      */
55685
55686     /**
55687      * @cfg {Object} extraParams
55688      * Extra parameters that will be included on every read request. Individual requests with params
55689      * of the same name will override these params when they are in conflict.
55690      */
55691
55692     // private
55693     paramOrderRe: /[\s,|]/,
55694
55695     constructor: function(config){
55696         var me = this;
55697
55698         Ext.apply(me, config);
55699         if (Ext.isString(me.paramOrder)) {
55700             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
55701         }
55702         me.callParent(arguments);
55703     },
55704
55705     doRequest: function(operation, callback, scope) {
55706         var me = this,
55707             writer = me.getWriter(),
55708             request = me.buildRequest(operation, callback, scope),
55709             fn = me.api[request.action]  || me.directFn,
55710             args = [],
55711             params = request.params,
55712             paramOrder = me.paramOrder,
55713             method,
55714             i = 0,
55715             len;
55716
55717         //<debug>
55718         if (!fn) {
55719             Ext.Error.raise('No direct function specified for this proxy');
55720         }
55721         //</debug>
55722
55723         if (operation.allowWrite()) {
55724             request = writer.write(request);
55725         }
55726
55727         if (operation.action == 'read') {
55728             // We need to pass params
55729             method = fn.directCfg.method;
55730
55731             if (method.ordered) {
55732                 if (method.len > 0) {
55733                     if (paramOrder) {
55734                         for (len = paramOrder.length; i < len; ++i) {
55735                             args.push(params[paramOrder[i]]);
55736                         }
55737                     } else if (me.paramsAsHash) {
55738                         args.push(params);
55739                     }
55740                 }
55741             } else {
55742                 args.push(params);
55743             }
55744         } else {
55745             args.push(request.jsonData);
55746         }
55747
55748         Ext.apply(request, {
55749             args: args,
55750             directFn: fn
55751         });
55752         args.push(me.createRequestCallback(request, operation, callback, scope), me);
55753         fn.apply(window, args);
55754     },
55755
55756     /*
55757      * Inherit docs. We don't apply any encoding here because
55758      * all of the direct requests go out as jsonData
55759      */
55760     applyEncoding: function(value){
55761         return value;
55762     },
55763
55764     createRequestCallback: function(request, operation, callback, scope){
55765         var me = this;
55766
55767         return function(data, event){
55768             me.processResponse(event.status, operation, request, event, callback, scope);
55769         };
55770     },
55771
55772     // inherit docs
55773     extractResponseData: function(response){
55774         return Ext.isDefined(response.result) ? response.result : response.data;
55775     },
55776
55777     // inherit docs
55778     setException: function(operation, response) {
55779         operation.setException(response.message);
55780     },
55781
55782     // inherit docs
55783     buildUrl: function(){
55784         return '';
55785     }
55786 });
55787
55788 /**
55789  * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
55790  * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
55791  * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
55792  * {@link Ext.data.Store} configured as needed.
55793  *
55794  * **Note:** Although they are not listed, this class inherits all of the config options of:
55795  *
55796  * - **{@link Ext.data.Store Store}**
55797  *
55798  * - **{@link Ext.data.reader.Json JsonReader}**
55799  *
55800  *   - **{@link Ext.data.reader.Json#root root}**
55801  *   - **{@link Ext.data.reader.Json#idProperty idProperty}**
55802  *   - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
55803  *
55804  * - **{@link Ext.data.proxy.Direct DirectProxy}**
55805  *
55806  *   - **{@link Ext.data.proxy.Direct#directFn directFn}**
55807  *   - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
55808  *   - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
55809  *
55810  */
55811 Ext.define('Ext.data.DirectStore', {
55812     /* Begin Definitions */
55813     
55814     extend: 'Ext.data.Store',
55815     
55816     alias: 'store.direct',
55817     
55818     requires: ['Ext.data.proxy.Direct'],
55819    
55820     /* End Definitions */
55821
55822     constructor : function(config){
55823         config = Ext.apply({}, config);
55824         if (!config.proxy) {
55825             var proxy = {
55826                 type: 'direct',
55827                 reader: {
55828                     type: 'json'
55829                 }
55830             };
55831             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
55832             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
55833             config.proxy = proxy;
55834         }
55835         this.callParent([config]);
55836     }    
55837 });
55838
55839 /**
55840  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
55841  * {@link #ordinalize ordinalizes} words. Sample usage:
55842  *
55843  *     //turning singular words into plurals
55844  *     Ext.util.Inflector.pluralize('word'); //'words'
55845  *     Ext.util.Inflector.pluralize('person'); //'people'
55846  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
55847  *
55848  *     //turning plurals into singulars
55849  *     Ext.util.Inflector.singularize('words'); //'word'
55850  *     Ext.util.Inflector.singularize('people'); //'person'
55851  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
55852  *
55853  *     //ordinalizing numbers
55854  *     Ext.util.Inflector.ordinalize(11); //"11th"
55855  *     Ext.util.Inflector.ordinalize(21); //"21th"
55856  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
55857  *
55858  * # Customization
55859  *
55860  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
55861  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
55862  * Here is how we might add a rule that pluralizes "ox" to "oxen":
55863  *
55864  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
55865  *
55866  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
55867  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
55868  * how we could add the inverse rule:
55869  *
55870  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
55871  *
55872  * Note that the ox/oxen rules are present by default.
55873  */
55874 Ext.define('Ext.util.Inflector', {
55875
55876     /* Begin Definitions */
55877
55878     singleton: true,
55879
55880     /* End Definitions */
55881
55882     /**
55883      * @private
55884      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
55885      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
55886      * part of the regular expression. This is managed by the {@link #plural} method.
55887      * @property {Array} plurals
55888      */
55889     plurals: [
55890         [(/(quiz)$/i),                "$1zes"  ],
55891         [(/^(ox)$/i),                 "$1en"   ],
55892         [(/([m|l])ouse$/i),           "$1ice"  ],
55893         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
55894         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
55895         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
55896         [(/(hive)$/i),                "$1s"    ],
55897         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
55898         [(/sis$/i),                   "ses"    ],
55899         [(/([ti])um$/i),              "$1a"    ],
55900         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
55901         [(/(bu)s$/i),                 "$1ses"  ],
55902         [(/(alias|status|sex)$/i),    "$1es"   ],
55903         [(/(octop|vir)us$/i),         "$1i"    ],
55904         [(/(ax|test)is$/i),           "$1es"   ],
55905         [(/^person$/),                "people" ],
55906         [(/^man$/),                   "men"    ],
55907         [(/^(child)$/),               "$1ren"  ],
55908         [(/s$/i),                     "s"      ],
55909         [(/$/),                       "s"      ]
55910     ],
55911
55912     /**
55913      * @private
55914      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
55915      * regular expression that matches the plural form of a word, the second must be a String that replaces the
55916      * matched part of the regular expression. This is managed by the {@link #singular} method.
55917      * @property {Array} singulars
55918      */
55919     singulars: [
55920       [(/(quiz)zes$/i),                                                    "$1"     ],
55921       [(/(matr)ices$/i),                                                   "$1ix"   ],
55922       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
55923       [(/^(ox)en/i),                                                       "$1"     ],
55924       [(/(alias|status)es$/i),                                             "$1"     ],
55925       [(/(octop|vir)i$/i),                                                 "$1us"   ],
55926       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
55927       [(/(shoe)s$/i),                                                      "$1"     ],
55928       [(/(o)es$/i),                                                        "$1"     ],
55929       [(/(bus)es$/i),                                                      "$1"     ],
55930       [(/([m|l])ice$/i),                                                   "$1ouse" ],
55931       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
55932       [(/(m)ovies$/i),                                                     "$1ovie" ],
55933       [(/(s)eries$/i),                                                     "$1eries"],
55934       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
55935       [(/([lr])ves$/i),                                                    "$1f"    ],
55936       [(/(tive)s$/i),                                                      "$1"     ],
55937       [(/(hive)s$/i),                                                      "$1"     ],
55938       [(/([^f])ves$/i),                                                    "$1fe"   ],
55939       [(/(^analy)ses$/i),                                                  "$1sis"  ],
55940       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
55941       [(/([ti])a$/i),                                                      "$1um"   ],
55942       [(/(n)ews$/i),                                                       "$1ews"  ],
55943       [(/people$/i),                                                       "person" ],
55944       [(/s$/i),                                                            ""       ]
55945     ],
55946
55947     /**
55948      * @private
55949      * The registered uncountable words
55950      * @property {String[]} uncountable
55951      */
55952      uncountable: [
55953         "sheep",
55954         "fish",
55955         "series",
55956         "species",
55957         "money",
55958         "rice",
55959         "information",
55960         "equipment",
55961         "grass",
55962         "mud",
55963         "offspring",
55964         "deer",
55965         "means"
55966     ],
55967
55968     /**
55969      * Adds a new singularization rule to the Inflector. See the intro docs for more information
55970      * @param {RegExp} matcher The matcher regex
55971      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
55972      */
55973     singular: function(matcher, replacer) {
55974         this.singulars.unshift([matcher, replacer]);
55975     },
55976
55977     /**
55978      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
55979      * @param {RegExp} matcher The matcher regex
55980      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
55981      */
55982     plural: function(matcher, replacer) {
55983         this.plurals.unshift([matcher, replacer]);
55984     },
55985
55986     /**
55987      * Removes all registered singularization rules
55988      */
55989     clearSingulars: function() {
55990         this.singulars = [];
55991     },
55992
55993     /**
55994      * Removes all registered pluralization rules
55995      */
55996     clearPlurals: function() {
55997         this.plurals = [];
55998     },
55999
56000     /**
56001      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
56002      * @param {String} word The word to test
56003      * @return {Boolean} True if the word is transnumeral
56004      */
56005     isTransnumeral: function(word) {
56006         return Ext.Array.indexOf(this.uncountable, word) != -1;
56007     },
56008
56009     /**
56010      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
56011      * @param {String} word The word to pluralize
56012      * @return {String} The pluralized form of the word
56013      */
56014     pluralize: function(word) {
56015         if (this.isTransnumeral(word)) {
56016             return word;
56017         }
56018
56019         var plurals = this.plurals,
56020             length  = plurals.length,
56021             tuple, regex, i;
56022
56023         for (i = 0; i < length; i++) {
56024             tuple = plurals[i];
56025             regex = tuple[0];
56026
56027             if (regex == word || (regex.test && regex.test(word))) {
56028                 return word.replace(regex, tuple[1]);
56029             }
56030         }
56031
56032         return word;
56033     },
56034
56035     /**
56036      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
56037      * @param {String} word The word to singularize
56038      * @return {String} The singularized form of the word
56039      */
56040     singularize: function(word) {
56041         if (this.isTransnumeral(word)) {
56042             return word;
56043         }
56044
56045         var singulars = this.singulars,
56046             length    = singulars.length,
56047             tuple, regex, i;
56048
56049         for (i = 0; i < length; i++) {
56050             tuple = singulars[i];
56051             regex = tuple[0];
56052
56053             if (regex == word || (regex.test && regex.test(word))) {
56054                 return word.replace(regex, tuple[1]);
56055             }
56056         }
56057
56058         return word;
56059     },
56060
56061     /**
56062      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
56063      * package
56064      * @param {String} word The word to classify
56065      * @return {String} The classified version of the word
56066      */
56067     classify: function(word) {
56068         return Ext.String.capitalize(this.singularize(word));
56069     },
56070
56071     /**
56072      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
56073      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
56074      * @param {Number} number The number to ordinalize
56075      * @return {String} The ordinalized number
56076      */
56077     ordinalize: function(number) {
56078         var parsed = parseInt(number, 10),
56079             mod10  = parsed % 10,
56080             mod100 = parsed % 100;
56081
56082         //11 through 13 are a special case
56083         if (11 <= mod100 && mod100 <= 13) {
56084             return number + "th";
56085         } else {
56086             switch(mod10) {
56087                 case 1 : return number + "st";
56088                 case 2 : return number + "nd";
56089                 case 3 : return number + "rd";
56090                 default: return number + "th";
56091             }
56092         }
56093     }
56094 }, function() {
56095     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
56096     var irregulars = {
56097             alumnus: 'alumni',
56098             cactus : 'cacti',
56099             focus  : 'foci',
56100             nucleus: 'nuclei',
56101             radius: 'radii',
56102             stimulus: 'stimuli',
56103             ellipsis: 'ellipses',
56104             paralysis: 'paralyses',
56105             oasis: 'oases',
56106             appendix: 'appendices',
56107             index: 'indexes',
56108             beau: 'beaux',
56109             bureau: 'bureaux',
56110             tableau: 'tableaux',
56111             woman: 'women',
56112             child: 'children',
56113             man: 'men',
56114             corpus:     'corpora',
56115             criterion: 'criteria',
56116             curriculum: 'curricula',
56117             genus: 'genera',
56118             memorandum: 'memoranda',
56119             phenomenon: 'phenomena',
56120             foot: 'feet',
56121             goose: 'geese',
56122             tooth: 'teeth',
56123             antenna: 'antennae',
56124             formula: 'formulae',
56125             nebula: 'nebulae',
56126             vertebra: 'vertebrae',
56127             vita: 'vitae'
56128         },
56129         singular;
56130
56131     for (singular in irregulars) {
56132         this.plural(singular, irregulars[singular]);
56133         this.singular(irregulars[singular], singular);
56134     }
56135 });
56136 /**
56137  * @author Ed Spencer
56138  * @class Ext.data.HasManyAssociation
56139  * @extends Ext.data.Association
56140  * 
56141  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
56142  * 
56143 <pre><code>
56144 Ext.define('Product', {
56145     extend: 'Ext.data.Model',
56146     fields: [
56147         {name: 'id',      type: 'int'},
56148         {name: 'user_id', type: 'int'},
56149         {name: 'name',    type: 'string'}
56150     ]
56151 });
56152
56153 Ext.define('User', {
56154     extend: 'Ext.data.Model',
56155     fields: [
56156         {name: 'id',   type: 'int'},
56157         {name: 'name', type: 'string'}
56158     ],
56159     // we can use the hasMany shortcut on the model to create a hasMany association
56160     hasMany: {model: 'Product', name: 'products'}
56161 });
56162 </pre></code>
56163
56164  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
56165  * us a new function on every User instance, in this case the function is called 'products' because that is the name
56166  * we specified in the association configuration above.</p>
56167  * 
56168  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
56169  * only Products for the given model instance:</p>
56170  * 
56171 <pre><code>
56172 //first, we load up a User with id of 1
56173 var user = Ext.create('User', {id: 1, name: 'Ed'});
56174
56175 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
56176 //the created store is automatically scoped to the set of Products for the User with id of 1
56177 var products = user.products();
56178
56179 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
56180 products.add({
56181     name: 'Another Product'
56182 });
56183
56184 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
56185 products.sync();
56186 </code></pre>
56187  * 
56188  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
56189  * though calling products() a second time returns the same store instance.</p>
56190  * 
56191  * <p><u>Custom filtering</u></p>
56192  * 
56193  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
56194  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
56195  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
56196  * 
56197  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
56198  * have models for Search and Tweet:</p>
56199  * 
56200 <pre><code>
56201 Ext.define('Search', {
56202     extend: 'Ext.data.Model',
56203     fields: [
56204         'id', 'query'
56205     ],
56206
56207     hasMany: {
56208         model: 'Tweet',
56209         name : 'tweets',
56210         filterProperty: 'query'
56211     }
56212 });
56213
56214 Ext.define('Tweet', {
56215     extend: 'Ext.data.Model',
56216     fields: [
56217         'id', 'text', 'from_user'
56218     ]
56219 });
56220
56221 //returns a Store filtered by the filterProperty
56222 var store = new Search({query: 'Sencha Touch'}).tweets();
56223 </code></pre>
56224  * 
56225  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
56226  * equivalent to this:</p>
56227  * 
56228 <pre><code>
56229 var store = Ext.create('Ext.data.Store', {
56230     model: 'Tweet',
56231     filters: [
56232         {
56233             property: 'query',
56234             value   : 'Sencha Touch'
56235         }
56236     ]
56237 });
56238 </code></pre>
56239  */
56240 Ext.define('Ext.data.HasManyAssociation', {
56241     extend: 'Ext.data.Association',
56242     requires: ['Ext.util.Inflector'],
56243
56244     alias: 'association.hasmany',
56245
56246     /**
56247      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
56248      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
56249      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
56250      * the store is automatically filtered so that only records with a matching foreign key are included in the 
56251      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
56252      * <pre><code>
56253 Ext.define('Group', {
56254     extend: 'Ext.data.Model',
56255     fields: ['id', 'name'],
56256     hasMany: 'User'
56257 });
56258
56259 Ext.define('User', {
56260     extend: 'Ext.data.Model',
56261     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
56262     belongsTo: 'Group'
56263 });
56264      * </code></pre>
56265      */
56266     
56267     /**
56268      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
56269      * If not specified, the pluralized name of the child model is used.
56270      * <pre><code>
56271 // This will create a users() method on any Group model instance
56272 Ext.define('Group', {
56273     extend: 'Ext.data.Model',
56274     fields: ['id', 'name'],
56275     hasMany: 'User'
56276 });
56277 var group = new Group();
56278 console.log(group.users());
56279
56280 // The method to retrieve the users will now be getUserList
56281 Ext.define('Group', {
56282     extend: 'Ext.data.Model',
56283     fields: ['id', 'name'],
56284     hasMany: {model: 'User', name: 'getUserList'}
56285 });
56286 var group = new Group();
56287 console.log(group.getUserList());
56288      * </code></pre>
56289      */
56290     
56291     /**
56292      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
56293      * undefined.
56294      */
56295     
56296     /**
56297      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
56298      * this is not set, a filter is automatically created which filters the association based on the configured 
56299      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
56300      */
56301     
56302     /**
56303      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
56304      * Defaults to <tt>false</tt>.
56305      */
56306     
56307     /**
56308      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
56309      * Use 'hasMany' to create a HasManyAssocation
56310      * <pre><code>
56311 associations: [{
56312     type: 'hasMany',
56313     model: 'User'
56314 }]
56315      * </code></pre>
56316      */
56317     
56318     constructor: function(config) {
56319         var me = this,
56320             ownerProto,
56321             name;
56322             
56323         me.callParent(arguments);
56324         
56325         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
56326         
56327         ownerProto = me.ownerModel.prototype;
56328         name = me.name;
56329         
56330         Ext.applyIf(me, {
56331             storeName : name + "Store",
56332             foreignKey: me.ownerName.toLowerCase() + "_id"
56333         });
56334         
56335         ownerProto[name] = me.createStore();
56336     },
56337     
56338     /**
56339      * @private
56340      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
56341      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
56342      * returns a Store configured to return the filtered set of a single Group's Users.
56343      * @return {Function} The store-generating function
56344      */
56345     createStore: function() {
56346         var that            = this,
56347             associatedModel = that.associatedModel,
56348             storeName       = that.storeName,
56349             foreignKey      = that.foreignKey,
56350             primaryKey      = that.primaryKey,
56351             filterProperty  = that.filterProperty,
56352             autoLoad        = that.autoLoad,
56353             storeConfig     = that.storeConfig || {};
56354         
56355         return function() {
56356             var me = this,
56357                 config, filter,
56358                 modelDefaults = {};
56359                 
56360             if (me[storeName] === undefined) {
56361                 if (filterProperty) {
56362                     filter = {
56363                         property  : filterProperty,
56364                         value     : me.get(filterProperty),
56365                         exactMatch: true
56366                     };
56367                 } else {
56368                     filter = {
56369                         property  : foreignKey,
56370                         value     : me.get(primaryKey),
56371                         exactMatch: true
56372                     };
56373                 }
56374                 
56375                 modelDefaults[foreignKey] = me.get(primaryKey);
56376                 
56377                 config = Ext.apply({}, storeConfig, {
56378                     model        : associatedModel,
56379                     filters      : [filter],
56380                     remoteFilter : false,
56381                     modelDefaults: modelDefaults
56382                 });
56383                 
56384                 me[storeName] = Ext.create('Ext.data.Store', config);
56385                 if (autoLoad) {
56386                     me[storeName].load();
56387                 }
56388             }
56389             
56390             return me[storeName];
56391         };
56392     },
56393     
56394     /**
56395      * Read associated data
56396      * @private
56397      * @param {Ext.data.Model} record The record we're writing to
56398      * @param {Ext.data.reader.Reader} reader The reader for the associated model
56399      * @param {Object} associationData The raw associated data
56400      */
56401     read: function(record, reader, associationData){
56402         var store = record[this.name](),
56403             inverse;
56404     
56405         store.add(reader.read(associationData).records);
56406     
56407         //now that we've added the related records to the hasMany association, set the inverse belongsTo
56408         //association on each of them if it exists
56409         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
56410             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
56411         });
56412     
56413         //if the inverse association was found, set it now on each record we've just created
56414         if (inverse) {
56415             store.data.each(function(associatedRecord){
56416                 associatedRecord[inverse.instanceName] = record;
56417             });
56418         }
56419     }
56420 });
56421 /**
56422  * @class Ext.data.JsonP
56423  * @singleton
56424  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
56425  * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
56426  */
56427 Ext.define('Ext.data.JsonP', {
56428
56429     /* Begin Definitions */
56430
56431     singleton: true,
56432
56433     statics: {
56434         requestCount: 0,
56435         requests: {}
56436     },
56437
56438     /* End Definitions */
56439
56440     /**
56441      * @property timeout
56442      * @type Number
56443      * A default timeout for any JsonP requests. If the request has not completed in this time the
56444      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
56445      */
56446     timeout: 30000,
56447
56448     /**
56449      * @property disableCaching
56450      * @type Boolean
56451      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
56452      */
56453     disableCaching: true,
56454
56455     /**
56456      * @property disableCachingParam
56457      * @type String
56458      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
56459      */
56460     disableCachingParam: '_dc',
56461
56462     /**
56463      * @property callbackKey
56464      * @type String
56465      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
56466      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
56467      * url?callback=Ext.data.JsonP.callback1
56468      */
56469     callbackKey: 'callback',
56470
56471     /**
56472      * Makes a JSONP request.
56473      * @param {Object} options An object which may contain the following properties. Note that options will
56474      * take priority over any defaults that are specified in the class.
56475      * <ul>
56476      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
56477      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
56478      * key value pairs that will be sent along with the request.</div></li>
56479      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
56480      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
56481      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
56482      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
56483      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
56484      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
56485      * in each JsonP request that you make.</div></li>
56486      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
56487      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
56488      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
56489      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
56490      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
56491      * completes, whether it is a success or failure.</div></li>
56492      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
56493      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
56494      * </ul>
56495      * @return {Object} request An object containing the request details.
56496      */
56497     request: function(options){
56498         options = Ext.apply({}, options);
56499
56500         //<debug>
56501         if (!options.url) {
56502             Ext.Error.raise('A url must be specified for a JSONP request.');
56503         }
56504         //</debug>
56505
56506         var me = this,
56507             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
56508             cacheParam = options.disableCachingParam || me.disableCachingParam,
56509             id = ++me.statics().requestCount,
56510             callbackName = options.callbackName || 'callback' + id,
56511             callbackKey = options.callbackKey || me.callbackKey,
56512             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
56513             params = Ext.apply({}, options.params),
56514             url = options.url,
56515             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
56516             request,
56517             script;
56518
56519         params[callbackKey] = name + '.data.JsonP.' + callbackName;
56520         if (disableCaching) {
56521             params[cacheParam] = new Date().getTime();
56522         }
56523
56524         script = me.createScript(url, params);
56525
56526         me.statics().requests[id] = request = {
56527             url: url,
56528             params: params,
56529             script: script,
56530             id: id,
56531             scope: options.scope,
56532             success: options.success,
56533             failure: options.failure,
56534             callback: options.callback,
56535             callbackName: callbackName
56536         };
56537
56538         if (timeout > 0) {
56539             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
56540         }
56541
56542         me.setupErrorHandling(request);
56543         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
56544         Ext.getHead().appendChild(script);
56545         return request;
56546     },
56547
56548     /**
56549      * Abort a request. If the request parameter is not specified all open requests will
56550      * be aborted.
56551      * @param {Object/String} request (Optional) The request to abort
56552      */
56553     abort: function(request){
56554         var requests = this.statics().requests,
56555             key;
56556
56557         if (request) {
56558             if (!request.id) {
56559                 request = requests[request];
56560             }
56561             this.abort(request);
56562         } else {
56563             for (key in requests) {
56564                 if (requests.hasOwnProperty(key)) {
56565                     this.abort(requests[key]);
56566                 }
56567             }
56568         }
56569     },
56570
56571     /**
56572      * Sets up error handling for the script
56573      * @private
56574      * @param {Object} request The request
56575      */
56576     setupErrorHandling: function(request){
56577         request.script.onerror = Ext.bind(this.handleError, this, [request]);
56578     },
56579
56580     /**
56581      * Handles any aborts when loading the script
56582      * @private
56583      * @param {Object} request The request
56584      */
56585     handleAbort: function(request){
56586         request.errorType = 'abort';
56587         this.handleResponse(null, request);
56588     },
56589
56590     /**
56591      * Handles any script errors when loading the script
56592      * @private
56593      * @param {Object} request The request
56594      */
56595     handleError: function(request){
56596         request.errorType = 'error';
56597         this.handleResponse(null, request);
56598     },
56599
56600     /**
56601      * Cleans up anu script handling errors
56602      * @private
56603      * @param {Object} request The request
56604      */
56605     cleanupErrorHandling: function(request){
56606         request.script.onerror = null;
56607     },
56608
56609     /**
56610      * Handle any script timeouts
56611      * @private
56612      * @param {Object} request The request
56613      */
56614     handleTimeout: function(request){
56615         request.errorType = 'timeout';
56616         this.handleResponse(null, request);
56617     },
56618
56619     /**
56620      * Handle a successful response
56621      * @private
56622      * @param {Object} result The result from the request
56623      * @param {Object} request The request
56624      */
56625     handleResponse: function(result, request){
56626
56627         var success = true;
56628
56629         if (request.timeout) {
56630             clearTimeout(request.timeout);
56631         }
56632         delete this[request.callbackName];
56633         delete this.statics()[request.id];
56634         this.cleanupErrorHandling(request);
56635         Ext.fly(request.script).remove();
56636
56637         if (request.errorType) {
56638             success = false;
56639             Ext.callback(request.failure, request.scope, [request.errorType]);
56640         } else {
56641             Ext.callback(request.success, request.scope, [result]);
56642         }
56643         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
56644     },
56645
56646     /**
56647      * Create the script tag
56648      * @private
56649      * @param {String} url The url of the request
56650      * @param {Object} params Any extra params to be sent
56651      */
56652     createScript: function(url, params) {
56653         var script = document.createElement('script');
56654         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
56655         script.setAttribute("async", true);
56656         script.setAttribute("type", "text/javascript");
56657         return script;
56658     }
56659 });
56660
56661 /**
56662  * @class Ext.data.JsonPStore
56663  * @extends Ext.data.Store
56664  * @private
56665  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
56666  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
56667  * <p>A store configuration would be something like:<pre><code>
56668 var store = new Ext.data.JsonPStore({
56669     // store configs
56670     autoDestroy: true,
56671     storeId: 'myStore',
56672
56673     // proxy configs
56674     url: 'get-images.php',
56675
56676     // reader configs
56677     root: 'images',
56678     idProperty: 'name',
56679     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
56680 });
56681  * </code></pre></p>
56682  * <p>This store is configured to consume a returned object of the form:<pre><code>
56683 stcCallback({
56684     images: [
56685         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
56686         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
56687     ]
56688 })
56689  * </code></pre>
56690  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
56691  * for details of how this works.</p>
56692  * An object literal of this form could also be used as the {@link #data} config option.</p>
56693  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
56694  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
56695  * @xtype jsonpstore
56696  */
56697 Ext.define('Ext.data.JsonPStore', {
56698     extend: 'Ext.data.Store',
56699     alias : 'store.jsonp',
56700
56701     /**
56702      * @cfg {Ext.data.DataReader} reader @hide
56703      */
56704     constructor: function(config) {
56705         this.callParent(Ext.apply(config, {
56706             reader: Ext.create('Ext.data.reader.Json', config),
56707             proxy : Ext.create('Ext.data.proxy.JsonP', config)
56708         }));
56709     }
56710 });
56711
56712 /**
56713  * This class is used as a set of methods that are applied to the prototype of a
56714  * Model to decorate it with a Node API. This means that models used in conjunction with a tree
56715  * will have all of the tree related methods available on the model. In general this class will
56716  * not be used directly by the developer. This class also creates extra fields on the model if
56717  * they do not exist, to help maintain the tree state and UI. These fields are documented as
56718  * config options.
56719  */
56720 Ext.define('Ext.data.NodeInterface', {
56721     requires: ['Ext.data.Field'],
56722
56723     /**
56724      * @cfg {String} parentId
56725      * ID of parent node.
56726      */
56727
56728     /**
56729      * @cfg {Number} index
56730      * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
56731      * index will be 2.
56732      */
56733
56734     /**
56735      * @cfg {Number} depth
56736      * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
56737      */
56738
56739     /**
56740      * @cfg {Boolean} [expanded=false]
56741      * True if the node is expanded.
56742      */
56743
56744     /**
56745      * @cfg {Boolean} [expandable=false]
56746      * Set to true to allow for expanding/collapsing of this node.
56747      */
56748
56749     /**
56750      * @cfg {Boolean} [checked=null]
56751      * Set to true or false to show a checkbox alongside this node.
56752      */
56753
56754     /**
56755      * @cfg {Boolean} [leaf=false]
56756      * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
56757      * rendered for this node.
56758      */
56759
56760     /**
56761      * @cfg {String} cls
56762      * CSS class to apply for this node.
56763      */
56764
56765     /**
56766      * @cfg {String} iconCls
56767      * CSS class to apply for this node's icon.
56768      */
56769
56770     /**
56771      * @cfg {String} icon
56772      * URL for this node's icon.
56773      */
56774
56775     /**
56776      * @cfg {Boolean} root
56777      * True if this is the root node.
56778      */
56779
56780     /**
56781      * @cfg {Boolean} isLast
56782      * True if this is the last node.
56783      */
56784
56785     /**
56786      * @cfg {Boolean} isFirst
56787      * True if this is the first node.
56788      */
56789
56790     /**
56791      * @cfg {Boolean} [allowDrop=true]
56792      * Set to false to deny dropping on this node.
56793      */
56794
56795     /**
56796      * @cfg {Boolean} [allowDrag=true]
56797      * Set to false to deny dragging of this node.
56798      */
56799
56800     /**
56801      * @cfg {Boolean} [loaded=false]
56802      * True if the node has finished loading.
56803      */
56804
56805     /**
56806      * @cfg {Boolean} [loading=false]
56807      * True if the node is currently loading.
56808      */
56809
56810     /**
56811      * @cfg {String} href
56812      * An URL for a link that's created when this config is specified.
56813      */
56814
56815     /**
56816      * @cfg {String} hrefTarget
56817      * Target for link. Only applicable when {@link #href} also specified.
56818      */
56819
56820     /**
56821      * @cfg {String} qtip
56822      * Tooltip text to show on this node.
56823      */
56824
56825     /**
56826      * @cfg {String} qtitle
56827      * Tooltip title.
56828      */
56829
56830     /**
56831      * @cfg {String} text
56832      * The text for to show on node label.
56833      */
56834
56835     /**
56836      * @cfg {Ext.data.NodeInterface[]} children
56837      * Array of child nodes.
56838      */
56839
56840
56841     /**
56842      * @property nextSibling
56843      * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
56844      */
56845
56846     /**
56847      * @property previousSibling
56848      * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
56849      */
56850
56851     /**
56852      * @property parentNode
56853      * A reference to this node's parent node. `null` if this node is the root node.
56854      */
56855
56856     /**
56857      * @property lastChild
56858      * A reference to this node's last child node. `null` if this node has no children.
56859      */
56860
56861     /**
56862      * @property firstChild
56863      * A reference to this node's first child node. `null` if this node has no children.
56864      */
56865
56866     /**
56867      * @property childNodes
56868      * An array of this nodes children.  Array will be empty if this node has no chidren.
56869      */
56870
56871     statics: {
56872         /**
56873          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
56874          * This adds a set of methods, new events, new properties and new fields on every Record
56875          * with the same Model as the passed Record.
56876          * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
56877          * @static
56878          */
56879         decorate: function(record) {
56880             if (!record.isNode) {
56881                 // Apply the methods and fields to the prototype
56882                 // @TODO: clean this up to use proper class system stuff
56883                 var mgr = Ext.ModelManager,
56884                     modelName = record.modelName,
56885                     modelClass = mgr.getModel(modelName),
56886                     idName = modelClass.prototype.idProperty,
56887                     newFields = [],
56888                     i, newField, len;
56889
56890                 // Start by adding the NodeInterface methods to the Model's prototype
56891                 modelClass.override(this.getPrototypeBody());
56892                 newFields = this.applyFields(modelClass, [
56893                     {name: idName,       type: 'string',  defaultValue: null},
56894                     {name: 'parentId',   type: 'string',  defaultValue: null},
56895                     {name: 'index',      type: 'int',     defaultValue: null},
56896                     {name: 'depth',      type: 'int',     defaultValue: 0},
56897                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
56898                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
56899                     {name: 'checked',    type: 'auto',    defaultValue: null},
56900                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
56901                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
56902                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
56903                     {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
56904                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
56905                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
56906                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
56907                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
56908                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
56909                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
56910                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
56911                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
56912                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
56913                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
56914                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
56915                 ]);
56916
56917                 len = newFields.length;
56918                 // Set default values
56919                 for (i = 0; i < len; ++i) {
56920                     newField = newFields[i];
56921                     if (record.get(newField.name) === undefined) {
56922                         record.data[newField.name] = newField.defaultValue;
56923                     }
56924                 }
56925             }
56926
56927             Ext.applyIf(record, {
56928                 firstChild: null,
56929                 lastChild: null,
56930                 parentNode: null,
56931                 previousSibling: null,
56932                 nextSibling: null,
56933                 childNodes: []
56934             });
56935             // Commit any fields so the record doesn't show as dirty initially
56936             record.commit(true);
56937
56938             record.enableBubble([
56939                 /**
56940                  * @event append
56941                  * Fires when a new child node is appended
56942                  * @param {Ext.data.NodeInterface} this This node
56943                  * @param {Ext.data.NodeInterface} node The newly appended node
56944                  * @param {Number} index The index of the newly appended node
56945                  */
56946                 "append",
56947
56948                 /**
56949                  * @event remove
56950                  * Fires when a child node is removed
56951                  * @param {Ext.data.NodeInterface} this This node
56952                  * @param {Ext.data.NodeInterface} node The removed node
56953                  */
56954                 "remove",
56955
56956                 /**
56957                  * @event move
56958                  * Fires when this node is moved to a new location in the tree
56959                  * @param {Ext.data.NodeInterface} this This node
56960                  * @param {Ext.data.NodeInterface} oldParent The old parent of this node
56961                  * @param {Ext.data.NodeInterface} newParent The new parent of this node
56962                  * @param {Number} index The index it was moved to
56963                  */
56964                 "move",
56965
56966                 /**
56967                  * @event insert
56968                  * Fires when a new child node is inserted.
56969                  * @param {Ext.data.NodeInterface} this This node
56970                  * @param {Ext.data.NodeInterface} node The child node inserted
56971                  * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
56972                  */
56973                 "insert",
56974
56975                 /**
56976                  * @event beforeappend
56977                  * Fires before a new child is appended, return false to cancel the append.
56978                  * @param {Ext.data.NodeInterface} this This node
56979                  * @param {Ext.data.NodeInterface} node The child node to be appended
56980                  */
56981                 "beforeappend",
56982
56983                 /**
56984                  * @event beforeremove
56985                  * Fires before a child is removed, return false to cancel the remove.
56986                  * @param {Ext.data.NodeInterface} this This node
56987                  * @param {Ext.data.NodeInterface} node The child node to be removed
56988                  */
56989                 "beforeremove",
56990
56991                 /**
56992                  * @event beforemove
56993                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
56994                  * @param {Ext.data.NodeInterface} this This node
56995                  * @param {Ext.data.NodeInterface} oldParent The parent of this node
56996                  * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
56997                  * @param {Number} index The index it is being moved to
56998                  */
56999                 "beforemove",
57000
57001                  /**
57002                   * @event beforeinsert
57003                   * Fires before a new child is inserted, return false to cancel the insert.
57004                   * @param {Ext.data.NodeInterface} this This node
57005                   * @param {Ext.data.NodeInterface} node The child node to be inserted
57006                   * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
57007                   */
57008                 "beforeinsert",
57009
57010                 /**
57011                  * @event expand
57012                  * Fires when this node is expanded.
57013                  * @param {Ext.data.NodeInterface} this The expanding node
57014                  */
57015                 "expand",
57016
57017                 /**
57018                  * @event collapse
57019                  * Fires when this node is collapsed.
57020                  * @param {Ext.data.NodeInterface} this The collapsing node
57021                  */
57022                 "collapse",
57023
57024                 /**
57025                  * @event beforeexpand
57026                  * Fires before this node is expanded.
57027                  * @param {Ext.data.NodeInterface} this The expanding node
57028                  */
57029                 "beforeexpand",
57030
57031                 /**
57032                  * @event beforecollapse
57033                  * Fires before this node is collapsed.
57034                  * @param {Ext.data.NodeInterface} this The collapsing node
57035                  */
57036                 "beforecollapse",
57037
57038                 /**
57039                  * @event sort
57040                  * Fires when this node's childNodes are sorted.
57041                  * @param {Ext.data.NodeInterface} this This node.
57042                  * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
57043                  */
57044                 "sort"
57045             ]);
57046
57047             return record;
57048         },
57049
57050         applyFields: function(modelClass, addFields) {
57051             var modelPrototype = modelClass.prototype,
57052                 fields = modelPrototype.fields,
57053                 keys = fields.keys,
57054                 ln = addFields.length,
57055                 addField, i, name,
57056                 newFields = [];
57057
57058             for (i = 0; i < ln; i++) {
57059                 addField = addFields[i];
57060                 if (!Ext.Array.contains(keys, addField.name)) {
57061                     addField = Ext.create('data.field', addField);
57062
57063                     newFields.push(addField);
57064                     fields.add(addField);
57065                 }
57066             }
57067
57068             return newFields;
57069         },
57070
57071         getPrototypeBody: function() {
57072             return {
57073                 isNode: true,
57074
57075                 /**
57076                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
57077                  * @return {Boolean}
57078                  */
57079                 createNode: function(node) {
57080                     if (Ext.isObject(node) && !node.isModel) {
57081                         node = Ext.ModelManager.create(node, this.modelName);
57082                     }
57083                     // Make sure the node implements the node interface
57084                     return Ext.data.NodeInterface.decorate(node);
57085                 },
57086
57087                 /**
57088                  * Returns true if this node is a leaf
57089                  * @return {Boolean}
57090                  */
57091                 isLeaf : function() {
57092                     return this.get('leaf') === true;
57093                 },
57094
57095                 /**
57096                  * Sets the first child of this node
57097                  * @private
57098                  * @param {Ext.data.NodeInterface} node
57099                  */
57100                 setFirstChild : function(node) {
57101                     this.firstChild = node;
57102                 },
57103
57104                 /**
57105                  * Sets the last child of this node
57106                  * @private
57107                  * @param {Ext.data.NodeInterface} node
57108                  */
57109                 setLastChild : function(node) {
57110                     this.lastChild = node;
57111                 },
57112
57113                 /**
57114                  * Updates general data of this node like isFirst, isLast, depth. This
57115                  * method is internally called after a node is moved. This shouldn't
57116                  * have to be called by the developer unless they are creating custom
57117                  * Tree plugins.
57118                  * @return {Boolean}
57119                  */
57120                 updateInfo: function(silent) {
57121                     var me = this,
57122                         isRoot = me.isRoot(),
57123                         parentNode = me.parentNode,
57124                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
57125                         isLast = (!parentNode ? true : parentNode.lastChild == me),
57126                         depth = 0,
57127                         parent = me,
57128                         children = me.childNodes,
57129                         len = children.length,
57130                         i = 0;
57131
57132                     while (parent.parentNode) {
57133                         ++depth;
57134                         parent = parent.parentNode;
57135                     }
57136
57137                     me.beginEdit();
57138                     me.set({
57139                         isFirst: isFirst,
57140                         isLast: isLast,
57141                         depth: depth,
57142                         index: parentNode ? parentNode.indexOf(me) : 0,
57143                         parentId: parentNode ? parentNode.getId() : null
57144                     });
57145                     me.endEdit(silent);
57146                     if (silent) {
57147                         me.commit();
57148                     }
57149
57150                     for (i = 0; i < len; i++) {
57151                         children[i].updateInfo(silent);
57152                     }
57153                 },
57154
57155                 /**
57156                  * Returns true if this node is the last child of its parent
57157                  * @return {Boolean}
57158                  */
57159                 isLast : function() {
57160                    return this.get('isLast');
57161                 },
57162
57163                 /**
57164                  * Returns true if this node is the first child of its parent
57165                  * @return {Boolean}
57166                  */
57167                 isFirst : function() {
57168                    return this.get('isFirst');
57169                 },
57170
57171                 /**
57172                  * Returns true if this node has one or more child nodes, else false.
57173                  * @return {Boolean}
57174                  */
57175                 hasChildNodes : function() {
57176                     return !this.isLeaf() && this.childNodes.length > 0;
57177                 },
57178
57179                 /**
57180                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
57181                  * node attribute is explicitly specified as true, otherwise returns false.
57182                  * @return {Boolean}
57183                  */
57184                 isExpandable : function() {
57185                     var me = this;
57186
57187                     if (me.get('expandable')) {
57188                         return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
57189                     }
57190                     return false;
57191                 },
57192
57193                 /**
57194                  * Inserts node(s) as the last child node of this node.
57195                  *
57196                  * If the node was previously a child node of another parent node, it will be removed from that node first.
57197                  *
57198                  * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
57199                  * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
57200                  */
57201                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
57202                     var me = this,
57203                         i, ln,
57204                         index,
57205                         oldParent,
57206                         ps;
57207
57208                     // if passed an array or multiple args do them one by one
57209                     if (Ext.isArray(node)) {
57210                         for (i = 0, ln = node.length; i < ln; i++) {
57211                             me.appendChild(node[i]);
57212                         }
57213                     } else {
57214                         // Make sure it is a record
57215                         node = me.createNode(node);
57216
57217                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
57218                             return false;
57219                         }
57220
57221                         index = me.childNodes.length;
57222                         oldParent = node.parentNode;
57223
57224                         // it's a move, make sure we move it cleanly
57225                         if (oldParent) {
57226                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
57227                                 return false;
57228                             }
57229                             oldParent.removeChild(node, null, false, true);
57230                         }
57231
57232                         index = me.childNodes.length;
57233                         if (index === 0) {
57234                             me.setFirstChild(node);
57235                         }
57236
57237                         me.childNodes.push(node);
57238                         node.parentNode = me;
57239                         node.nextSibling = null;
57240
57241                         me.setLastChild(node);
57242
57243                         ps = me.childNodes[index - 1];
57244                         if (ps) {
57245                             node.previousSibling = ps;
57246                             ps.nextSibling = node;
57247                             ps.updateInfo(suppressNodeUpdate);
57248                         } else {
57249                             node.previousSibling = null;
57250                         }
57251
57252                         node.updateInfo(suppressNodeUpdate);
57253
57254                         // As soon as we append a child to this node, we are loaded
57255                         if (!me.isLoaded()) {
57256                             me.set('loaded', true);
57257                         }
57258                         // If this node didnt have any childnodes before, update myself
57259                         else if (me.childNodes.length === 1) {
57260                             me.set('loaded', me.isLoaded());
57261                         }
57262
57263                         if (suppressEvents !== true) {
57264                             me.fireEvent("append", me, node, index);
57265
57266                             if (oldParent) {
57267                                 node.fireEvent("move", node, oldParent, me, index);
57268                             }
57269                         }
57270
57271                         return node;
57272                     }
57273                 },
57274
57275                 /**
57276                  * Returns the bubble target for this node
57277                  * @private
57278                  * @return {Object} The bubble target
57279                  */
57280                 getBubbleTarget: function() {
57281                     return this.parentNode;
57282                 },
57283
57284                 /**
57285                  * Removes a child node from this node.
57286                  * @param {Ext.data.NodeInterface} node The node to remove
57287                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
57288                  * @return {Ext.data.NodeInterface} The removed node
57289                  */
57290                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
57291                     var me = this,
57292                         index = me.indexOf(node);
57293
57294                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
57295                         return false;
57296                     }
57297
57298                     // remove it from childNodes collection
57299                     Ext.Array.erase(me.childNodes, index, 1);
57300
57301                     // update child refs
57302                     if (me.firstChild == node) {
57303                         me.setFirstChild(node.nextSibling);
57304                     }
57305                     if (me.lastChild == node) {
57306                         me.setLastChild(node.previousSibling);
57307                     }
57308
57309                     // update siblings
57310                     if (node.previousSibling) {
57311                         node.previousSibling.nextSibling = node.nextSibling;
57312                         node.previousSibling.updateInfo(suppressNodeUpdate);
57313                     }
57314                     if (node.nextSibling) {
57315                         node.nextSibling.previousSibling = node.previousSibling;
57316                         node.nextSibling.updateInfo(suppressNodeUpdate);
57317                     }
57318
57319                     if (suppressEvents !== true) {
57320                         me.fireEvent("remove", me, node);
57321                     }
57322
57323
57324                     // If this node suddenly doesnt have childnodes anymore, update myself
57325                     if (!me.childNodes.length) {
57326                         me.set('loaded', me.isLoaded());
57327                     }
57328
57329                     if (destroy) {
57330                         node.destroy(true);
57331                     } else {
57332                         node.clear();
57333                     }
57334
57335                     return node;
57336                 },
57337
57338                 /**
57339                  * Creates a copy (clone) of this Node.
57340                  * @param {String} [id] A new id, defaults to this Node's id.
57341                  * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
57342                  * False to copy without child Nodes.
57343                  * @return {Ext.data.NodeInterface} A copy of this Node.
57344                  */
57345                 copy: function(newId, deep) {
57346                     var me = this,
57347                         result = me.callOverridden(arguments),
57348                         len = me.childNodes ? me.childNodes.length : 0,
57349                         i;
57350
57351                     // Move child nodes across to the copy if required
57352                     if (deep) {
57353                         for (i = 0; i < len; i++) {
57354                             result.appendChild(me.childNodes[i].copy(true));
57355                         }
57356                     }
57357                     return result;
57358                 },
57359
57360                 /**
57361                  * Clears the node.
57362                  * @private
57363                  * @param {Boolean} [destroy=false] True to destroy the node.
57364                  */
57365                 clear : function(destroy) {
57366                     var me = this;
57367
57368                     // clear any references from the node
57369                     me.parentNode = me.previousSibling = me.nextSibling = null;
57370                     if (destroy) {
57371                         me.firstChild = me.lastChild = null;
57372                     }
57373                 },
57374
57375                 /**
57376                  * Destroys the node.
57377                  */
57378                 destroy : function(silent) {
57379                     /*
57380                      * Silent is to be used in a number of cases
57381                      * 1) When setRoot is called.
57382                      * 2) When destroy on the tree is called
57383                      * 3) For destroying child nodes on a node
57384                      */
57385                     var me = this,
57386                         options = me.destroyOptions;
57387
57388                     if (silent === true) {
57389                         me.clear(true);
57390                         Ext.each(me.childNodes, function(n) {
57391                             n.destroy(true);
57392                         });
57393                         me.childNodes = null;
57394                         delete me.destroyOptions;
57395                         me.callOverridden([options]);
57396                     } else {
57397                         me.destroyOptions = silent;
57398                         // overridden method will be called, since remove will end up calling destroy(true);
57399                         me.remove(true);
57400                     }
57401                 },
57402
57403                 /**
57404                  * Inserts the first node before the second node in this nodes childNodes collection.
57405                  * @param {Ext.data.NodeInterface} node The node to insert
57406                  * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
57407                  * @return {Ext.data.NodeInterface} The inserted node
57408                  */
57409                 insertBefore : function(node, refNode, suppressEvents) {
57410                     var me = this,
57411                         index     = me.indexOf(refNode),
57412                         oldParent = node.parentNode,
57413                         refIndex  = index,
57414                         ps;
57415
57416                     if (!refNode) { // like standard Dom, refNode can be null for append
57417                         return me.appendChild(node);
57418                     }
57419
57420                     // nothing to do
57421                     if (node == refNode) {
57422                         return false;
57423                     }
57424
57425                     // Make sure it is a record with the NodeInterface
57426                     node = me.createNode(node);
57427
57428                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
57429                         return false;
57430                     }
57431
57432                     // when moving internally, indexes will change after remove
57433                     if (oldParent == me && me.indexOf(node) < index) {
57434                         refIndex--;
57435                     }
57436
57437                     // it's a move, make sure we move it cleanly
57438                     if (oldParent) {
57439                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
57440                             return false;
57441                         }
57442                         oldParent.removeChild(node);
57443                     }
57444
57445                     if (refIndex === 0) {
57446                         me.setFirstChild(node);
57447                     }
57448
57449                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
57450                     node.parentNode = me;
57451
57452                     node.nextSibling = refNode;
57453                     refNode.previousSibling = node;
57454
57455                     ps = me.childNodes[refIndex - 1];
57456                     if (ps) {
57457                         node.previousSibling = ps;
57458                         ps.nextSibling = node;
57459                         ps.updateInfo();
57460                     } else {
57461                         node.previousSibling = null;
57462                     }
57463
57464                     node.updateInfo();
57465
57466                     if (!me.isLoaded()) {
57467                         me.set('loaded', true);
57468                     }
57469                     // If this node didnt have any childnodes before, update myself
57470                     else if (me.childNodes.length === 1) {
57471                         me.set('loaded', me.isLoaded());
57472                     }
57473
57474                     if (suppressEvents !== true) {
57475                         me.fireEvent("insert", me, node, refNode);
57476
57477                         if (oldParent) {
57478                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
57479                         }
57480                     }
57481
57482                     return node;
57483                 },
57484
57485                 /**
57486                  * Insert a node into this node
57487                  * @param {Number} index The zero-based index to insert the node at
57488                  * @param {Ext.data.Model} node The node to insert
57489                  * @return {Ext.data.Model} The record you just inserted
57490                  */
57491                 insertChild: function(index, node) {
57492                     var sibling = this.childNodes[index];
57493                     if (sibling) {
57494                         return this.insertBefore(node, sibling);
57495                     }
57496                     else {
57497                         return this.appendChild(node);
57498                     }
57499                 },
57500
57501                 /**
57502                  * Removes this node from its parent
57503                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
57504                  * @return {Ext.data.NodeInterface} this
57505                  */
57506                 remove : function(destroy, suppressEvents) {
57507                     var parentNode = this.parentNode;
57508
57509                     if (parentNode) {
57510                         parentNode.removeChild(this, destroy, suppressEvents, true);
57511                     }
57512                     return this;
57513                 },
57514
57515                 /**
57516                  * Removes all child nodes from this node.
57517                  * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
57518                  * @return {Ext.data.NodeInterface} this
57519                  */
57520                 removeAll : function(destroy, suppressEvents) {
57521                     var cn = this.childNodes,
57522                         n;
57523
57524                     while ((n = cn[0])) {
57525                         this.removeChild(n, destroy, suppressEvents);
57526                     }
57527                     return this;
57528                 },
57529
57530                 /**
57531                  * Returns the child node at the specified index.
57532                  * @param {Number} index
57533                  * @return {Ext.data.NodeInterface}
57534                  */
57535                 getChildAt : function(index) {
57536                     return this.childNodes[index];
57537                 },
57538
57539                 /**
57540                  * Replaces one child node in this node with another.
57541                  * @param {Ext.data.NodeInterface} newChild The replacement node
57542                  * @param {Ext.data.NodeInterface} oldChild The node to replace
57543                  * @return {Ext.data.NodeInterface} The replaced node
57544                  */
57545                 replaceChild : function(newChild, oldChild, suppressEvents) {
57546                     var s = oldChild ? oldChild.nextSibling : null;
57547
57548                     this.removeChild(oldChild, suppressEvents);
57549                     this.insertBefore(newChild, s, suppressEvents);
57550                     return oldChild;
57551                 },
57552
57553                 /**
57554                  * Returns the index of a child node
57555                  * @param {Ext.data.NodeInterface} node
57556                  * @return {Number} The index of the node or -1 if it was not found
57557                  */
57558                 indexOf : function(child) {
57559                     return Ext.Array.indexOf(this.childNodes, child);
57560                 },
57561
57562                 /**
57563                  * Gets the hierarchical path from the root of the current node.
57564                  * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
57565                  * @param {String} [separator="/"] A separator to use.
57566                  * @return {String} The node path
57567                  */
57568                 getPath: function(field, separator) {
57569                     field = field || this.idProperty;
57570                     separator = separator || '/';
57571
57572                     var path = [this.get(field)],
57573                         parent = this.parentNode;
57574
57575                     while (parent) {
57576                         path.unshift(parent.get(field));
57577                         parent = parent.parentNode;
57578                     }
57579                     return separator + path.join(separator);
57580                 },
57581
57582                 /**
57583                  * Returns depth of this node (the root node has a depth of 0)
57584                  * @return {Number}
57585                  */
57586                 getDepth : function() {
57587                     return this.get('depth');
57588                 },
57589
57590                 /**
57591                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
57592                  * will be the args provided or the current node. If the function returns false at any point,
57593                  * the bubble is stopped.
57594                  * @param {Function} fn The function to call
57595                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
57596                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
57597                  */
57598                 bubble : function(fn, scope, args) {
57599                     var p = this;
57600                     while (p) {
57601                         if (fn.apply(scope || p, args || [p]) === false) {
57602                             break;
57603                         }
57604                         p = p.parentNode;
57605                     }
57606                 },
57607
57608                 //<deprecated since=0.99>
57609                 cascade: function() {
57610                     if (Ext.isDefined(Ext.global.console)) {
57611                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
57612                     }
57613                     return this.cascadeBy.apply(this, arguments);
57614                 },
57615                 //</deprecated>
57616
57617                 /**
57618                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
57619                  * will be the args provided or the current node. If the function returns false at any point,
57620                  * the cascade is stopped on that branch.
57621                  * @param {Function} fn The function to call
57622                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
57623                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
57624                  */
57625                 cascadeBy : function(fn, scope, args) {
57626                     if (fn.apply(scope || this, args || [this]) !== false) {
57627                         var childNodes = this.childNodes,
57628                             length     = childNodes.length,
57629                             i;
57630
57631                         for (i = 0; i < length; i++) {
57632                             childNodes[i].cascadeBy(fn, scope, args);
57633                         }
57634                     }
57635                 },
57636
57637                 /**
57638                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
57639                  * will be the args provided or the current node. If the function returns false at any point,
57640                  * the iteration stops.
57641                  * @param {Function} fn The function to call
57642                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
57643                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
57644                  */
57645                 eachChild : function(fn, scope, args) {
57646                     var childNodes = this.childNodes,
57647                         length     = childNodes.length,
57648                         i;
57649
57650                     for (i = 0; i < length; i++) {
57651                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
57652                             break;
57653                         }
57654                     }
57655                 },
57656
57657                 /**
57658                  * Finds the first child that has the attribute with the specified value.
57659                  * @param {String} attribute The attribute name
57660                  * @param {Object} value The value to search for
57661                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
57662                  * @return {Ext.data.NodeInterface} The found child or null if none was found
57663                  */
57664                 findChild : function(attribute, value, deep) {
57665                     return this.findChildBy(function() {
57666                         return this.get(attribute) == value;
57667                     }, null, deep);
57668                 },
57669
57670                 /**
57671                  * Finds the first child by a custom function. The child matches if the function passed returns true.
57672                  * @param {Function} fn A function which must return true if the passed Node is the required Node.
57673                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
57674                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
57675                  * @return {Ext.data.NodeInterface} The found child or null if none was found
57676                  */
57677                 findChildBy : function(fn, scope, deep) {
57678                     var cs = this.childNodes,
57679                         len = cs.length,
57680                         i = 0, n, res;
57681
57682                     for (; i < len; i++) {
57683                         n = cs[i];
57684                         if (fn.call(scope || n, n) === true) {
57685                             return n;
57686                         }
57687                         else if (deep) {
57688                             res = n.findChildBy(fn, scope, deep);
57689                             if (res !== null) {
57690                                 return res;
57691                             }
57692                         }
57693                     }
57694
57695                     return null;
57696                 },
57697
57698                 /**
57699                  * Returns true if this node is an ancestor (at any point) of the passed node.
57700                  * @param {Ext.data.NodeInterface} node
57701                  * @return {Boolean}
57702                  */
57703                 contains : function(node) {
57704                     return node.isAncestor(this);
57705                 },
57706
57707                 /**
57708                  * Returns true if the passed node is an ancestor (at any point) of this node.
57709                  * @param {Ext.data.NodeInterface} node
57710                  * @return {Boolean}
57711                  */
57712                 isAncestor : function(node) {
57713                     var p = this.parentNode;
57714                     while (p) {
57715                         if (p == node) {
57716                             return true;
57717                         }
57718                         p = p.parentNode;
57719                     }
57720                     return false;
57721                 },
57722
57723                 /**
57724                  * Sorts this nodes children using the supplied sort function.
57725                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
57726                  * @param {Boolean} [recursive=false] True to apply this sort recursively
57727                  * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
57728                  */
57729                 sort : function(sortFn, recursive, suppressEvent) {
57730                     var cs  = this.childNodes,
57731                         ln = cs.length,
57732                         i, n;
57733
57734                     if (ln > 0) {
57735                         Ext.Array.sort(cs, sortFn);
57736                         for (i = 0; i < ln; i++) {
57737                             n = cs[i];
57738                             n.previousSibling = cs[i-1];
57739                             n.nextSibling = cs[i+1];
57740
57741                             if (i === 0) {
57742                                 this.setFirstChild(n);
57743                                 n.updateInfo();
57744                             }
57745                             if (i == ln - 1) {
57746                                 this.setLastChild(n);
57747                                 n.updateInfo();
57748                             }
57749                             if (recursive && !n.isLeaf()) {
57750                                 n.sort(sortFn, true, true);
57751                             }
57752                         }
57753
57754                         if (suppressEvent !== true) {
57755                             this.fireEvent('sort', this, cs);
57756                         }
57757                     }
57758                 },
57759
57760                 /**
57761                  * Returns true if this node is expaned
57762                  * @return {Boolean}
57763                  */
57764                 isExpanded: function() {
57765                     return this.get('expanded');
57766                 },
57767
57768                 /**
57769                  * Returns true if this node is loaded
57770                  * @return {Boolean}
57771                  */
57772                 isLoaded: function() {
57773                     return this.get('loaded');
57774                 },
57775
57776                 /**
57777                  * Returns true if this node is loading
57778                  * @return {Boolean}
57779                  */
57780                 isLoading: function() {
57781                     return this.get('loading');
57782                 },
57783
57784                 /**
57785                  * Returns true if this node is the root node
57786                  * @return {Boolean}
57787                  */
57788                 isRoot: function() {
57789                     return !this.parentNode;
57790                 },
57791
57792                 /**
57793                  * Returns true if this node is visible
57794                  * @return {Boolean}
57795                  */
57796                 isVisible: function() {
57797                     var parent = this.parentNode;
57798                     while (parent) {
57799                         if (!parent.isExpanded()) {
57800                             return false;
57801                         }
57802                         parent = parent.parentNode;
57803                     }
57804                     return true;
57805                 },
57806
57807                 /**
57808                  * Expand this node.
57809                  * @param {Boolean} [recursive=false] True to recursively expand all the children
57810                  * @param {Function} [callback] The function to execute once the expand completes
57811                  * @param {Object} [scope] The scope to run the callback in
57812                  */
57813                 expand: function(recursive, callback, scope) {
57814                     var me = this;
57815
57816                     // all paths must call the callback (eventually) or things like
57817                     // selectPath fail
57818
57819                     // First we start by checking if this node is a parent
57820                     if (!me.isLeaf()) {
57821                         // If it's loaded, wait until it loads before proceeding
57822                         if (me.isLoading()) {
57823                             me.on('expand', function(){
57824                                 me.expand(recursive, callback, scope);
57825                             }, me, {single: true});
57826                         } else {
57827                             // Now we check if this record is already expanding or expanded
57828                             if (!me.isExpanded()) {
57829                                 // The TreeStore actually listens for the beforeexpand method and checks
57830                                 // whether we have to asynchronously load the children from the server
57831                                 // first. Thats why we pass a callback function to the event that the
57832                                 // store can call once it has loaded and parsed all the children.
57833                                 me.fireEvent('beforeexpand', me, function(){
57834                                     me.set('expanded', true);
57835                                     me.fireEvent('expand', me, me.childNodes, false);
57836
57837                                     // Call the expandChildren method if recursive was set to true
57838                                     if (recursive) {
57839                                         me.expandChildren(true, callback, scope);
57840                                     } else {
57841                                         Ext.callback(callback, scope || me, [me.childNodes]);
57842                                     }
57843                                 }, me);
57844                             } else if (recursive) {
57845                                 // If it is is already expanded but we want to recursively expand then call expandChildren
57846                                 me.expandChildren(true, callback, scope);
57847                             } else {
57848                                 Ext.callback(callback, scope || me, [me.childNodes]);
57849                             }
57850                         }
57851                     } else {
57852                         // If it's not then we fire the callback right away
57853                         Ext.callback(callback, scope || me); // leaf = no childNodes
57854                     }
57855                 },
57856
57857                 /**
57858                  * Expand all the children of this node.
57859                  * @param {Boolean} [recursive=false] True to recursively expand all the children
57860                  * @param {Function} [callback] The function to execute once all the children are expanded
57861                  * @param {Object} [scope] The scope to run the callback in
57862                  */
57863                 expandChildren: function(recursive, callback, scope) {
57864                     var me = this,
57865                         i = 0,
57866                         nodes = me.childNodes,
57867                         ln = nodes.length,
57868                         node,
57869                         expanding = 0;
57870
57871                     for (; i < ln; ++i) {
57872                         node = nodes[i];
57873                         if (!node.isLeaf() && !node.isExpanded()) {
57874                             expanding++;
57875                             nodes[i].expand(recursive, function () {
57876                                 expanding--;
57877                                 if (callback && !expanding) {
57878                                     Ext.callback(callback, scope || me, [me.childNodes]);
57879                                 }
57880                             });
57881                         }
57882                     }
57883
57884                     if (!expanding && callback) {
57885                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
57886                 },
57887
57888                 /**
57889                  * Collapse this node.
57890                  * @param {Boolean} [recursive=false] True to recursively collapse all the children
57891                  * @param {Function} [callback] The function to execute once the collapse completes
57892                  * @param {Object} [scope] The scope to run the callback in
57893                  */
57894                 collapse: function(recursive, callback, scope) {
57895                     var me = this;
57896
57897                     // First we start by checking if this node is a parent
57898                     if (!me.isLeaf()) {
57899                         // Now we check if this record is already collapsing or collapsed
57900                         if (!me.collapsing && me.isExpanded()) {
57901                             me.fireEvent('beforecollapse', me, function() {
57902                                 me.set('expanded', false);
57903                                 me.fireEvent('collapse', me, me.childNodes, false);
57904
57905                                 // Call the collapseChildren method if recursive was set to true
57906                                 if (recursive) {
57907                                     me.collapseChildren(true, callback, scope);
57908                                 }
57909                                 else {
57910                                     Ext.callback(callback, scope || me, [me.childNodes]);
57911                                 }
57912                             }, me);
57913                         }
57914                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
57915                         else if (recursive) {
57916                             me.collapseChildren(true, callback, scope);
57917                         }
57918                     }
57919                     // If it's not then we fire the callback right away
57920                     else {
57921                         Ext.callback(callback, scope || me, [me.childNodes]);
57922                     }
57923                 },
57924
57925                 /**
57926                  * Collapse all the children of this node.
57927                  * @param {Function} [recursive=false] True to recursively collapse all the children
57928                  * @param {Function} [callback] The function to execute once all the children are collapsed
57929                  * @param {Object} [scope] The scope to run the callback in
57930                  */
57931                 collapseChildren: function(recursive, callback, scope) {
57932                     var me = this,
57933                         i = 0,
57934                         nodes = me.childNodes,
57935                         ln = nodes.length,
57936                         node,
57937                         collapsing = 0;
57938
57939                     for (; i < ln; ++i) {
57940                         node = nodes[i];
57941                         if (!node.isLeaf() && node.isExpanded()) {
57942                             collapsing++;
57943                             nodes[i].collapse(recursive, function () {
57944                                 collapsing--;
57945                                 if (callback && !collapsing) {
57946                                     Ext.callback(callback, scope || me, [me.childNodes]);
57947                                 }
57948                             });
57949                         }
57950                     }
57951
57952                     if (!collapsing && callback) {
57953                         Ext.callback(callback, scope || me, [me.childNodes]);
57954                     }
57955                 }
57956             };
57957         }
57958     }
57959 });
57960 /**
57961  * @class Ext.data.NodeStore
57962  * @extends Ext.data.AbstractStore
57963  * Node Store
57964  * @ignore
57965  */
57966 Ext.define('Ext.data.NodeStore', {
57967     extend: 'Ext.data.Store',
57968     alias: 'store.node',
57969     requires: ['Ext.data.NodeInterface'],
57970     
57971     /**
57972      * @cfg {Ext.data.Model} node
57973      * The Record you want to bind this Store to. Note that
57974      * this record will be decorated with the Ext.data.NodeInterface if this is not the
57975      * case yet.
57976      */
57977     node: null,
57978     
57979     /**
57980      * @cfg {Boolean} recursive
57981      * Set this to true if you want this NodeStore to represent
57982      * all the descendents of the node in its flat data collection. This is useful for
57983      * rendering a tree structure to a DataView and is being used internally by
57984      * the TreeView. Any records that are moved, removed, inserted or appended to the
57985      * node at any depth below the node this store is bound to will be automatically
57986      * updated in this Store's internal flat data structure.
57987      */
57988     recursive: false,
57989     
57990     /** 
57991      * @cfg {Boolean} rootVisible
57992      * False to not include the root node in this Stores collection.
57993      */    
57994     rootVisible: false,
57995     
57996     constructor: function(config) {
57997         var me = this,
57998             node;
57999             
58000         config = config || {};
58001         Ext.apply(me, config);
58002         
58003         //<debug>
58004         if (Ext.isDefined(me.proxy)) {
58005             Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
58006                             "decorated with the NodeInterface by setting the node config.");
58007         }
58008         //</debug>
58009
58010         config.proxy = {type: 'proxy'};
58011         me.callParent([config]);
58012
58013         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
58014         
58015         node = me.node;
58016         if (node) {
58017             me.node = null;
58018             me.setNode(node);
58019         }
58020     },
58021     
58022     setNode: function(node) {
58023         var me = this;
58024         
58025         if (me.node && me.node != node) {
58026             // We want to unbind our listeners on the old node
58027             me.mun(me.node, {
58028                 expand: me.onNodeExpand,
58029                 collapse: me.onNodeCollapse,
58030                 append: me.onNodeAppend,
58031                 insert: me.onNodeInsert,
58032                 remove: me.onNodeRemove,
58033                 sort: me.onNodeSort,
58034                 scope: me
58035             });
58036             me.node = null;
58037         }
58038         
58039         if (node) {
58040             Ext.data.NodeInterface.decorate(node);
58041             me.removeAll();
58042             if (me.rootVisible) {
58043                 me.add(node);
58044             }
58045             me.mon(node, {
58046                 expand: me.onNodeExpand,
58047                 collapse: me.onNodeCollapse,
58048                 append: me.onNodeAppend,
58049                 insert: me.onNodeInsert,
58050                 remove: me.onNodeRemove,
58051                 sort: me.onNodeSort,
58052                 scope: me
58053             });
58054             me.node = node;
58055             if (node.isExpanded() && node.isLoaded()) {
58056                 me.onNodeExpand(node, node.childNodes, true);
58057             }
58058         }
58059     },
58060     
58061     onNodeSort: function(node, childNodes) {
58062         var me = this;
58063         
58064         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
58065             me.onNodeCollapse(node, childNodes, true);
58066             me.onNodeExpand(node, childNodes, true);
58067         }
58068     },
58069     
58070     onNodeExpand: function(parent, records, suppressEvent) {
58071         var me = this,
58072             insertIndex = me.indexOf(parent) + 1,
58073             ln = records ? records.length : 0,
58074             i, record;
58075             
58076         if (!me.recursive && parent !== me.node) {
58077             return;
58078         }
58079         
58080         if (!me.isVisible(parent)) {
58081             return;
58082         }
58083
58084         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
58085             return;
58086         }
58087         
58088         if (ln) {
58089             me.insert(insertIndex, records);
58090             for (i = 0; i < ln; i++) {
58091                 record = records[i];
58092                 if (record.isExpanded()) {
58093                     if (record.isLoaded()) {
58094                         // Take a shortcut                        
58095                         me.onNodeExpand(record, record.childNodes, true);
58096                     }
58097                     else {
58098                         record.set('expanded', false);
58099                         record.expand();
58100                     }
58101                 }
58102             }
58103         }
58104
58105         if (!suppressEvent) {
58106             me.fireEvent('expand', parent, records);
58107         }
58108     },
58109
58110     onNodeCollapse: function(parent, records, suppressEvent) {
58111         var me = this,
58112             ln = records.length,
58113             collapseIndex = me.indexOf(parent) + 1,
58114             i, record;
58115             
58116         if (!me.recursive && parent !== me.node) {
58117             return;
58118         }
58119         
58120         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
58121             return;
58122         }
58123
58124         for (i = 0; i < ln; i++) {
58125             record = records[i];
58126             me.remove(record);
58127             if (record.isExpanded()) {
58128                 me.onNodeCollapse(record, record.childNodes, true);
58129             }
58130         }
58131         
58132         if (!suppressEvent) {
58133             me.fireEvent('collapse', parent, records, collapseIndex);
58134         }
58135     },
58136     
58137     onNodeAppend: function(parent, node, index) {
58138         var me = this,
58139             refNode, sibling;
58140
58141         if (me.isVisible(node)) {
58142             if (index === 0) {
58143                 refNode = parent;
58144             } else {
58145                 sibling = node.previousSibling;
58146                 while (sibling.isExpanded() && sibling.lastChild) {
58147                     sibling = sibling.lastChild;
58148                 }
58149                 refNode = sibling;
58150             }
58151             me.insert(me.indexOf(refNode) + 1, node);
58152             if (!node.isLeaf() && node.isExpanded()) {
58153                 if (node.isLoaded()) {
58154                     // Take a shortcut                        
58155                     me.onNodeExpand(node, node.childNodes, true);
58156                 }
58157                 else {
58158                     node.set('expanded', false);
58159                     node.expand();
58160                 }
58161             }
58162         } 
58163     },
58164     
58165     onNodeInsert: function(parent, node, refNode) {
58166         var me = this,
58167             index = this.indexOf(refNode);
58168             
58169         if (index != -1 && me.isVisible(node)) {
58170             me.insert(index, node);
58171             if (!node.isLeaf() && node.isExpanded()) {
58172                 if (node.isLoaded()) {
58173                     // Take a shortcut                        
58174                     me.onNodeExpand(node, node.childNodes, true);
58175                 }
58176                 else {
58177                     node.set('expanded', false);
58178                     node.expand();
58179                 }
58180             }
58181         }
58182     },
58183     
58184     onNodeRemove: function(parent, node, index) {
58185         var me = this;
58186         if (me.indexOf(node) != -1) {
58187             if (!node.isLeaf() && node.isExpanded()) {
58188                 me.onNodeCollapse(node, node.childNodes, true);
58189             }            
58190             me.remove(node);
58191         }
58192     },
58193     
58194     isVisible: function(node) {
58195         var parent = node.parentNode;
58196         while (parent) {
58197             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
58198                 return true;
58199             }
58200             
58201             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
58202                 return false;
58203             }
58204             
58205             parent = parent.parentNode;
58206         }
58207         return true;
58208     }
58209 });
58210 /**
58211  * @author Ed Spencer
58212  * 
58213  * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
58214  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
58215  * it does not contain any actual logic or perform the request itself.
58216  */
58217 Ext.define('Ext.data.Request', {
58218     /**
58219      * @cfg {String} action
58220      * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
58221      */
58222     action: undefined,
58223     
58224     /**
58225      * @cfg {Object} params
58226      * HTTP request params. The Proxy and its Writer have access to and can modify this object.
58227      */
58228     params: undefined,
58229     
58230     /**
58231      * @cfg {String} method
58232      * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
58233      */
58234     method: 'GET',
58235     
58236     /**
58237      * @cfg {String} url
58238      * The url to access on this Request
58239      */
58240     url: undefined,
58241
58242     /**
58243      * Creates the Request object.
58244      * @param {Object} [config] Config object.
58245      */
58246     constructor: function(config) {
58247         Ext.apply(this, config);
58248     }
58249 });
58250 /**
58251  * @author Don Griffin
58252  *
58253  * This class is a sequential id generator. A simple use of this class would be like so:
58254  *
58255  *     Ext.define('MyApp.data.MyModel', {
58256  *         extend: 'Ext.data.Model',
58257  *         idgen: 'sequential'
58258  *     });
58259  *     // assign id's of 1, 2, 3, etc.
58260  *
58261  * An example of a configured generator would be:
58262  *
58263  *     Ext.define('MyApp.data.MyModel', {
58264  *         extend: 'Ext.data.Model',
58265  *         idgen: {
58266  *             type: 'sequential',
58267  *             prefix: 'ID_',
58268  *             seed: 1000
58269  *         }
58270  *     });
58271  *     // assign id's of ID_1000, ID_1001, ID_1002, etc.
58272  *
58273  */
58274 Ext.define('Ext.data.SequentialIdGenerator', {
58275     extend: 'Ext.data.IdGenerator',
58276     alias: 'idgen.sequential',
58277
58278     constructor: function() {
58279         var me = this;
58280
58281         me.callParent(arguments);
58282
58283         me.parts = [ me.prefix, ''];
58284     },
58285
58286     /**
58287      * @cfg {String} prefix
58288      * The string to place in front of the sequential number for each generated id. The
58289      * default is blank.
58290      */
58291     prefix: '',
58292
58293     /**
58294      * @cfg {Number} seed
58295      * The number at which to start generating sequential id's. The default is 1.
58296      */
58297     seed: 1,
58298
58299     /**
58300      * Generates and returns the next id.
58301      * @return {String} The next id.
58302      */
58303     generate: function () {
58304         var me = this,
58305             parts = me.parts;
58306
58307         parts[1] = me.seed++;
58308         return parts.join('');
58309     }
58310 });
58311
58312 /**
58313  * @class Ext.data.Tree
58314  *
58315  * This class is used as a container for a series of nodes. The nodes themselves maintain
58316  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
58317  * to retrieve a node by its identifier: {@link #getNodeById}.
58318  *
58319  * The tree also relays events from any of it's child nodes, allowing them to be handled in a
58320  * centralized fashion. In general this class is not used directly, rather used internally
58321  * by other parts of the framework.
58322  *
58323  */
58324 Ext.define('Ext.data.Tree', {
58325     alias: 'data.tree',
58326
58327     mixins: {
58328         observable: "Ext.util.Observable"
58329     },
58330
58331     /**
58332      * @property {Ext.data.NodeInterface}
58333      * The root node for this tree
58334      */
58335     root: null,
58336
58337     /**
58338      * Creates new Tree object.
58339      * @param {Ext.data.NodeInterface} root (optional) The root node
58340      */
58341     constructor: function(root) {
58342         var me = this;
58343
58344         
58345
58346         me.mixins.observable.constructor.call(me);
58347
58348         if (root) {
58349             me.setRootNode(root);
58350         }
58351     },
58352
58353     /**
58354      * Returns the root node for this tree.
58355      * @return {Ext.data.NodeInterface}
58356      */
58357     getRootNode : function() {
58358         return this.root;
58359     },
58360
58361     /**
58362      * Sets the root node for this tree.
58363      * @param {Ext.data.NodeInterface} node
58364      * @return {Ext.data.NodeInterface} The root node
58365      */
58366     setRootNode : function(node) {
58367         var me = this;
58368
58369         me.root = node;
58370         Ext.data.NodeInterface.decorate(node);
58371
58372         if (me.fireEvent('beforeappend', null, node) !== false) {
58373             node.set('root', true);
58374             node.updateInfo();
58375
58376             me.relayEvents(node, [
58377                 /**
58378                  * @event append
58379                  * @alias Ext.data.NodeInterface#append
58380                  */
58381                 "append",
58382
58383                 /**
58384                  * @event remove
58385                  * @alias Ext.data.NodeInterface#remove
58386                  */
58387                 "remove",
58388
58389                 /**
58390                  * @event move
58391                  * @alias Ext.data.NodeInterface#move
58392                  */
58393                 "move",
58394
58395                 /**
58396                  * @event insert
58397                  * @alias Ext.data.NodeInterface#insert
58398                  */
58399                 "insert",
58400
58401                 /**
58402                  * @event beforeappend
58403                  * @alias Ext.data.NodeInterface#beforeappend
58404                  */
58405                 "beforeappend",
58406
58407                 /**
58408                  * @event beforeremove
58409                  * @alias Ext.data.NodeInterface#beforeremove
58410                  */
58411                 "beforeremove",
58412
58413                 /**
58414                  * @event beforemove
58415                  * @alias Ext.data.NodeInterface#beforemove
58416                  */
58417                 "beforemove",
58418
58419                 /**
58420                  * @event beforeinsert
58421                  * @alias Ext.data.NodeInterface#beforeinsert
58422                  */
58423                 "beforeinsert",
58424
58425                  /**
58426                   * @event expand
58427                   * @alias Ext.data.NodeInterface#expand
58428                   */
58429                  "expand",
58430
58431                  /**
58432                   * @event collapse
58433                   * @alias Ext.data.NodeInterface#collapse
58434                   */
58435                  "collapse",
58436
58437                  /**
58438                   * @event beforeexpand
58439                   * @alias Ext.data.NodeInterface#beforeexpand
58440                   */
58441                  "beforeexpand",
58442
58443                  /**
58444                   * @event beforecollapse
58445                   * @alias Ext.data.NodeInterface#beforecollapse
58446                   */
58447                  "beforecollapse" ,
58448
58449                  /**
58450                   * @event rootchange
58451                   * Fires whenever the root node is changed in the tree.
58452                   * @param {Ext.data.Model} root The new root
58453                   */
58454                  "rootchange"
58455             ]);
58456
58457             node.on({
58458                 scope: me,
58459                 insert: me.onNodeInsert,
58460                 append: me.onNodeAppend,
58461                 remove: me.onNodeRemove
58462             });
58463
58464             me.nodeHash = {};
58465             me.registerNode(node);
58466             me.fireEvent('append', null, node);
58467             me.fireEvent('rootchange', node);
58468         }
58469
58470         return node;
58471     },
58472
58473     /**
58474      * Flattens all the nodes in the tree into an array.
58475      * @private
58476      * @return {Ext.data.NodeInterface[]} The flattened nodes.
58477      */
58478     flatten: function(){
58479         var nodes = [],
58480             hash = this.nodeHash,
58481             key;
58482
58483         for (key in hash) {
58484             if (hash.hasOwnProperty(key)) {
58485                 nodes.push(hash[key]);
58486             }
58487         }
58488         return nodes;
58489     },
58490
58491     /**
58492      * Fired when a node is inserted into the root or one of it's children
58493      * @private
58494      * @param {Ext.data.NodeInterface} parent The parent node
58495      * @param {Ext.data.NodeInterface} node The inserted node
58496      */
58497     onNodeInsert: function(parent, node) {
58498         this.registerNode(node, true);
58499     },
58500
58501     /**
58502      * Fired when a node is appended into the root or one of it's children
58503      * @private
58504      * @param {Ext.data.NodeInterface} parent The parent node
58505      * @param {Ext.data.NodeInterface} node The appended node
58506      */
58507     onNodeAppend: function(parent, node) {
58508         this.registerNode(node, true);
58509     },
58510
58511     /**
58512      * Fired when a node is removed from the root or one of it's children
58513      * @private
58514      * @param {Ext.data.NodeInterface} parent The parent node
58515      * @param {Ext.data.NodeInterface} node The removed node
58516      */
58517     onNodeRemove: function(parent, node) {
58518         this.unregisterNode(node, true);
58519     },
58520
58521     /**
58522      * Gets a node in this tree by its id.
58523      * @param {String} id
58524      * @return {Ext.data.NodeInterface} The match node.
58525      */
58526     getNodeById : function(id) {
58527         return this.nodeHash[id];
58528     },
58529
58530     /**
58531      * Registers a node with the tree
58532      * @private
58533      * @param {Ext.data.NodeInterface} The node to register
58534      * @param {Boolean} [includeChildren] True to unregister any child nodes
58535      */
58536     registerNode : function(node, includeChildren) {
58537         this.nodeHash[node.getId() || node.internalId] = node;
58538         if (includeChildren === true) {
58539             node.eachChild(function(child){
58540                 this.registerNode(child, true);
58541             }, this);
58542         }
58543     },
58544
58545     /**
58546      * Unregisters a node with the tree
58547      * @private
58548      * @param {Ext.data.NodeInterface} The node to unregister
58549      * @param {Boolean} [includeChildren] True to unregister any child nodes
58550      */
58551     unregisterNode : function(node, includeChildren) {
58552         delete this.nodeHash[node.getId() || node.internalId];
58553         if (includeChildren === true) {
58554             node.eachChild(function(child){
58555                 this.unregisterNode(child, true);
58556             }, this);
58557         }
58558     },
58559
58560     /**
58561      * Sorts this tree
58562      * @private
58563      * @param {Function} sorterFn The function to use for sorting
58564      * @param {Boolean} recursive True to perform recursive sorting
58565      */
58566     sort: function(sorterFn, recursive) {
58567         this.getRootNode().sort(sorterFn, recursive);
58568     },
58569
58570      /**
58571      * Filters this tree
58572      * @private
58573      * @param {Function} sorterFn The function to use for filtering
58574      * @param {Boolean} recursive True to perform recursive filtering
58575      */
58576     filter: function(filters, recursive) {
58577         this.getRootNode().filter(filters, recursive);
58578     }
58579 });
58580 /**
58581  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
58582  * It provides convenience methods for loading nodes, as well as the ability to use
58583  * the hierarchical tree structure combined with a store. This class is generally used
58584  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
58585  * the Tree for convenience.
58586  *
58587  * # Using Models
58588  *
58589  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
58590  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
58591  * in the {@link Ext.data.NodeInterface} documentation.
58592  *
58593  * # Reading Nested Data
58594  *
58595  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
58596  * so the reader can find nested data for each node. If a root is not specified, it will default to
58597  * 'children'.
58598  */
58599 Ext.define('Ext.data.TreeStore', {
58600     extend: 'Ext.data.AbstractStore',
58601     alias: 'store.tree',
58602     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
58603
58604     /**
58605      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
58606      * The root node for this store. For example:
58607      *
58608      *     root: {
58609      *         expanded: true,
58610      *         text: "My Root",
58611      *         children: [
58612      *             { text: "Child 1", leaf: true },
58613      *             { text: "Child 2", expanded: true, children: [
58614      *                 { text: "GrandChild", leaf: true }
58615      *             ] }
58616      *         ]
58617      *     }
58618      *
58619      * Setting the `root` config option is the same as calling {@link #setRootNode}.
58620      */
58621
58622     /**
58623      * @cfg {Boolean} clearOnLoad
58624      * Remove previously existing child nodes before loading. Default to true.
58625      */
58626     clearOnLoad : true,
58627
58628     /**
58629      * @cfg {String} nodeParam
58630      * The name of the parameter sent to the server which contains the identifier of the node.
58631      * Defaults to 'node'.
58632      */
58633     nodeParam: 'node',
58634
58635     /**
58636      * @cfg {String} defaultRootId
58637      * The default root id. Defaults to 'root'
58638      */
58639     defaultRootId: 'root',
58640
58641     /**
58642      * @cfg {String} defaultRootProperty
58643      * The root property to specify on the reader if one is not explicitly defined.
58644      */
58645     defaultRootProperty: 'children',
58646
58647     /**
58648      * @cfg {Boolean} folderSort
58649      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
58650      */
58651     folderSort: false,
58652
58653     constructor: function(config) {
58654         var me = this,
58655             root,
58656             fields;
58657
58658         config = Ext.apply({}, config);
58659
58660         /**
58661          * If we have no fields declare for the store, add some defaults.
58662          * These will be ignored if a model is explicitly specified.
58663          */
58664         fields = config.fields || me.fields;
58665         if (!fields) {
58666             config.fields = [{name: 'text', type: 'string'}];
58667         }
58668
58669         me.callParent([config]);
58670
58671         // We create our data tree.
58672         me.tree = Ext.create('Ext.data.Tree');
58673
58674         me.relayEvents(me.tree, [
58675             /**
58676              * @event append
58677              * @alias Ext.data.Tree#append
58678              */
58679             "append",
58680
58681             /**
58682              * @event remove
58683              * @alias Ext.data.Tree#remove
58684              */
58685             "remove",
58686
58687             /**
58688              * @event move
58689              * @alias Ext.data.Tree#move
58690              */
58691             "move",
58692
58693             /**
58694              * @event insert
58695              * @alias Ext.data.Tree#insert
58696              */
58697             "insert",
58698
58699             /**
58700              * @event beforeappend
58701              * @alias Ext.data.Tree#beforeappend
58702              */
58703             "beforeappend",
58704
58705             /**
58706              * @event beforeremove
58707              * @alias Ext.data.Tree#beforeremove
58708              */
58709             "beforeremove",
58710
58711             /**
58712              * @event beforemove
58713              * @alias Ext.data.Tree#beforemove
58714              */
58715             "beforemove",
58716
58717             /**
58718              * @event beforeinsert
58719              * @alias Ext.data.Tree#beforeinsert
58720              */
58721             "beforeinsert",
58722
58723              /**
58724               * @event expand
58725               * @alias Ext.data.Tree#expand
58726               */
58727              "expand",
58728
58729              /**
58730               * @event collapse
58731               * @alias Ext.data.Tree#collapse
58732               */
58733              "collapse",
58734
58735              /**
58736               * @event beforeexpand
58737               * @alias Ext.data.Tree#beforeexpand
58738               */
58739              "beforeexpand",
58740
58741              /**
58742               * @event beforecollapse
58743               * @alias Ext.data.Tree#beforecollapse
58744               */
58745              "beforecollapse",
58746
58747              /**
58748               * @event rootchange
58749               * @alias Ext.data.Tree#rootchange
58750               */
58751              "rootchange"
58752         ]);
58753
58754         me.tree.on({
58755             scope: me,
58756             remove: me.onNodeRemove,
58757             // this event must follow the relay to beforeitemexpand to allow users to
58758             // cancel the expand:
58759             beforeexpand: me.onBeforeNodeExpand,
58760             beforecollapse: me.onBeforeNodeCollapse,
58761             append: me.onNodeAdded,
58762             insert: me.onNodeAdded
58763         });
58764
58765         me.onBeforeSort();
58766
58767         root = me.root;
58768         if (root) {
58769             delete me.root;
58770             me.setRootNode(root);
58771         }
58772
58773         me.addEvents(
58774             /**
58775              * @event sort
58776              * Fires when this TreeStore is sorted.
58777              * @param {Ext.data.NodeInterface} node The node that is sorted.
58778              */
58779             'sort'
58780         );
58781
58782         //<deprecated since=0.99>
58783         if (Ext.isDefined(me.nodeParameter)) {
58784             if (Ext.isDefined(Ext.global.console)) {
58785                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
58786             }
58787             me.nodeParam = me.nodeParameter;
58788             delete me.nodeParameter;
58789         }
58790         //</deprecated>
58791     },
58792
58793     // inherit docs
58794     setProxy: function(proxy) {
58795         var reader,
58796             needsRoot;
58797
58798         if (proxy instanceof Ext.data.proxy.Proxy) {
58799             // proxy instance, check if a root was set
58800             needsRoot = Ext.isEmpty(proxy.getReader().root);
58801         } else if (Ext.isString(proxy)) {
58802             // string type, means a reader can't be set
58803             needsRoot = true;
58804         } else {
58805             // object, check if a reader and a root were specified.
58806             reader = proxy.reader;
58807             needsRoot = !(reader && !Ext.isEmpty(reader.root));
58808         }
58809         proxy = this.callParent(arguments);
58810         if (needsRoot) {
58811             reader = proxy.getReader();
58812             reader.root = this.defaultRootProperty;
58813             // force rebuild
58814             reader.buildExtractors(true);
58815         }
58816     },
58817
58818     // inherit docs
58819     onBeforeSort: function() {
58820         if (this.folderSort) {
58821             this.sort({
58822                 property: 'leaf',
58823                 direction: 'ASC'
58824             }, 'prepend', false);
58825         }
58826     },
58827
58828     /**
58829      * Called before a node is expanded.
58830      * @private
58831      * @param {Ext.data.NodeInterface} node The node being expanded.
58832      * @param {Function} callback The function to run after the expand finishes
58833      * @param {Object} scope The scope in which to run the callback function
58834      */
58835     onBeforeNodeExpand: function(node, callback, scope) {
58836         if (node.isLoaded()) {
58837             Ext.callback(callback, scope || node, [node.childNodes]);
58838         }
58839         else if (node.isLoading()) {
58840             this.on('load', function() {
58841                 Ext.callback(callback, scope || node, [node.childNodes]);
58842             }, this, {single: true});
58843         }
58844         else {
58845             this.read({
58846                 node: node,
58847                 callback: function() {
58848                     Ext.callback(callback, scope || node, [node.childNodes]);
58849                 }
58850             });
58851         }
58852     },
58853
58854     //inherit docs
58855     getNewRecords: function() {
58856         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
58857     },
58858
58859     //inherit docs
58860     getUpdatedRecords: function() {
58861         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
58862     },
58863
58864     /**
58865      * Called before a node is collapsed.
58866      * @private
58867      * @param {Ext.data.NodeInterface} node The node being collapsed.
58868      * @param {Function} callback The function to run after the collapse finishes
58869      * @param {Object} scope The scope in which to run the callback function
58870      */
58871     onBeforeNodeCollapse: function(node, callback, scope) {
58872         callback.call(scope || node, node.childNodes);
58873     },
58874
58875     onNodeRemove: function(parent, node) {
58876         var removed = this.removed;
58877
58878         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
58879             removed.push(node);
58880         }
58881     },
58882
58883     onNodeAdded: function(parent, node) {
58884         var proxy = this.getProxy(),
58885             reader = proxy.getReader(),
58886             data = node.raw || node.data,
58887             dataRoot, children;
58888
58889         Ext.Array.remove(this.removed, node);
58890
58891         if (!node.isLeaf() && !node.isLoaded()) {
58892             dataRoot = reader.getRoot(data);
58893             if (dataRoot) {
58894                 this.fillNode(node, reader.extractData(dataRoot));
58895                 delete data[reader.root];
58896             }
58897         }
58898     },
58899
58900     /**
58901      * Sets the root node for this store.  See also the {@link #root} config option.
58902      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
58903      * @return {Ext.data.NodeInterface} The new root
58904      */
58905     setRootNode: function(root) {
58906         var me = this;
58907
58908         root = root || {};
58909         if (!root.isNode) {
58910             // create a default rootNode and create internal data struct.
58911             Ext.applyIf(root, {
58912                 id: me.defaultRootId,
58913                 text: 'Root',
58914                 allowDrag: false
58915             });
58916             root = Ext.ModelManager.create(root, me.model);
58917         }
58918         Ext.data.NodeInterface.decorate(root);
58919
58920         // Because we have decorated the model with new fields,
58921         // we need to build new extactor functions on the reader.
58922         me.getProxy().getReader().buildExtractors(true);
58923
58924         // When we add the root to the tree, it will automaticaly get the NodeInterface
58925         me.tree.setRootNode(root);
58926
58927         // If the user has set expanded: true on the root, we want to call the expand function
58928         if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
58929             me.load({
58930                 node: root
58931             });
58932         }
58933
58934         return root;
58935     },
58936
58937     /**
58938      * Returns the root node for this tree.
58939      * @return {Ext.data.NodeInterface}
58940      */
58941     getRootNode: function() {
58942         return this.tree.getRootNode();
58943     },
58944
58945     /**
58946      * Returns the record node by id
58947      * @return {Ext.data.NodeInterface}
58948      */
58949     getNodeById: function(id) {
58950         return this.tree.getNodeById(id);
58951     },
58952
58953     /**
58954      * Loads the Store using its configured {@link #proxy}.
58955      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
58956      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
58957      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
58958      * default to the root node.
58959      */
58960     load: function(options) {
58961         options = options || {};
58962         options.params = options.params || {};
58963
58964         var me = this,
58965             node = options.node || me.tree.getRootNode(),
58966             root;
58967
58968         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
58969         // create one for them.
58970         if (!node) {
58971             node = me.setRootNode({
58972                 expanded: true
58973             });
58974         }
58975
58976         if (me.clearOnLoad) {
58977             node.removeAll(true);
58978         }
58979
58980         Ext.applyIf(options, {
58981             node: node
58982         });
58983         options.params[me.nodeParam] = node ? node.getId() : 'root';
58984
58985         if (node) {
58986             node.set('loading', true);
58987         }
58988
58989         return me.callParent([options]);
58990     },
58991
58992
58993     /**
58994      * Fills a node with a series of child records.
58995      * @private
58996      * @param {Ext.data.NodeInterface} node The node to fill
58997      * @param {Ext.data.Model[]} records The records to add
58998      */
58999     fillNode: function(node, records) {
59000         var me = this,
59001             ln = records ? records.length : 0,
59002             i = 0, sortCollection;
59003
59004         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
59005             sortCollection = Ext.create('Ext.util.MixedCollection');
59006             sortCollection.addAll(records);
59007             sortCollection.sort(me.sorters.items);
59008             records = sortCollection.items;
59009         }
59010
59011         node.set('loaded', true);
59012         for (; i < ln; i++) {
59013             node.appendChild(records[i], undefined, true);
59014         }
59015
59016         return records;
59017     },
59018
59019     // inherit docs
59020     onProxyLoad: function(operation) {
59021         var me = this,
59022             successful = operation.wasSuccessful(),
59023             records = operation.getRecords(),
59024             node = operation.node;
59025
59026         me.loading = false;
59027         node.set('loading', false);
59028         if (successful) {
59029             records = me.fillNode(node, records);
59030         }
59031         // The load event has an extra node parameter
59032         // (differing from the load event described in AbstractStore)
59033         /**
59034          * @event load
59035          * Fires whenever the store reads data from a remote data source.
59036          * @param {Ext.data.TreeStore} this
59037          * @param {Ext.data.NodeInterface} node The node that was loaded.
59038          * @param {Ext.data.Model[]} records An array of records.
59039          * @param {Boolean} successful True if the operation was successful.
59040          */
59041         // deprecate read?
59042         me.fireEvent('read', me, operation.node, records, successful);
59043         me.fireEvent('load', me, operation.node, records, successful);
59044         //this is a callback that would have been passed to the 'read' function and is optional
59045         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
59046     },
59047
59048     /**
59049      * Creates any new records when a write is returned from the server.
59050      * @private
59051      * @param {Ext.data.Model[]} records The array of new records
59052      * @param {Ext.data.Operation} operation The operation that just completed
59053      * @param {Boolean} success True if the operation was successful
59054      */
59055     onCreateRecords: function(records, operation, success) {
59056         if (success) {
59057             var i = 0,
59058                 length = records.length,
59059                 originalRecords = operation.records,
59060                 parentNode,
59061                 record,
59062                 original,
59063                 index;
59064
59065             /*
59066              * Loop over each record returned from the server. Assume they are
59067              * returned in order of how they were sent. If we find a matching
59068              * record, replace it with the newly created one.
59069              */
59070             for (; i < length; ++i) {
59071                 record = records[i];
59072                 original = originalRecords[i];
59073                 if (original) {
59074                     parentNode = original.parentNode;
59075                     if (parentNode) {
59076                         // prevent being added to the removed cache
59077                         original.isReplace = true;
59078                         parentNode.replaceChild(record, original);
59079                         delete original.isReplace;
59080                     }
59081                     record.phantom = false;
59082                 }
59083             }
59084         }
59085     },
59086
59087     /**
59088      * Updates any records when a write is returned from the server.
59089      * @private
59090      * @param {Ext.data.Model[]} records The array of updated records
59091      * @param {Ext.data.Operation} operation The operation that just completed
59092      * @param {Boolean} success True if the operation was successful
59093      */
59094     onUpdateRecords: function(records, operation, success){
59095         if (success) {
59096             var me = this,
59097                 i = 0,
59098                 length = records.length,
59099                 data = me.data,
59100                 original,
59101                 parentNode,
59102                 record;
59103
59104             for (; i < length; ++i) {
59105                 record = records[i];
59106                 original = me.tree.getNodeById(record.getId());
59107                 parentNode = original.parentNode;
59108                 if (parentNode) {
59109                     // prevent being added to the removed cache
59110                     original.isReplace = true;
59111                     parentNode.replaceChild(record, original);
59112                     original.isReplace = false;
59113                 }
59114             }
59115         }
59116     },
59117
59118     /**
59119      * Removes any records when a write is returned from the server.
59120      * @private
59121      * @param {Ext.data.Model[]} records The array of removed records
59122      * @param {Ext.data.Operation} operation The operation that just completed
59123      * @param {Boolean} success True if the operation was successful
59124      */
59125     onDestroyRecords: function(records, operation, success){
59126         if (success) {
59127             this.removed = [];
59128         }
59129     },
59130
59131     // inherit docs
59132     removeAll: function() {
59133         this.getRootNode().destroy(true);
59134         this.fireEvent('clear', this);
59135     },
59136
59137     // inherit docs
59138     doSort: function(sorterFn) {
59139         var me = this;
59140         if (me.remoteSort) {
59141             //the load function will pick up the new sorters and request the sorted data from the proxy
59142             me.load();
59143         } else {
59144             me.tree.sort(sorterFn, true);
59145             me.fireEvent('datachanged', me);
59146         }
59147         me.fireEvent('sort', me);
59148     }
59149 });
59150
59151 /**
59152  * @extend Ext.data.IdGenerator
59153  * @author Don Griffin
59154  *
59155  * This class generates UUID's according to RFC 4122. This class has a default id property.
59156  * This means that a single instance is shared unless the id property is overridden. Thus,
59157  * two {@link Ext.data.Model} instances configured like the following share one generator:
59158  *
59159  *     Ext.define('MyApp.data.MyModelX', {
59160  *         extend: 'Ext.data.Model',
59161  *         idgen: 'uuid'
59162  *     });
59163  *
59164  *     Ext.define('MyApp.data.MyModelY', {
59165  *         extend: 'Ext.data.Model',
59166  *         idgen: 'uuid'
59167  *     });
59168  *
59169  * This allows all models using this class to share a commonly configured instance.
59170  *
59171  * # Using Version 1 ("Sequential") UUID's
59172  *
59173  * If a server can provide a proper timestamp and a "cryptographic quality random number"
59174  * (as described in RFC 4122), the shared instance can be configured as follows:
59175  *
59176  *     Ext.data.IdGenerator.get('uuid').reconfigure({
59177  *         version: 1,
59178  *         clockSeq: clock, // 14 random bits
59179  *         salt: salt,      // 48 secure random bits (the Node field)
59180  *         timestamp: ts    // timestamp per Section 4.1.4
59181  *     });
59182  *
59183  *     // or these values can be split into 32-bit chunks:
59184  *
59185  *     Ext.data.IdGenerator.get('uuid').reconfigure({
59186  *         version: 1,
59187  *         clockSeq: clock,
59188  *         salt: { lo: saltLow32, hi: saltHigh32 },
59189  *         timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
59190  *     });
59191  *
59192  * This approach improves the generator's uniqueness by providing a valid timestamp and
59193  * higher quality random data. Version 1 UUID's should not be used unless this information
59194  * can be provided by a server and care should be taken to avoid caching of this data.
59195  *
59196  * See http://www.ietf.org/rfc/rfc4122.txt for details.
59197  */
59198 Ext.define('Ext.data.UuidGenerator', function () {
59199     var twoPow14 = Math.pow(2, 14),
59200         twoPow16 = Math.pow(2, 16),
59201         twoPow28 = Math.pow(2, 28),
59202         twoPow32 = Math.pow(2, 32);
59203
59204     function toHex (value, length) {
59205         var ret = value.toString(16);
59206         if (ret.length > length) {
59207             ret = ret.substring(ret.length - length); // right-most digits
59208         } else if (ret.length < length) {
59209             ret = Ext.String.leftPad(ret, length, '0');
59210         }
59211         return ret;
59212     }
59213
59214     function rand (lo, hi) {
59215         var v = Math.random() * (hi - lo + 1);
59216         return Math.floor(v) + lo;
59217     }
59218
59219     function split (bignum) {
59220         if (typeof(bignum) == 'number') {
59221             var hi = Math.floor(bignum / twoPow32);
59222             return {
59223                 lo: Math.floor(bignum - hi * twoPow32),
59224                 hi: hi
59225             };
59226         }
59227         return bignum;
59228     }
59229
59230     return {
59231         extend: 'Ext.data.IdGenerator',
59232
59233         alias: 'idgen.uuid',
59234
59235         id: 'uuid', // shared by default
59236
59237         /**
59238          * @property {Number/Object} salt
59239          * When created, this value is a 48-bit number. For computation, this value is split
59240          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
59241          */
59242
59243         /**
59244          * @property {Number/Object} timestamp
59245          * When created, this value is a 60-bit number. For computation, this value is split
59246          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
59247          */
59248
59249         /**
59250          * @cfg {Number} version
59251          * The Version of UUID. Supported values are:
59252          *
59253          *  * 1 : Time-based, "sequential" UUID.
59254          *  * 4 : Pseudo-random UUID.
59255          *
59256          * The default is 4.
59257          */
59258         version: 4,
59259
59260         constructor: function() {
59261             var me = this;
59262
59263             me.callParent(arguments);
59264
59265             me.parts = [];
59266             me.init();
59267         },
59268
59269         generate: function () {
59270             var me = this,
59271                 parts = me.parts,
59272                 ts = me.timestamp;
59273
59274             /*
59275                The magic decoder ring (derived from RFC 4122 Section 4.2.2):
59276
59277                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59278                |                          time_low                             |
59279                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59280                |           time_mid            |  ver  |        time_hi        |
59281                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59282                |res|  clock_hi |   clock_low   |    salt 0   |M|     salt 1    |
59283                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59284                |                         salt (2-5)                            |
59285                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59286
59287                          time_mid      clock_hi (low 6 bits)
59288                 time_low     | time_hi |clock_lo
59289                     |        |     |   || salt[0]
59290                     |        |     |   ||   | salt[1..5]
59291                     v        v     v   vv   v v
59292                     0badf00d-aced-1def-b123-dfad0badbeef
59293                                   ^    ^     ^
59294                             version    |     multicast (low bit)
59295                                        |
59296                                     reserved (upper 2 bits)
59297             */
59298             parts[0] = toHex(ts.lo, 8);
59299             parts[1] = toHex(ts.hi & 0xFFFF, 4);
59300             parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
59301             parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
59302                        toHex(me.clockSeq & 0xFF, 2);
59303             parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
59304
59305             if (me.version == 4) {
59306                 me.init(); // just regenerate all the random values...
59307             } else {
59308                 // sequentially increment the timestamp...
59309                 ++ts.lo;
59310                 if (ts.lo >= twoPow32) { // if (overflow)
59311                     ts.lo = 0;
59312                     ++ts.hi;
59313                 }
59314             }
59315
59316             return parts.join('-').toLowerCase();
59317         },
59318
59319         getRecId: function (rec) {
59320             return rec.getId();
59321         },
59322
59323         /**
59324          * @private
59325          */
59326         init: function () {
59327             var me = this,
59328                 salt, time;
59329
59330             if (me.version == 4) {
59331                 // See RFC 4122 (Secion 4.4)
59332                 //   o  If the state was unavailable (e.g., non-existent or corrupted),
59333                 //      or the saved node ID is different than the current node ID,
59334                 //      generate a random clock sequence value.
59335                 me.clockSeq = rand(0, twoPow14-1);
59336
59337                 // we run this on every id generation...
59338                 salt = me.salt || (me.salt = {});
59339                 time = me.timestamp || (me.timestamp = {});
59340
59341                 // See RFC 4122 (Secion 4.4)
59342                 salt.lo = rand(0, twoPow32-1);
59343                 salt.hi = rand(0, twoPow16-1);
59344                 time.lo = rand(0, twoPow32-1);
59345                 time.hi = rand(0, twoPow28-1);
59346             } else {
59347                 // this is run only once per-instance
59348                 me.salt = split(me.salt);
59349                 me.timestamp = split(me.timestamp);
59350
59351                 // Set multicast bit: "the least significant bit of the first octet of the
59352                 // node ID" (nodeId = salt for this implementation):
59353                 me.salt.hi |= 0x100;
59354             }
59355         },
59356
59357         /**
59358          * Reconfigures this generator given new config properties.
59359          */
59360         reconfigure: function (config) {
59361             Ext.apply(this, config);
59362             this.init();
59363         }
59364     };
59365 }());
59366
59367 /**
59368  * @author Ed Spencer
59369  * @class Ext.data.XmlStore
59370  * @extends Ext.data.Store
59371  * @private
59372  * @ignore
59373  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
59374  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
59375  * <p>A store configuration would be something like:<pre><code>
59376 var store = new Ext.data.XmlStore({
59377     // store configs
59378     autoDestroy: true,
59379     storeId: 'myStore',
59380     url: 'sheldon.xml', // automatically configures a HttpProxy
59381     // reader configs
59382     record: 'Item', // records will have an "Item" tag
59383     idPath: 'ASIN',
59384     totalRecords: '@TotalResults'
59385     fields: [
59386         // set up the fields mapping into the xml doc
59387         // The first needs mapping, the others are very basic
59388         {name: 'Author', mapping: 'ItemAttributes > Author'},
59389         'Title', 'Manufacturer', 'ProductGroup'
59390     ]
59391 });
59392  * </code></pre></p>
59393  * <p>This store is configured to consume a returned object of the form:<pre><code>
59394 &#60?xml version="1.0" encoding="UTF-8"?>
59395 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
59396     &#60Items>
59397         &#60Request>
59398             &#60IsValid>True&#60/IsValid>
59399             &#60ItemSearchRequest>
59400                 &#60Author>Sidney Sheldon&#60/Author>
59401                 &#60SearchIndex>Books&#60/SearchIndex>
59402             &#60/ItemSearchRequest>
59403         &#60/Request>
59404         &#60TotalResults>203&#60/TotalResults>
59405         &#60TotalPages>21&#60/TotalPages>
59406         &#60Item>
59407             &#60ASIN>0446355453&#60/ASIN>
59408             &#60DetailPageURL>
59409                 http://www.amazon.com/
59410             &#60/DetailPageURL>
59411             &#60ItemAttributes>
59412                 &#60Author>Sidney Sheldon&#60/Author>
59413                 &#60Manufacturer>Warner Books&#60/Manufacturer>
59414                 &#60ProductGroup>Book&#60/ProductGroup>
59415                 &#60Title>Master of the Game&#60/Title>
59416             &#60/ItemAttributes>
59417         &#60/Item>
59418     &#60/Items>
59419 &#60/ItemSearchResponse>
59420  * </code></pre>
59421  * An object literal of this form could also be used as the {@link #data} config option.</p>
59422  * <p><b>Note:</b> This class accepts all of the configuration options of
59423  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
59424  * @xtype xmlstore
59425  */
59426 Ext.define('Ext.data.XmlStore', {
59427     extend: 'Ext.data.Store',
59428     alternateClassName: 'Ext.data.XmlStore',
59429     alias: 'store.xml',
59430
59431     /**
59432      * @cfg {Ext.data.DataReader} reader @hide
59433      */
59434     constructor: function(config){
59435         config = config || {};
59436         config = config || {};
59437
59438         Ext.applyIf(config, {
59439             proxy: {
59440                 type: 'ajax',
59441                 reader: 'xml',
59442                 writer: 'xml'
59443             }
59444         });
59445
59446         this.callParent([config]);
59447     }
59448 });
59449
59450 /**
59451  * @author Ed Spencer
59452  *
59453  * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
59454  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
59455  * @private
59456  */
59457 Ext.define('Ext.data.proxy.Client', {
59458     extend: 'Ext.data.proxy.Proxy',
59459     alternateClassName: 'Ext.data.ClientProxy',
59460
59461     /**
59462      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
59463      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
59464      */
59465     clear: function() {
59466         //<debug>
59467         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.");
59468         //</debug>
59469     }
59470 });
59471 /**
59472  * @author Ed Spencer
59473  *
59474  * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
59475  * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
59476  * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
59477  *
59478  * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
59479  * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
59480  * injected might look like this:
59481  *
59482  *     <script src="http://domainB.com/users?callback=someCallback"></script>
59483  *
59484  * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
59485  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
59486  * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
59487  * long as the server formats the response to look like this, everything will work:
59488  *
59489  *     someCallback({
59490  *         users: [
59491  *             {
59492  *                 id: 1,
59493  *                 name: "Ed Spencer",
59494  *                 email: "ed@sencha.com"
59495  *             }
59496  *         ]
59497  *     });
59498  *
59499  * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
59500  * object that the server returned.
59501  *
59502  * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
59503  * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
59504  * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
59505  * we might set that up:
59506  *
59507  *     Ext.define('User', {
59508  *         extend: 'Ext.data.Model',
59509  *         fields: ['id', 'name', 'email']
59510  *     });
59511  *
59512  *     var store = Ext.create('Ext.data.Store', {
59513  *         model: 'User',
59514  *         proxy: {
59515  *             type: 'jsonp',
59516  *             url : 'http://domainB.com/users'
59517  *         }
59518  *     });
59519  *
59520  *     store.load();
59521  *
59522  * 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
59523  * like this:
59524  *
59525  *     <script src="http://domainB.com/users?callback=callback1"></script>
59526  *
59527  * # Customization
59528  *
59529  * This script tag can be customized using the {@link #callbackKey} configuration. For example:
59530  *
59531  *     var store = Ext.create('Ext.data.Store', {
59532  *         model: 'User',
59533  *         proxy: {
59534  *             type: 'jsonp',
59535  *             url : 'http://domainB.com/users',
59536  *             callbackKey: 'theCallbackFunction'
59537  *         }
59538  *     });
59539  *
59540  *     store.load();
59541  *
59542  * Would inject a script tag like this:
59543  *
59544  *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
59545  *
59546  * # Implementing on the server side
59547  *
59548  * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
59549  * achieve this using Java, PHP and ASP.net:
59550  *
59551  * Java:
59552  *
59553  *     boolean jsonP = false;
59554  *     String cb = request.getParameter("callback");
59555  *     if (cb != null) {
59556  *         jsonP = true;
59557  *         response.setContentType("text/javascript");
59558  *     } else {
59559  *         response.setContentType("application/x-json");
59560  *     }
59561  *     Writer out = response.getWriter();
59562  *     if (jsonP) {
59563  *         out.write(cb + "(");
59564  *     }
59565  *     out.print(dataBlock.toJsonString());
59566  *     if (jsonP) {
59567  *         out.write(");");
59568  *     }
59569  *
59570  * PHP:
59571  *
59572  *     $callback = $_REQUEST['callback'];
59573  *
59574  *     // Create the output object.
59575  *     $output = array('a' => 'Apple', 'b' => 'Banana');
59576  *
59577  *     //start output
59578  *     if ($callback) {
59579  *         header('Content-Type: text/javascript');
59580  *         echo $callback . '(' . json_encode($output) . ');';
59581  *     } else {
59582  *         header('Content-Type: application/x-json');
59583  *         echo json_encode($output);
59584  *     }
59585  *
59586  * ASP.net:
59587  *
59588  *     String jsonString = "{success: true}";
59589  *     String cb = Request.Params.Get("callback");
59590  *     String responseString = "";
59591  *     if (!String.IsNullOrEmpty(cb)) {
59592  *         responseString = cb + "(" + jsonString + ")";
59593  *     } else {
59594  *         responseString = jsonString;
59595  *     }
59596  *     Response.Write(responseString);
59597  */
59598 Ext.define('Ext.data.proxy.JsonP', {
59599     extend: 'Ext.data.proxy.Server',
59600     alternateClassName: 'Ext.data.ScriptTagProxy',
59601     alias: ['proxy.jsonp', 'proxy.scripttag'],
59602     requires: ['Ext.data.JsonP'],
59603
59604     defaultWriterType: 'base',
59605
59606     /**
59607      * @cfg {String} callbackKey
59608      * See {@link Ext.data.JsonP#callbackKey}.
59609      */
59610     callbackKey : 'callback',
59611
59612     /**
59613      * @cfg {String} recordParam
59614      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
59615      * 'records'
59616      */
59617     recordParam: 'records',
59618
59619     /**
59620      * @cfg {Boolean} autoAppendParams
59621      * True to automatically append the request's params to the generated url. Defaults to true
59622      */
59623     autoAppendParams: true,
59624
59625     constructor: function(){
59626         this.addEvents(
59627             /**
59628              * @event
59629              * Fires when the server returns an exception
59630              * @param {Ext.data.proxy.Proxy} this
59631              * @param {Ext.data.Request} request The request that was sent
59632              * @param {Ext.data.Operation} operation The operation that triggered the request
59633              */
59634             'exception'
59635         );
59636         this.callParent(arguments);
59637     },
59638
59639     /**
59640      * @private
59641      * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
59642      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
59643      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
59644      * @param {Function} callback A callback function to execute when the Operation has been completed
59645      * @param {Object} scope The scope to execute the callback in
59646      */
59647     doRequest: function(operation, callback, scope) {
59648         //generate the unique IDs for this request
59649         var me      = this,
59650             writer  = me.getWriter(),
59651             request = me.buildRequest(operation),
59652             params = request.params;
59653
59654         if (operation.allowWrite()) {
59655             request = writer.write(request);
59656         }
59657
59658         // apply JsonP proxy-specific attributes to the Request
59659         Ext.apply(request, {
59660             callbackKey: me.callbackKey,
59661             timeout: me.timeout,
59662             scope: me,
59663             disableCaching: false, // handled by the proxy
59664             callback: me.createRequestCallback(request, operation, callback, scope)
59665         });
59666
59667         // prevent doubling up
59668         if (me.autoAppendParams) {
59669             request.params = {};
59670         }
59671
59672         request.jsonp = Ext.data.JsonP.request(request);
59673         // restore on the request
59674         request.params = params;
59675         operation.setStarted();
59676         me.lastRequest = request;
59677
59678         return request;
59679     },
59680
59681     /**
59682      * @private
59683      * Creates and returns the function that is called when the request has completed. The returned function
59684      * should accept a Response object, which contains the response to be read by the configured Reader.
59685      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
59686      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
59687      * theCallback refers to the callback argument received by this function.
59688      * See {@link #doRequest} for details.
59689      * @param {Ext.data.Request} request The Request object
59690      * @param {Ext.data.Operation} operation The Operation being executed
59691      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
59692      * passed to doRequest
59693      * @param {Object} scope The scope in which to execute the callback function
59694      * @return {Function} The callback function
59695      */
59696     createRequestCallback: function(request, operation, callback, scope) {
59697         var me = this;
59698
59699         return function(success, response, errorType) {
59700             delete me.lastRequest;
59701             me.processResponse(success, operation, request, response, callback, scope);
59702         };
59703     },
59704
59705     // inherit docs
59706     setException: function(operation, response) {
59707         operation.setException(operation.request.jsonp.errorType);
59708     },
59709
59710
59711     /**
59712      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
59713      * @param {Ext.data.Request} request The request object
59714      * @return {String} The url
59715      */
59716     buildUrl: function(request) {
59717         var me      = this,
59718             url     = me.callParent(arguments),
59719             params  = Ext.apply({}, request.params),
59720             filters = params.filters,
59721             records,
59722             filter, i;
59723
59724         delete params.filters;
59725
59726         if (me.autoAppendParams) {
59727             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
59728         }
59729
59730         if (filters && filters.length) {
59731             for (i = 0; i < filters.length; i++) {
59732                 filter = filters[i];
59733
59734                 if (filter.value) {
59735                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
59736                 }
59737             }
59738         }
59739
59740         //if there are any records present, append them to the url also
59741         records = request.records;
59742
59743         if (Ext.isArray(records) && records.length > 0) {
59744             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
59745         }
59746
59747         return url;
59748     },
59749
59750     //inherit docs
59751     destroy: function() {
59752         this.abort();
59753         this.callParent();
59754     },
59755
59756     /**
59757      * Aborts the current server request if one is currently running
59758      */
59759     abort: function() {
59760         var lastRequest = this.lastRequest;
59761         if (lastRequest) {
59762             Ext.data.JsonP.abort(lastRequest.jsonp);
59763         }
59764     },
59765
59766     /**
59767      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
59768      * its own function so that it can be easily overridden.
59769      * @param {Ext.data.Model[]} records The records array
59770      * @return {String} The encoded records string
59771      */
59772     encodeRecords: function(records) {
59773         var encoded = "",
59774             i = 0,
59775             len = records.length;
59776
59777         for (; i < len; i++) {
59778             encoded += Ext.Object.toQueryString(records[i].data);
59779         }
59780
59781         return encoded;
59782     }
59783 });
59784
59785 /**
59786  * @author Ed Spencer
59787  *
59788  * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
59789  * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
59790  * save {@link Ext.data.Model model instances} for offline use.
59791  * @private
59792  */
59793 Ext.define('Ext.data.proxy.WebStorage', {
59794     extend: 'Ext.data.proxy.Client',
59795     alternateClassName: 'Ext.data.WebStorageProxy',
59796
59797     /**
59798      * @cfg {String} id
59799      * The unique ID used as the key in which all record data are stored in the local storage object.
59800      */
59801     id: undefined,
59802
59803     /**
59804      * Creates the proxy, throws an error if local storage is not supported in the current browser.
59805      * @param {Object} config (optional) Config object.
59806      */
59807     constructor: function(config) {
59808         this.callParent(arguments);
59809
59810         /**
59811          * @property {Object} cache
59812          * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
59813          */
59814         this.cache = {};
59815
59816         //<debug>
59817         if (this.getStorageObject() === undefined) {
59818             Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
59819         }
59820         //</debug>
59821
59822         //if an id is not given, try to use the store's id instead
59823         this.id = this.id || (this.store ? this.store.storeId : undefined);
59824
59825         //<debug>
59826         if (this.id === undefined) {
59827             Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
59828         }
59829         //</debug>
59830
59831         this.initialize();
59832     },
59833
59834     //inherit docs
59835     create: function(operation, callback, scope) {
59836         var records = operation.records,
59837             length  = records.length,
59838             ids     = this.getIds(),
59839             id, record, i;
59840
59841         operation.setStarted();
59842
59843         for (i = 0; i < length; i++) {
59844             record = records[i];
59845
59846             if (record.phantom) {
59847                 record.phantom = false;
59848                 id = this.getNextId();
59849             } else {
59850                 id = record.getId();
59851             }
59852
59853             this.setRecord(record, id);
59854             ids.push(id);
59855         }
59856
59857         this.setIds(ids);
59858
59859         operation.setCompleted();
59860         operation.setSuccessful();
59861
59862         if (typeof callback == 'function') {
59863             callback.call(scope || this, operation);
59864         }
59865     },
59866
59867     //inherit docs
59868     read: function(operation, callback, scope) {
59869         //TODO: respect sorters, filters, start and limit options on the Operation
59870
59871         var records = [],
59872             ids     = this.getIds(),
59873             length  = ids.length,
59874             i, recordData, record;
59875
59876         //read a single record
59877         if (operation.id) {
59878             record = this.getRecord(operation.id);
59879
59880             if (record) {
59881                 records.push(record);
59882                 operation.setSuccessful();
59883             }
59884         } else {
59885             for (i = 0; i < length; i++) {
59886                 records.push(this.getRecord(ids[i]));
59887             }
59888             operation.setSuccessful();
59889         }
59890
59891         operation.setCompleted();
59892
59893         operation.resultSet = Ext.create('Ext.data.ResultSet', {
59894             records: records,
59895             total  : records.length,
59896             loaded : true
59897         });
59898
59899         if (typeof callback == 'function') {
59900             callback.call(scope || this, operation);
59901         }
59902     },
59903
59904     //inherit docs
59905     update: function(operation, callback, scope) {
59906         var records = operation.records,
59907             length  = records.length,
59908             ids     = this.getIds(),
59909             record, id, i;
59910
59911         operation.setStarted();
59912
59913         for (i = 0; i < length; i++) {
59914             record = records[i];
59915             this.setRecord(record);
59916
59917             //we need to update the set of ids here because it's possible that a non-phantom record was added
59918             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
59919             id = record.getId();
59920             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
59921                 ids.push(id);
59922             }
59923         }
59924         this.setIds(ids);
59925
59926         operation.setCompleted();
59927         operation.setSuccessful();
59928
59929         if (typeof callback == 'function') {
59930             callback.call(scope || this, operation);
59931         }
59932     },
59933
59934     //inherit
59935     destroy: function(operation, callback, scope) {
59936         var records = operation.records,
59937             length  = records.length,
59938             ids     = this.getIds(),
59939
59940             //newIds is a copy of ids, from which we remove the destroyed records
59941             newIds  = [].concat(ids),
59942             i;
59943
59944         for (i = 0; i < length; i++) {
59945             Ext.Array.remove(newIds, records[i].getId());
59946             this.removeRecord(records[i], false);
59947         }
59948
59949         this.setIds(newIds);
59950
59951         operation.setCompleted();
59952         operation.setSuccessful();
59953
59954         if (typeof callback == 'function') {
59955             callback.call(scope || this, operation);
59956         }
59957     },
59958
59959     /**
59960      * @private
59961      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
59962      * @param {String} id The record's unique ID
59963      * @return {Ext.data.Model} The model instance
59964      */
59965     getRecord: function(id) {
59966         if (this.cache[id] === undefined) {
59967             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
59968                 data    = {},
59969                 Model   = this.model,
59970                 fields  = Model.prototype.fields.items,
59971                 length  = fields.length,
59972                 i, field, name, record;
59973
59974             for (i = 0; i < length; i++) {
59975                 field = fields[i];
59976                 name  = field.name;
59977
59978                 if (typeof field.decode == 'function') {
59979                     data[name] = field.decode(rawData[name]);
59980                 } else {
59981                     data[name] = rawData[name];
59982                 }
59983             }
59984
59985             record = new Model(data, id);
59986             record.phantom = false;
59987
59988             this.cache[id] = record;
59989         }
59990
59991         return this.cache[id];
59992     },
59993
59994     /**
59995      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
59996      * @param {Ext.data.Model} record The model instance
59997      * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
59998      */
59999     setRecord: function(record, id) {
60000         if (id) {
60001             record.setId(id);
60002         } else {
60003             id = record.getId();
60004         }
60005
60006         var me = this,
60007             rawData = record.data,
60008             data    = {},
60009             model   = me.model,
60010             fields  = model.prototype.fields.items,
60011             length  = fields.length,
60012             i = 0,
60013             field, name, obj, key;
60014
60015         for (; i < length; i++) {
60016             field = fields[i];
60017             name  = field.name;
60018
60019             if (typeof field.encode == 'function') {
60020                 data[name] = field.encode(rawData[name], record);
60021             } else {
60022                 data[name] = rawData[name];
60023             }
60024         }
60025
60026         obj = me.getStorageObject();
60027         key = me.getRecordKey(id);
60028
60029         //keep the cache up to date
60030         me.cache[id] = record;
60031
60032         //iPad bug requires that we remove the item before setting it
60033         obj.removeItem(key);
60034         obj.setItem(key, Ext.encode(data));
60035     },
60036
60037     /**
60038      * @private
60039      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
60040      * use instead because it updates the list of currently-stored record ids
60041      * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
60042      */
60043     removeRecord: function(id, updateIds) {
60044         var me = this,
60045             ids;
60046
60047         if (id.isModel) {
60048             id = id.getId();
60049         }
60050
60051         if (updateIds !== false) {
60052             ids = me.getIds();
60053             Ext.Array.remove(ids, id);
60054             me.setIds(ids);
60055         }
60056
60057         me.getStorageObject().removeItem(me.getRecordKey(id));
60058     },
60059
60060     /**
60061      * @private
60062      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
60063      * storing data in the local storage object and should prevent naming collisions.
60064      * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
60065      * @return {String} The unique key for this record
60066      */
60067     getRecordKey: function(id) {
60068         if (id.isModel) {
60069             id = id.getId();
60070         }
60071
60072         return Ext.String.format("{0}-{1}", this.id, id);
60073     },
60074
60075     /**
60076      * @private
60077      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
60078      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
60079      * @return {String} The counter key
60080      */
60081     getRecordCounterKey: function() {
60082         return Ext.String.format("{0}-counter", this.id);
60083     },
60084
60085     /**
60086      * @private
60087      * Returns the array of record IDs stored in this Proxy
60088      * @return {Number[]} The record IDs. Each is cast as a Number
60089      */
60090     getIds: function() {
60091         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
60092             length = ids.length,
60093             i;
60094
60095         if (length == 1 && ids[0] === "") {
60096             ids = [];
60097         } else {
60098             for (i = 0; i < length; i++) {
60099                 ids[i] = parseInt(ids[i], 10);
60100             }
60101         }
60102
60103         return ids;
60104     },
60105
60106     /**
60107      * @private
60108      * Saves the array of ids representing the set of all records in the Proxy
60109      * @param {Number[]} ids The ids to set
60110      */
60111     setIds: function(ids) {
60112         var obj = this.getStorageObject(),
60113             str = ids.join(",");
60114
60115         obj.removeItem(this.id);
60116
60117         if (!Ext.isEmpty(str)) {
60118             obj.setItem(this.id, str);
60119         }
60120     },
60121
60122     /**
60123      * @private
60124      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
60125      * Increments the counter.
60126      * @return {Number} The id
60127      */
60128     getNextId: function() {
60129         var obj  = this.getStorageObject(),
60130             key  = this.getRecordCounterKey(),
60131             last = obj.getItem(key),
60132             ids, id;
60133
60134         if (last === null) {
60135             ids = this.getIds();
60136             last = ids[ids.length - 1] || 0;
60137         }
60138
60139         id = parseInt(last, 10) + 1;
60140         obj.setItem(key, id);
60141
60142         return id;
60143     },
60144
60145     /**
60146      * @private
60147      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
60148      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
60149      */
60150     initialize: function() {
60151         var storageObject = this.getStorageObject();
60152         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
60153     },
60154
60155     /**
60156      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
60157      * storage object.
60158      */
60159     clear: function() {
60160         var obj = this.getStorageObject(),
60161             ids = this.getIds(),
60162             len = ids.length,
60163             i;
60164
60165         //remove all the records
60166         for (i = 0; i < len; i++) {
60167             this.removeRecord(ids[i]);
60168         }
60169
60170         //remove the supporting objects
60171         obj.removeItem(this.getRecordCounterKey());
60172         obj.removeItem(this.id);
60173     },
60174
60175     /**
60176      * @private
60177      * Abstract function which should return the storage object that data will be saved to. This must be implemented
60178      * in each subclass.
60179      * @return {Object} The storage object
60180      */
60181     getStorageObject: function() {
60182         //<debug>
60183         Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
60184         //</debug>
60185     }
60186 });
60187 /**
60188  * @author Ed Spencer
60189  *
60190  * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
60191  * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
60192  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
60193  *
60194  * localStorage is extremely useful for saving user-specific information without needing to build server-side
60195  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
60196  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
60197  *
60198  *     Ext.define('Search', {
60199  *         fields: ['id', 'query'],
60200  *         extend: 'Ext.data.Model',
60201  *         proxy: {
60202  *             type: 'localstorage',
60203  *             id  : 'twitter-Searches'
60204  *         }
60205  *     });
60206  *
60207  * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
60208  * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
60209  * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
60210  * LocalStorageProxy to manage the saved Search data.
60211  *
60212  * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
60213  *
60214  *     //our Store automatically picks up the LocalStorageProxy defined on the Search model
60215  *     var store = Ext.create('Ext.data.Store', {
60216  *         model: "Search"
60217  *     });
60218  *
60219  *     //loads any existing Search data from localStorage
60220  *     store.load();
60221  *
60222  *     //now add some Searches
60223  *     store.add({query: 'Sencha Touch'});
60224  *     store.add({query: 'Ext JS'});
60225  *
60226  *     //finally, save our Search data to localStorage
60227  *     store.sync();
60228  *
60229  * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
60230  * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
60231  *
60232  *     var search = Ext.create('Search', {query: 'Sencha Animator'});
60233  *
60234  *     //uses the configured LocalStorageProxy to save the new Search to localStorage
60235  *     search.save();
60236  *
60237  * # Limitations
60238  *
60239  * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
60240  * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
60241  * object.
60242  *
60243  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
60244  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
60245  */
60246 Ext.define('Ext.data.proxy.LocalStorage', {
60247     extend: 'Ext.data.proxy.WebStorage',
60248     alias: 'proxy.localstorage',
60249     alternateClassName: 'Ext.data.LocalStorageProxy',
60250     
60251     //inherit docs
60252     getStorageObject: function() {
60253         return window.localStorage;
60254     }
60255 });
60256 /**
60257  * @author Ed Spencer
60258  *
60259  * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
60260  * every page refresh.
60261  *
60262  * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
60263  * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
60264  * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
60265  * Store:
60266  *
60267  *     //this is the model we will be using in the store
60268  *     Ext.define('User', {
60269  *         extend: 'Ext.data.Model',
60270  *         fields: [
60271  *             {name: 'id',    type: 'int'},
60272  *             {name: 'name',  type: 'string'},
60273  *             {name: 'phone', type: 'string', mapping: 'phoneNumber'}
60274  *         ]
60275  *     });
60276  *
60277  *     //this data does not line up to our model fields - the phone field is called phoneNumber
60278  *     var data = {
60279  *         users: [
60280  *             {
60281  *                 id: 1,
60282  *                 name: 'Ed Spencer',
60283  *                 phoneNumber: '555 1234'
60284  *             },
60285  *             {
60286  *                 id: 2,
60287  *                 name: 'Abe Elias',
60288  *                 phoneNumber: '666 1234'
60289  *             }
60290  *         ]
60291  *     };
60292  *
60293  *     //note how we set the 'root' in the reader to match the data structure above
60294  *     var store = Ext.create('Ext.data.Store', {
60295  *         autoLoad: true,
60296  *         model: 'User',
60297  *         data : data,
60298  *         proxy: {
60299  *             type: 'memory',
60300  *             reader: {
60301  *                 type: 'json',
60302  *                 root: 'users'
60303  *             }
60304  *         }
60305  *     });
60306  */
60307 Ext.define('Ext.data.proxy.Memory', {
60308     extend: 'Ext.data.proxy.Client',
60309     alias: 'proxy.memory',
60310     alternateClassName: 'Ext.data.MemoryProxy',
60311
60312     /**
60313      * @cfg {Ext.data.Model[]} data
60314      * Optional array of Records to load into the Proxy
60315      */
60316
60317     constructor: function(config) {
60318         this.callParent([config]);
60319
60320         //ensures that the reader has been instantiated properly
60321         this.setReader(this.reader);
60322     },
60323
60324     /**
60325      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
60326      * @param {Ext.data.Operation} operation The read Operation
60327      * @param {Function} callback The callback to call when reading has completed
60328      * @param {Object} scope The scope to call the callback function in
60329      */
60330     read: function(operation, callback, scope) {
60331         var me     = this,
60332             reader = me.getReader(),
60333             result = reader.read(me.data);
60334
60335         Ext.apply(operation, {
60336             resultSet: result
60337         });
60338
60339         operation.setCompleted();
60340         operation.setSuccessful();
60341         Ext.callback(callback, scope || me, [operation]);
60342     },
60343
60344     clear: Ext.emptyFn
60345 });
60346
60347 /**
60348  * @author Ed Spencer
60349  *
60350  * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
60351  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
60352  * with an inline Rest proxy
60353  *
60354  *     Ext.define('User', {
60355  *         extend: 'Ext.data.Model',
60356  *         fields: ['id', 'name', 'email'],
60357  *
60358  *         proxy: {
60359  *             type: 'rest',
60360  *             url : '/users'
60361  *         }
60362  *     });
60363  *
60364  * 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
60365  * request to '/users':
60366  *
60367  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
60368  *
60369  *     user.save(); //POST /users
60370  *
60371  * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
60372  * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
60373  *
60374  *     user.save({
60375  *         success: function(user) {
60376  *             user.set('name', 'Khan Noonien Singh');
60377  *
60378  *             user.save(); //PUT /users/123
60379  *         }
60380  *     });
60381  *
60382  * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
60383  * relevant url for that user. Now let's delete this user, which will use the DELETE method:
60384  *
60385  *         user.destroy(); //DELETE /users/123
60386  *
60387  * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
60388  *
60389  *     //1. Load via Store
60390  *
60391  *     //the Store automatically picks up the Proxy from the User model
60392  *     var store = Ext.create('Ext.data.Store', {
60393  *         model: 'User'
60394  *     });
60395  *
60396  *     store.load(); //GET /users
60397  *
60398  *     //2. Load directly from the Model
60399  *
60400  *     //GET /users/123
60401  *     Ext.ModelManager.getModel('User').load(123, {
60402  *         success: function(user) {
60403  *             console.log(user.getId()); //outputs 123
60404  *         }
60405  *     });
60406  *
60407  * # Url generation
60408  *
60409  * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
60410  * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
60411  * instance in question to the configured url, resulting in the '/users/123' that we saw above.
60412  *
60413  * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
60414  * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
60415  *
60416  *     new Ext.data.proxy.Rest({
60417  *         url: '/users',
60418  *         appendId: true //default
60419  *     });
60420  *
60421  *     // Collection url: /users
60422  *     // Instance url  : /users/123
60423  *
60424  * The Rest proxy can also optionally append a format string to the end of any generated url:
60425  *
60426  *     new Ext.data.proxy.Rest({
60427  *         url: '/users',
60428  *         format: 'json'
60429  *     });
60430  *
60431  *     // Collection url: /users.json
60432  *     // Instance url  : /users/123.json
60433  *
60434  * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
60435  * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
60436  * an example of how to achieve this.
60437  *
60438  * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
60439  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
60440  * details.
60441  *
60442  * [1]: source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl
60443  */
60444 Ext.define('Ext.data.proxy.Rest', {
60445     extend: 'Ext.data.proxy.Ajax',
60446     alternateClassName: 'Ext.data.RestProxy',
60447     alias : 'proxy.rest',
60448     
60449     /**
60450      * @cfg {Boolean} appendId
60451      * True to automatically append the ID of a Model instance when performing a request based on that single instance.
60452      * See Rest proxy intro docs for more details. Defaults to true.
60453      */
60454     appendId: true,
60455     
60456     /**
60457      * @cfg {String} format
60458      * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
60459      * for full details. Defaults to undefined.
60460      */
60461     
60462     /**
60463      * @cfg {Boolean} batchActions
60464      * True to batch actions of a particular type when synchronizing the store. Defaults to false.
60465      */
60466     batchActions: false,
60467     
60468     /**
60469      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
60470      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
60471      * that additional parameters like the cache buster string are appended.
60472      * @param {Object} request
60473      */
60474     buildUrl: function(request) {
60475         var me        = this,
60476             operation = request.operation,
60477             records   = operation.records || [],
60478             record    = records[0],
60479             format    = me.format,
60480             url       = me.getUrl(request),
60481             id        = record ? record.getId() : operation.id;
60482         
60483         if (me.appendId && id) {
60484             if (!url.match(/\/$/)) {
60485                 url += '/';
60486             }
60487             
60488             url += id;
60489         }
60490         
60491         if (format) {
60492             if (!url.match(/\.$/)) {
60493                 url += '.';
60494             }
60495             
60496             url += format;
60497         }
60498         
60499         request.url = url;
60500         
60501         return me.callParent(arguments);
60502     }
60503 }, function() {
60504     Ext.apply(this.prototype, {
60505         /**
60506          * @property {Object} actionMethods
60507          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
60508          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
60509          * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
60510          * can be overridden instead.
60511          */
60512         actionMethods: {
60513             create : 'POST',
60514             read   : 'GET',
60515             update : 'PUT',
60516             destroy: 'DELETE'
60517         }
60518     });
60519 });
60520
60521 /**
60522  * @author Ed Spencer
60523  *
60524  * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
60525  * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
60526  * unique ID which is used as a key in which all record data are stored in the session storage object.
60527  *
60528  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
60529  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
60530  *
60531  * Proxies are almost always used with a {@link Ext.data.Store store}:
60532  *
60533  *     new Ext.data.Store({
60534  *         proxy: {
60535  *             type: 'sessionstorage',
60536  *             id  : 'myProxyKey'
60537  *         }
60538  *     });
60539  *
60540  * Alternatively you can instantiate the Proxy directly:
60541  *
60542  *     new Ext.data.proxy.SessionStorage({
60543  *         id  : 'myOtherProxyKey'
60544  *     });
60545  *
60546  * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
60547  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
60548  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
60549  */
60550 Ext.define('Ext.data.proxy.SessionStorage', {
60551     extend: 'Ext.data.proxy.WebStorage',
60552     alias: 'proxy.sessionstorage',
60553     alternateClassName: 'Ext.data.SessionStorageProxy',
60554     
60555     //inherit docs
60556     getStorageObject: function() {
60557         return window.sessionStorage;
60558     }
60559 });
60560
60561 /**
60562  * @author Ed Spencer
60563  * @class Ext.data.reader.Array
60564  * @extends Ext.data.reader.Json
60565  * 
60566  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
60567  * Each element of that Array represents a row of data fields. The
60568  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
60569  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
60570  * 
60571  * <p><u>Example code:</u></p>
60572  * 
60573 <pre><code>
60574 Employee = Ext.define('Employee', {
60575     extend: 'Ext.data.Model',
60576     fields: [
60577         'id',
60578         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
60579         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
60580     ]
60581 });
60582
60583 var myReader = new Ext.data.reader.Array({
60584     model: 'Employee'
60585 }, Employee);
60586 </code></pre>
60587  * 
60588  * <p>This would consume an Array like this:</p>
60589  * 
60590 <pre><code>
60591 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
60592 </code></pre>
60593  * 
60594  * @constructor
60595  * Create a new ArrayReader
60596  * @param {Object} meta Metadata configuration options.
60597  */
60598 Ext.define('Ext.data.reader.Array', {
60599     extend: 'Ext.data.reader.Json',
60600     alternateClassName: 'Ext.data.ArrayReader',
60601     alias : 'reader.array',
60602
60603     /**
60604      * @private
60605      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
60606      * reference the correct position in the array.
60607      */
60608     buildExtractors: function() {
60609         this.callParent(arguments);
60610         
60611         var fields = this.model.prototype.fields.items,
60612             i = 0,
60613             length = fields.length,
60614             extractorFunctions = [],
60615             map;
60616         
60617         for (; i < length; i++) {
60618             map = fields[i].mapping;
60619             extractorFunctions.push(function(index) {
60620                 return function(data) {
60621                     return data[index];
60622                 };
60623             }(map !== null ? map : i));
60624         }
60625         
60626         this.extractorFunctions = extractorFunctions;
60627     }
60628 });
60629
60630 /**
60631  * @author Ed Spencer
60632  * @class Ext.data.reader.Xml
60633  * @extends Ext.data.reader.Reader
60634  *
60635  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
60636  * happens as a result of loading a Store - for example we might create something like this:</p>
60637  *
60638 <pre><code>
60639 Ext.define('User', {
60640     extend: 'Ext.data.Model',
60641     fields: ['id', 'name', 'email']
60642 });
60643
60644 var store = Ext.create('Ext.data.Store', {
60645     model: 'User',
60646     proxy: {
60647         type: 'ajax',
60648         url : 'users.xml',
60649         reader: {
60650             type: 'xml',
60651             record: 'user'
60652         }
60653     }
60654 });
60655 </code></pre>
60656  *
60657  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
60658  * not already familiar with them.</p>
60659  *
60660  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
60661  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
60662  * Store, so it is as if we passed this instead:
60663  *
60664 <pre><code>
60665 reader: {
60666     type : 'xml',
60667     model: 'User',
60668     record: 'user'
60669 }
60670 </code></pre>
60671  *
60672  * <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>
60673  *
60674 <pre><code>
60675 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
60676 &lt;user&gt;
60677     &lt;id&gt;1&lt;/id&gt;
60678     &lt;name&gt;Ed Spencer&lt;/name&gt;
60679     &lt;email&gt;ed@sencha.com&lt;/email&gt;
60680 &lt;/user&gt;
60681 &lt;user&gt;
60682     &lt;id&gt;2&lt;/id&gt;
60683     &lt;name&gt;Abe Elias&lt;/name&gt;
60684     &lt;email&gt;abe@sencha.com&lt;/email&gt;
60685 &lt;/user&gt;
60686 </code></pre>
60687  *
60688  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
60689  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
60690  *
60691  * <p><u>Reading other XML formats</u></p>
60692  *
60693  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
60694  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
60695  * {@link #root} configuration to parse data that comes back like this:</p>
60696  *
60697 <pre><code>
60698 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
60699 &lt;users&gt;
60700     &lt;user&gt;
60701         &lt;id&gt;1&lt;/id&gt;
60702         &lt;name&gt;Ed Spencer&lt;/name&gt;
60703         &lt;email&gt;ed@sencha.com&lt;/email&gt;
60704     &lt;/user&gt;
60705     &lt;user&gt;
60706         &lt;id&gt;2&lt;/id&gt;
60707         &lt;name&gt;Abe Elias&lt;/name&gt;
60708         &lt;email&gt;abe@sencha.com&lt;/email&gt;
60709     &lt;/user&gt;
60710 &lt;/users&gt;
60711 </code></pre>
60712  *
60713  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
60714  *
60715 <pre><code>
60716 reader: {
60717     type  : 'xml',
60718     root  : 'users',
60719     record: 'user'
60720 }
60721 </code></pre>
60722  *
60723  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
60724  * a larger structure, so a response like this will still work:
60725  *
60726 <pre><code>
60727 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
60728 &lt;deeply&gt;
60729     &lt;nested&gt;
60730         &lt;xml&gt;
60731             &lt;users&gt;
60732                 &lt;user&gt;
60733                     &lt;id&gt;1&lt;/id&gt;
60734                     &lt;name&gt;Ed Spencer&lt;/name&gt;
60735                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
60736                 &lt;/user&gt;
60737                 &lt;user&gt;
60738                     &lt;id&gt;2&lt;/id&gt;
60739                     &lt;name&gt;Abe Elias&lt;/name&gt;
60740                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
60741                 &lt;/user&gt;
60742             &lt;/users&gt;
60743         &lt;/xml&gt;
60744     &lt;/nested&gt;
60745 &lt;/deeply&gt;
60746 </code></pre>
60747  *
60748  * <p><u>Response metadata</u></p>
60749  *
60750  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
60751  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
60752  * like this:</p>
60753  *
60754 <pre><code>
60755 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
60756 &lt;total&gt;100&lt;/total&gt;
60757 &lt;success&gt;true&lt;/success&gt;
60758 &lt;users&gt;
60759     &lt;user&gt;
60760         &lt;id&gt;1&lt;/id&gt;
60761         &lt;name&gt;Ed Spencer&lt;/name&gt;
60762         &lt;email&gt;ed@sencha.com&lt;/email&gt;
60763     &lt;/user&gt;
60764     &lt;user&gt;
60765         &lt;id&gt;2&lt;/id&gt;
60766         &lt;name&gt;Abe Elias&lt;/name&gt;
60767         &lt;email&gt;abe@sencha.com&lt;/email&gt;
60768     &lt;/user&gt;
60769 &lt;/users&gt;
60770 </code></pre>
60771  *
60772  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
60773  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
60774  * options:</p>
60775  *
60776 <pre><code>
60777 reader: {
60778     type: 'xml',
60779     root: 'users',
60780     totalProperty  : 'total',
60781     successProperty: 'success'
60782 }
60783 </code></pre>
60784  *
60785  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
60786  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
60787  * returned.</p>
60788  *
60789  * <p><u>Response format</u></p>
60790  *
60791  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP
60792  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
60793  * work correctly otherwise.</p>
60794  */
60795 Ext.define('Ext.data.reader.Xml', {
60796     extend: 'Ext.data.reader.Reader',
60797     alternateClassName: 'Ext.data.XmlReader',
60798     alias : 'reader.xml',
60799
60800     /**
60801      * @cfg {String} record (required)
60802      * The DomQuery path to the repeated element which contains record information.
60803      */
60804
60805     /**
60806      * @private
60807      * Creates a function to return some particular key of data from a response. The totalProperty and
60808      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
60809      * @param {String} key
60810      * @return {Function}
60811      */
60812     createAccessor: function(expr) {
60813         var me = this;
60814
60815         if (Ext.isEmpty(expr)) {
60816             return Ext.emptyFn;
60817         }
60818
60819         if (Ext.isFunction(expr)) {
60820             return expr;
60821         }
60822
60823         return function(root) {
60824             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
60825         };
60826     },
60827
60828     getNodeValue: function(node) {
60829         if (node && node.firstChild) {
60830             return node.firstChild.nodeValue;
60831         }
60832         return undefined;
60833     },
60834
60835     //inherit docs
60836     getResponseData: function(response) {
60837         var xml = response.responseXML;
60838
60839         //<debug>
60840         if (!xml) {
60841             Ext.Error.raise({
60842                 response: response,
60843                 msg: 'XML data not found in the response'
60844             });
60845         }
60846         //</debug>
60847
60848         return xml;
60849     },
60850
60851     /**
60852      * Normalizes the data object
60853      * @param {Object} data The raw data object
60854      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
60855      */
60856     getData: function(data) {
60857         return data.documentElement || data;
60858     },
60859
60860     /**
60861      * @private
60862      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
60863      * @param {Object} data The XML data object
60864      * @return {XMLElement} The root node element
60865      */
60866     getRoot: function(data) {
60867         var nodeName = data.nodeName,
60868             root     = this.root;
60869
60870         if (!root || (nodeName && nodeName == root)) {
60871             return data;
60872         } else if (Ext.DomQuery.isXml(data)) {
60873             // This fix ensures we have XML data
60874             // Related to TreeStore calling getRoot with the root node, which isn't XML
60875             // Probably should be resolved in TreeStore at some point
60876             return Ext.DomQuery.selectNode(root, data);
60877         }
60878     },
60879
60880     /**
60881      * @private
60882      * We're just preparing the data for the superclass by pulling out the record nodes we want
60883      * @param {XMLElement} root The XML root node
60884      * @return {Ext.data.Model[]} The records
60885      */
60886     extractData: function(root) {
60887         var recordName = this.record;
60888
60889         //<debug>
60890         if (!recordName) {
60891             Ext.Error.raise('Record is a required parameter');
60892         }
60893         //</debug>
60894
60895         if (recordName != root.nodeName) {
60896             root = Ext.DomQuery.select(recordName, root);
60897         } else {
60898             root = [root];
60899         }
60900         return this.callParent([root]);
60901     },
60902
60903     /**
60904      * @private
60905      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
60906      * @param {Object} data The raw data object
60907      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
60908      * @return {XMLElement} The root
60909      */
60910     getAssociatedDataRoot: function(data, associationName) {
60911         return Ext.DomQuery.select(associationName, data)[0];
60912     },
60913
60914     /**
60915      * Parses an XML document and returns a ResultSet containing the model instances
60916      * @param {Object} doc Parsed XML document
60917      * @return {Ext.data.ResultSet} The parsed result set
60918      */
60919     readRecords: function(doc) {
60920         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
60921         if (Ext.isArray(doc)) {
60922             doc = doc[0];
60923         }
60924
60925         /**
60926          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
60927          * @property xmlData
60928          * @type Object
60929          */
60930         this.xmlData = doc;
60931         return this.callParent([doc]);
60932     }
60933 });
60934 /**
60935  * @author Ed Spencer
60936  * @class Ext.data.writer.Xml
60937  * @extends Ext.data.writer.Writer
60938
60939 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
60940 The {@link #documentRoot} property is used to specify the root element in the XML document.
60941 The {@link #record} option is used to specify the element name for each record that will make
60942 up the XML document.
60943
60944  * @markdown
60945  */
60946 Ext.define('Ext.data.writer.Xml', {
60947     
60948     /* Begin Definitions */
60949     
60950     extend: 'Ext.data.writer.Writer',
60951     alternateClassName: 'Ext.data.XmlWriter',
60952     
60953     alias: 'writer.xml',
60954     
60955     /* End Definitions */
60956     
60957     /**
60958      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
60959      * If there is more than 1 record and the root is not specified, the default document root will still be used
60960      * to ensure a valid XML document is created.
60961      */
60962     documentRoot: 'xmlData',
60963     
60964     /**
60965      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
60966      * to form a valid XML document.
60967      */
60968     defaultDocumentRoot: 'xmlData',
60969
60970     /**
60971      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
60972      * Defaults to <tt>''</tt>.
60973      */
60974     header: '',
60975
60976     /**
60977      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
60978      */
60979     record: 'record',
60980
60981     //inherit docs
60982     writeRecords: function(request, data) {
60983         var me = this,
60984             xml = [],
60985             i = 0,
60986             len = data.length,
60987             root = me.documentRoot,
60988             record = me.record,
60989             needsRoot = data.length !== 1,
60990             item,
60991             key;
60992             
60993         // may not exist
60994         xml.push(me.header || '');
60995         
60996         if (!root && needsRoot) {
60997             root = me.defaultDocumentRoot;
60998         }
60999         
61000         if (root) {
61001             xml.push('<', root, '>');
61002         }
61003             
61004         for (; i < len; ++i) {
61005             item = data[i];
61006             xml.push('<', record, '>');
61007             for (key in item) {
61008                 if (item.hasOwnProperty(key)) {
61009                     xml.push('<', key, '>', item[key], '</', key, '>');
61010                 }
61011             }
61012             xml.push('</', record, '>');
61013         }
61014         
61015         if (root) {
61016             xml.push('</', root, '>');
61017         }
61018             
61019         request.xmlData = xml.join('');
61020         return request;
61021     }
61022 });
61023
61024 /**
61025  * @class Ext.direct.Event
61026  * A base class for all Ext.direct events. An event is
61027  * created after some kind of interaction with the server.
61028  * The event class is essentially just a data structure
61029  * to hold a Direct response.
61030  */
61031 Ext.define('Ext.direct.Event', {
61032
61033     /* Begin Definitions */
61034
61035     alias: 'direct.event',
61036
61037     requires: ['Ext.direct.Manager'],
61038
61039     /* End Definitions */
61040
61041     status: true,
61042
61043     /**
61044      * Creates new Event.
61045      * @param {Object} config (optional) Config object.
61046      */
61047     constructor: function(config) {
61048         Ext.apply(this, config);
61049     },
61050
61051     /**
61052      * Return the raw data for this event.
61053      * @return {Object} The data from the event
61054      */
61055     getData: function(){
61056         return this.data;
61057     }
61058 });
61059
61060 /**
61061  * @class Ext.direct.RemotingEvent
61062  * @extends Ext.direct.Event
61063  * An event that is fired when data is received from a 
61064  * {@link Ext.direct.RemotingProvider}. Contains a method to the
61065  * related transaction for the direct request, see {@link #getTransaction}
61066  */
61067 Ext.define('Ext.direct.RemotingEvent', {
61068     
61069     /* Begin Definitions */
61070    
61071     extend: 'Ext.direct.Event',
61072     
61073     alias: 'direct.rpc',
61074     
61075     /* End Definitions */
61076     
61077     /**
61078      * Get the transaction associated with this event.
61079      * @return {Ext.direct.Transaction} The transaction
61080      */
61081     getTransaction: function(){
61082         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
61083     }
61084 });
61085
61086 /**
61087  * @class Ext.direct.ExceptionEvent
61088  * @extends Ext.direct.RemotingEvent
61089  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
61090  */
61091 Ext.define('Ext.direct.ExceptionEvent', {
61092     
61093     /* Begin Definitions */
61094    
61095     extend: 'Ext.direct.RemotingEvent',
61096     
61097     alias: 'direct.exception',
61098     
61099     /* End Definitions */
61100    
61101    status: false
61102 });
61103
61104 /**
61105  * @class Ext.direct.Provider
61106  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
61107  *
61108  * <p>For example Ext JS implements the following subclasses:</p>
61109  * <pre><code>
61110 Provider
61111 |
61112 +---{@link Ext.direct.JsonProvider JsonProvider}
61113     |
61114     +---{@link Ext.direct.PollingProvider PollingProvider}
61115     |
61116     +---{@link Ext.direct.RemotingProvider RemotingProvider}
61117  * </code></pre>
61118  * @abstract
61119  */
61120 Ext.define('Ext.direct.Provider', {
61121
61122     /* Begin Definitions */
61123
61124    alias: 'direct.provider',
61125
61126     mixins: {
61127         observable: 'Ext.util.Observable'
61128     },
61129
61130     /* End Definitions */
61131
61132    /**
61133      * @cfg {String} id
61134      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
61135      * You should assign an id if you need to be able to access the provider later and you do
61136      * not have an object reference available, for example:
61137      * <pre><code>
61138 Ext.direct.Manager.addProvider({
61139     type: 'polling',
61140     url:  'php/poll.php',
61141     id:   'poll-provider'
61142 });
61143 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
61144 p.disconnect();
61145      * </code></pre>
61146      */
61147
61148     constructor : function(config){
61149         var me = this;
61150
61151         Ext.apply(me, config);
61152         me.addEvents(
61153             /**
61154              * @event connect
61155              * Fires when the Provider connects to the server-side
61156              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
61157              */
61158             'connect',
61159             /**
61160              * @event disconnect
61161              * Fires when the Provider disconnects from the server-side
61162              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
61163              */
61164             'disconnect',
61165             /**
61166              * @event data
61167              * Fires when the Provider receives data from the server-side
61168              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
61169              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
61170              */
61171             'data',
61172             /**
61173              * @event exception
61174              * Fires when the Provider receives an exception from the server-side
61175              */
61176             'exception'
61177         );
61178         me.mixins.observable.constructor.call(me, config);
61179     },
61180
61181     /**
61182      * Returns whether or not the server-side is currently connected.
61183      * Abstract method for subclasses to implement.
61184      */
61185     isConnected: function(){
61186         return false;
61187     },
61188
61189     /**
61190      * Abstract methods for subclasses to implement.
61191      * @method
61192      */
61193     connect: Ext.emptyFn,
61194
61195     /**
61196      * Abstract methods for subclasses to implement.
61197      * @method
61198      */
61199     disconnect: Ext.emptyFn
61200 });
61201
61202 /**
61203  * @class Ext.direct.JsonProvider
61204  * @extends Ext.direct.Provider
61205
61206 A base provider for communicating using JSON. This is an abstract class
61207 and should not be instanced directly.
61208
61209  * @markdown
61210  * @abstract
61211  */
61212
61213 Ext.define('Ext.direct.JsonProvider', {
61214
61215     /* Begin Definitions */
61216
61217     extend: 'Ext.direct.Provider',
61218
61219     alias: 'direct.jsonprovider',
61220
61221     uses: ['Ext.direct.ExceptionEvent'],
61222
61223     /* End Definitions */
61224
61225    /**
61226     * Parse the JSON response
61227     * @private
61228     * @param {Object} response The XHR response object
61229     * @return {Object} The data in the response.
61230     */
61231    parseResponse: function(response){
61232         if (!Ext.isEmpty(response.responseText)) {
61233             if (Ext.isObject(response.responseText)) {
61234                 return response.responseText;
61235             }
61236             return Ext.decode(response.responseText);
61237         }
61238         return null;
61239     },
61240
61241     /**
61242      * Creates a set of events based on the XHR response
61243      * @private
61244      * @param {Object} response The XHR response
61245      * @return {Ext.direct.Event[]} An array of Ext.direct.Event
61246      */
61247     createEvents: function(response){
61248         var data = null,
61249             events = [],
61250             event,
61251             i = 0,
61252             len;
61253
61254         try{
61255             data = this.parseResponse(response);
61256         } catch(e) {
61257             event = Ext.create('Ext.direct.ExceptionEvent', {
61258                 data: e,
61259                 xhr: response,
61260                 code: Ext.direct.Manager.self.exceptions.PARSE,
61261                 message: 'Error parsing json response: \n\n ' + data
61262             });
61263             return [event];
61264         }
61265
61266         if (Ext.isArray(data)) {
61267             for (len = data.length; i < len; ++i) {
61268                 events.push(this.createEvent(data[i]));
61269             }
61270         } else {
61271             events.push(this.createEvent(data));
61272         }
61273         return events;
61274     },
61275
61276     /**
61277      * Create an event from a response object
61278      * @param {Object} response The XHR response object
61279      * @return {Ext.direct.Event} The event
61280      */
61281     createEvent: function(response){
61282         return Ext.create('direct.' + response.type, response);
61283     }
61284 });
61285 /**
61286  * @class Ext.direct.PollingProvider
61287  * @extends Ext.direct.JsonProvider
61288  *
61289  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
61290  * The initial request for data originates from the client, and then is responded to by the
61291  * server.</p>
61292  * 
61293  * <p>All configurations for the PollingProvider should be generated by the server-side
61294  * API portion of the Ext.Direct stack.</p>
61295  *
61296  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
61297  * specifying <tt>type = 'polling'</tt>.  For example:</p>
61298  * <pre><code>
61299 var pollA = new Ext.direct.PollingProvider({
61300     type:'polling',
61301     url: 'php/pollA.php',
61302 });
61303 Ext.direct.Manager.addProvider(pollA);
61304 pollA.disconnect();
61305
61306 Ext.direct.Manager.addProvider(
61307     {
61308         type:'polling',
61309         url: 'php/pollB.php',
61310         id: 'pollB-provider'
61311     }
61312 );
61313 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
61314  * </code></pre>
61315  */
61316 Ext.define('Ext.direct.PollingProvider', {
61317     
61318     /* Begin Definitions */
61319     
61320     extend: 'Ext.direct.JsonProvider',
61321     
61322     alias: 'direct.pollingprovider',
61323     
61324     uses: ['Ext.direct.ExceptionEvent'],
61325     
61326     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
61327     
61328     /* End Definitions */
61329     
61330     /**
61331      * @cfg {Number} interval
61332      * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
61333      */
61334     interval: 3000,
61335
61336     /**
61337      * @cfg {Object} baseParams
61338      * An object containing properties which are to be sent as parameters on every polling request
61339      */
61340     
61341     /**
61342      * @cfg {String/Function} url
61343      * The url which the PollingProvider should contact with each request. This can also be
61344      * an imported Ext.Direct method which will accept the baseParams as its only argument.
61345      */
61346
61347     // private
61348     constructor : function(config){
61349         this.callParent(arguments);
61350         this.addEvents(
61351             /**
61352              * @event beforepoll
61353              * Fired immediately before a poll takes place, an event handler can return false
61354              * in order to cancel the poll.
61355              * @param {Ext.direct.PollingProvider} this
61356              */
61357             'beforepoll',            
61358             /**
61359              * @event poll
61360              * This event has not yet been implemented.
61361              * @param {Ext.direct.PollingProvider} this
61362              */
61363             'poll'
61364         );
61365     },
61366
61367     // inherited
61368     isConnected: function(){
61369         return !!this.pollTask;
61370     },
61371
61372     /**
61373      * Connect to the server-side and begin the polling process. To handle each
61374      * response subscribe to the data event.
61375      */
61376     connect: function(){
61377         var me = this, url = me.url;
61378         
61379         if (url && !me.pollTask) {
61380             me.pollTask = Ext.TaskManager.start({
61381                 run: function(){
61382                     if (me.fireEvent('beforepoll', me) !== false) {
61383                         if (Ext.isFunction(url)) {
61384                             url(me.baseParams);
61385                         } else {
61386                             Ext.Ajax.request({
61387                                 url: url,
61388                                 callback: me.onData,
61389                                 scope: me,
61390                                 params: me.baseParams
61391                             });
61392                         }
61393                     }
61394                 },
61395                 interval: me.interval,
61396                 scope: me
61397             });
61398             me.fireEvent('connect', me);
61399         } else if (!url) {
61400             //<debug>
61401             Ext.Error.raise('Error initializing PollingProvider, no url configured.');
61402             //</debug>
61403         }
61404     },
61405
61406     /**
61407      * Disconnect from the server-side and stop the polling process. The disconnect
61408      * event will be fired on a successful disconnect.
61409      */
61410     disconnect: function(){
61411         var me = this;
61412         
61413         if (me.pollTask) {
61414             Ext.TaskManager.stop(me.pollTask);
61415             delete me.pollTask;
61416             me.fireEvent('disconnect', me);
61417         }
61418     },
61419
61420     // private
61421     onData: function(opt, success, response){
61422         var me = this, 
61423             i = 0, 
61424             len,
61425             events;
61426         
61427         if (success) {
61428             events = me.createEvents(response);
61429             for (len = events.length; i < len; ++i) {
61430                 me.fireEvent('data', me, events[i]);
61431             }
61432         } else {
61433             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
61434                 data: null,
61435                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
61436                 message: 'Unable to connect to the server.',
61437                 xhr: response
61438             }));
61439         }
61440     }
61441 });
61442 /**
61443  * Small utility class used internally to represent a Direct method.
61444  * @class Ext.direct.RemotingMethod
61445  * @ignore
61446  */
61447 Ext.define('Ext.direct.RemotingMethod', {
61448
61449     constructor: function(config){
61450         var me = this,
61451             params = Ext.isDefined(config.params) ? config.params : config.len,
61452             name;
61453
61454         me.name = config.name;
61455         me.formHandler = config.formHandler;
61456         if (Ext.isNumber(params)) {
61457             // given only the number of parameters
61458             me.len = params;
61459             me.ordered = true;
61460         } else {
61461             /*
61462              * Given an array of either
61463              * a) String
61464              * b) Objects with a name property. We may want to encode extra info in here later
61465              */
61466             me.params = [];
61467             Ext.each(params, function(param){
61468                 name = Ext.isObject(param) ? param.name : param;
61469                 me.params.push(name);
61470             });
61471         }
61472     },
61473
61474     /**
61475      * Takes the arguments for the Direct function and splits the arguments
61476      * from the scope and the callback.
61477      * @param {Array} args The arguments passed to the direct call
61478      * @return {Object} An object with 3 properties, args, callback & scope.
61479      */
61480     getCallData: function(args){
61481         var me = this,
61482             data = null,
61483             len  = me.len,
61484             params = me.params,
61485             callback,
61486             scope,
61487             name;
61488
61489         if (me.ordered) {
61490             callback = args[len];
61491             scope = args[len + 1];
61492             if (len !== 0) {
61493                 data = args.slice(0, len);
61494             }
61495         } else {
61496             data = Ext.apply({}, args[0]);
61497             callback = args[1];
61498             scope = args[2];
61499
61500             // filter out any non-existent properties
61501             for (name in data) {
61502                 if (data.hasOwnProperty(name)) {
61503                     if (!Ext.Array.contains(params, name)) {
61504                         delete data[name];
61505                     }
61506                 }
61507             }
61508         }
61509
61510         return {
61511             data: data,
61512             callback: callback,
61513             scope: scope
61514         };
61515     }
61516 });
61517
61518 /**
61519  * Supporting Class for Ext.Direct (not intended to be used directly).
61520  */
61521 Ext.define('Ext.direct.Transaction', {
61522     
61523     /* Begin Definitions */
61524    
61525     alias: 'direct.transaction',
61526     alternateClassName: 'Ext.Direct.Transaction',
61527    
61528     statics: {
61529         TRANSACTION_ID: 0
61530     },
61531    
61532     /* End Definitions */
61533
61534     /**
61535      * Creates new Transaction.
61536      * @param {Object} [config] Config object.
61537      */
61538     constructor: function(config){
61539         var me = this;
61540         
61541         Ext.apply(me, config);
61542         me.id = ++me.self.TRANSACTION_ID;
61543         me.retryCount = 0;
61544     },
61545    
61546     send: function(){
61547          this.provider.queueTransaction(this);
61548     },
61549
61550     retry: function(){
61551         this.retryCount++;
61552         this.send();
61553     },
61554
61555     getProvider: function(){
61556         return this.provider;
61557     }
61558 });
61559
61560 /**
61561  * @class Ext.direct.RemotingProvider
61562  * @extends Ext.direct.JsonProvider
61563  * 
61564  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
61565  * server side methods on the client (a remote procedure call (RPC) type of
61566  * connection where the client can initiate a procedure on the server).</p>
61567  * 
61568  * <p>This allows for code to be organized in a fashion that is maintainable,
61569  * while providing a clear path between client and server, something that is
61570  * not always apparent when using URLs.</p>
61571  * 
61572  * <p>To accomplish this the server-side needs to describe what classes and methods
61573  * are available on the client-side. This configuration will typically be
61574  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
61575  */
61576 Ext.define('Ext.direct.RemotingProvider', {
61577     
61578     /* Begin Definitions */
61579    
61580     alias: 'direct.remotingprovider',
61581     
61582     extend: 'Ext.direct.JsonProvider', 
61583     
61584     requires: [
61585         'Ext.util.MixedCollection', 
61586         'Ext.util.DelayedTask', 
61587         'Ext.direct.Transaction',
61588         'Ext.direct.RemotingMethod'
61589     ],
61590    
61591     /* End Definitions */
61592    
61593    /**
61594      * @cfg {Object} actions
61595      * Object literal defining the server side actions and methods. For example, if
61596      * the Provider is configured with:
61597      * <pre><code>
61598 "actions":{ // each property within the 'actions' object represents a server side Class 
61599     "TestAction":[ // array of methods within each server side Class to be   
61600     {              // stubbed out on client
61601         "name":"doEcho", 
61602         "len":1            
61603     },{
61604         "name":"multiply",// name of method
61605         "len":2           // The number of parameters that will be used to create an
61606                           // array of data to send to the server side function.
61607                           // Ensure the server sends back a Number, not a String. 
61608     },{
61609         "name":"doForm",
61610         "formHandler":true, // direct the client to use specialized form handling method 
61611         "len":1
61612     }]
61613 }
61614      * </code></pre>
61615      * <p>Note that a Store is not required, a server method can be called at any time.
61616      * In the following example a <b>client side</b> handler is used to call the
61617      * server side method "multiply" in the server-side "TestAction" Class:</p>
61618      * <pre><code>
61619 TestAction.multiply(
61620     2, 4, // pass two arguments to server, so specify len=2
61621     // callback function after the server is called
61622     // result: the result returned by the server
61623     //      e: Ext.direct.RemotingEvent object
61624     function(result, e){
61625         var t = e.getTransaction();
61626         var action = t.action; // server side Class called
61627         var method = t.method; // server side method called
61628         if(e.status){
61629             var answer = Ext.encode(result); // 8
61630     
61631         }else{
61632             var msg = e.message; // failure message
61633         }
61634     }
61635 );
61636      * </code></pre>
61637      * In the example above, the server side "multiply" function will be passed two
61638      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
61639      * available as the <tt>result</tt> in the example above. 
61640      */
61641     
61642     /**
61643      * @cfg {String/Object} namespace
61644      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
61645      * Explicitly specify the namespace Object, or specify a String to have a
61646      * {@link Ext#namespace namespace created} implicitly.
61647      */
61648     
61649     /**
61650      * @cfg {String} url
61651      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
61652      */
61653     
61654     /**
61655      * @cfg {String} enableUrlEncode
61656      * Specify which param will hold the arguments for the method.
61657      * Defaults to <tt>'data'</tt>.
61658      */
61659     
61660     /**
61661      * @cfg {Number/Boolean} enableBuffer
61662      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
61663      * calls. If a number is specified this is the amount of time in milliseconds
61664      * to wait before sending a batched request.</p>
61665      * <br><p>Calls which are received within the specified timeframe will be
61666      * concatenated together and sent in a single request, optimizing the
61667      * application by reducing the amount of round trips that have to be made
61668      * to the server.</p>
61669      */
61670     enableBuffer: 10,
61671     
61672     /**
61673      * @cfg {Number} maxRetries
61674      * Number of times to re-attempt delivery on failure of a call.
61675      */
61676     maxRetries: 1,
61677     
61678     /**
61679      * @cfg {Number} timeout
61680      * The timeout to use for each request.
61681      */
61682     timeout: undefined,
61683     
61684     constructor : function(config){
61685         var me = this;
61686         me.callParent(arguments);
61687         me.addEvents(
61688             /**
61689              * @event beforecall
61690              * Fires immediately before the client-side sends off the RPC call.
61691              * By returning false from an event handler you can prevent the call from
61692              * executing.
61693              * @param {Ext.direct.RemotingProvider} provider
61694              * @param {Ext.direct.Transaction} transaction
61695              * @param {Object} meta The meta data
61696              */            
61697             'beforecall',            
61698             /**
61699              * @event call
61700              * Fires immediately after the request to the server-side is sent. This does
61701              * NOT fire after the response has come back from the call.
61702              * @param {Ext.direct.RemotingProvider} provider
61703              * @param {Ext.direct.Transaction} transaction
61704              * @param {Object} meta The meta data
61705              */            
61706             'call'
61707         );
61708         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
61709         me.transactions = Ext.create('Ext.util.MixedCollection');
61710         me.callBuffer = [];
61711     },
61712     
61713     /**
61714      * Initialize the API
61715      * @private
61716      */
61717     initAPI : function(){
61718         var actions = this.actions,
61719             namespace = this.namespace,
61720             action,
61721             cls,
61722             methods,
61723             i,
61724             len,
61725             method;
61726             
61727         for (action in actions) {
61728             cls = namespace[action];
61729             if (!cls) {
61730                 cls = namespace[action] = {};
61731             }
61732             methods = actions[action];
61733             
61734             for (i = 0, len = methods.length; i < len; ++i) {
61735                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
61736                 cls[method.name] = this.createHandler(action, method);
61737             }
61738         }
61739     },
61740     
61741     /**
61742      * Create a handler function for a direct call.
61743      * @private
61744      * @param {String} action The action the call is for
61745      * @param {Object} method The details of the method
61746      * @return {Function} A JS function that will kick off the call
61747      */
61748     createHandler : function(action, method){
61749         var me = this,
61750             handler;
61751         
61752         if (!method.formHandler) {
61753             handler = function(){
61754                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
61755             };
61756         } else {
61757             handler = function(form, callback, scope){
61758                 me.configureFormRequest(action, method, form, callback, scope);
61759             };
61760         }
61761         handler.directCfg = {
61762             action: action,
61763             method: method
61764         };
61765         return handler;
61766     },
61767     
61768     // inherit docs
61769     isConnected: function(){
61770         return !!this.connected;
61771     },
61772
61773     // inherit docs
61774     connect: function(){
61775         var me = this;
61776         
61777         if (me.url) {
61778             me.initAPI();
61779             me.connected = true;
61780             me.fireEvent('connect', me);
61781         } else if(!me.url) {
61782             //<debug>
61783             Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
61784             //</debug>
61785         }
61786     },
61787
61788     // inherit docs
61789     disconnect: function(){
61790         var me = this;
61791         
61792         if (me.connected) {
61793             me.connected = false;
61794             me.fireEvent('disconnect', me);
61795         }
61796     },
61797     
61798     /**
61799      * Run any callbacks related to the transaction.
61800      * @private
61801      * @param {Ext.direct.Transaction} transaction The transaction
61802      * @param {Ext.direct.Event} event The event
61803      */
61804     runCallback: function(transaction, event){
61805         var funcName = event.status ? 'success' : 'failure',
61806             callback,
61807             result;
61808         
61809         if (transaction && transaction.callback) {
61810             callback = transaction.callback;
61811             result = Ext.isDefined(event.result) ? event.result : event.data;
61812         
61813             if (Ext.isFunction(callback)) {
61814                 callback(result, event);
61815             } else {
61816                 Ext.callback(callback[funcName], callback.scope, [result, event]);
61817                 Ext.callback(callback.callback, callback.scope, [result, event]);
61818             }
61819         }
61820     },
61821     
61822     /**
61823      * React to the ajax request being completed
61824      * @private
61825      */
61826     onData: function(options, success, response){
61827         var me = this,
61828             i = 0,
61829             len,
61830             events,
61831             event,
61832             transaction,
61833             transactions;
61834             
61835         if (success) {
61836             events = me.createEvents(response);
61837             for (len = events.length; i < len; ++i) {
61838                 event = events[i];
61839                 transaction = me.getTransaction(event);
61840                 me.fireEvent('data', me, event);
61841                 if (transaction) {
61842                     me.runCallback(transaction, event, true);
61843                     Ext.direct.Manager.removeTransaction(transaction);
61844                 }
61845             }
61846         } else {
61847             transactions = [].concat(options.transaction);
61848             for (len = transactions.length; i < len; ++i) {
61849                 transaction = me.getTransaction(transactions[i]);
61850                 if (transaction && transaction.retryCount < me.maxRetries) {
61851                     transaction.retry();
61852                 } else {
61853                     event = Ext.create('Ext.direct.ExceptionEvent', {
61854                         data: null,
61855                         transaction: transaction,
61856                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
61857                         message: 'Unable to connect to the server.',
61858                         xhr: response
61859                     });
61860                     me.fireEvent('data', me, event);
61861                     if (transaction) {
61862                         me.runCallback(transaction, event, false);
61863                         Ext.direct.Manager.removeTransaction(transaction);
61864                     }
61865                 }
61866             }
61867         }
61868     },
61869     
61870     /**
61871      * Get transaction from XHR options
61872      * @private
61873      * @param {Object} options The options sent to the Ajax request
61874      * @return {Ext.direct.Transaction} The transaction, null if not found
61875      */
61876     getTransaction: function(options){
61877         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
61878     },
61879     
61880     /**
61881      * Configure a direct request
61882      * @private
61883      * @param {String} action The action being executed
61884      * @param {Object} method The being executed
61885      */
61886     configureRequest: function(action, method, args){
61887         var me = this,
61888             callData = method.getCallData(args),
61889             data = callData.data, 
61890             callback = callData.callback, 
61891             scope = callData.scope,
61892             transaction;
61893
61894         transaction = Ext.create('Ext.direct.Transaction', {
61895             provider: me,
61896             args: args,
61897             action: action,
61898             method: method.name,
61899             data: data,
61900             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
61901         });
61902
61903         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
61904             Ext.direct.Manager.addTransaction(transaction);
61905             me.queueTransaction(transaction);
61906             me.fireEvent('call', me, transaction, method);
61907         }
61908     },
61909     
61910     /**
61911      * Gets the Ajax call info for a transaction
61912      * @private
61913      * @param {Ext.direct.Transaction} transaction The transaction
61914      * @return {Object} The call params
61915      */
61916     getCallData: function(transaction){
61917         return {
61918             action: transaction.action,
61919             method: transaction.method,
61920             data: transaction.data,
61921             type: 'rpc',
61922             tid: transaction.id
61923         };
61924     },
61925     
61926     /**
61927      * Sends a request to the server
61928      * @private
61929      * @param {Object/Array} data The data to send
61930      */
61931     sendRequest : function(data){
61932         var me = this,
61933             request = {
61934                 url: me.url,
61935                 callback: me.onData,
61936                 scope: me,
61937                 transaction: data,
61938                 timeout: me.timeout
61939             }, callData,
61940             enableUrlEncode = me.enableUrlEncode,
61941             i = 0,
61942             len,
61943             params;
61944             
61945
61946         if (Ext.isArray(data)) {
61947             callData = [];
61948             for (len = data.length; i < len; ++i) {
61949                 callData.push(me.getCallData(data[i]));
61950             }
61951         } else {
61952             callData = me.getCallData(data);
61953         }
61954
61955         if (enableUrlEncode) {
61956             params = {};
61957             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
61958             request.params = params;
61959         } else {
61960             request.jsonData = callData;
61961         }
61962         Ext.Ajax.request(request);
61963     },
61964     
61965     /**
61966      * Add a new transaction to the queue
61967      * @private
61968      * @param {Ext.direct.Transaction} transaction The transaction
61969      */
61970     queueTransaction: function(transaction){
61971         var me = this,
61972             enableBuffer = me.enableBuffer;
61973         
61974         if (transaction.form) {
61975             me.sendFormRequest(transaction);
61976             return;
61977         }
61978         
61979         me.callBuffer.push(transaction);
61980         if (enableBuffer) {
61981             if (!me.callTask) {
61982                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
61983             }
61984             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
61985         } else {
61986             me.combineAndSend();
61987         }
61988     },
61989     
61990     /**
61991      * Combine any buffered requests and send them off
61992      * @private
61993      */
61994     combineAndSend : function(){
61995         var buffer = this.callBuffer,
61996             len = buffer.length;
61997             
61998         if (len > 0) {
61999             this.sendRequest(len == 1 ? buffer[0] : buffer);
62000             this.callBuffer = [];
62001         }
62002     },
62003     
62004     /**
62005      * Configure a form submission request
62006      * @private
62007      * @param {String} action The action being executed
62008      * @param {Object} method The method being executed
62009      * @param {HTMLElement} form The form being submitted
62010      * @param {Function} callback (optional) A callback to run after the form submits
62011      * @param {Object} scope (optional) A scope to execute the callback in
62012      */
62013     configureFormRequest : function(action, method, form, callback, scope){
62014         var me = this,
62015             transaction = Ext.create('Ext.direct.Transaction', {
62016                 provider: me,
62017                 action: action,
62018                 method: method.name,
62019                 args: [form, callback, scope],
62020                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
62021                 isForm: true
62022             }),
62023             isUpload,
62024             params;
62025
62026         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
62027             Ext.direct.Manager.addTransaction(transaction);
62028             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
62029             
62030             params = {
62031                 extTID: transaction.id,
62032                 extAction: action,
62033                 extMethod: method.name,
62034                 extType: 'rpc',
62035                 extUpload: String(isUpload)
62036             };
62037             
62038             // change made from typeof callback check to callback.params
62039             // to support addl param passing in DirectSubmit EAC 6/2
62040             Ext.apply(transaction, {
62041                 form: Ext.getDom(form),
62042                 isUpload: isUpload,
62043                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
62044             });
62045             me.fireEvent('call', me, transaction, method);
62046             me.sendFormRequest(transaction);
62047         }
62048     },
62049     
62050     /**
62051      * Sends a form request
62052      * @private
62053      * @param {Ext.direct.Transaction} transaction The transaction to send
62054      */
62055     sendFormRequest: function(transaction){
62056         Ext.Ajax.request({
62057             url: this.url,
62058             params: transaction.params,
62059             callback: this.onData,
62060             scope: this,
62061             form: transaction.form,
62062             isUpload: transaction.isUpload,
62063             transaction: transaction
62064         });
62065     }
62066     
62067 });
62068
62069 /*
62070  * @class Ext.draw.Matrix
62071  * @private
62072  */
62073 Ext.define('Ext.draw.Matrix', {
62074
62075     /* Begin Definitions */
62076
62077     requires: ['Ext.draw.Draw'],
62078
62079     /* End Definitions */
62080
62081     constructor: function(a, b, c, d, e, f) {
62082         if (a != null) {
62083             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
62084         }
62085         else {
62086             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
62087         }
62088     },
62089
62090     add: function(a, b, c, d, e, f) {
62091         var me = this,
62092             out = [[], [], []],
62093             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
62094             x,
62095             y,
62096             z,
62097             res;
62098
62099         for (x = 0; x < 3; x++) {
62100             for (y = 0; y < 3; y++) {
62101                 res = 0;
62102                 for (z = 0; z < 3; z++) {
62103                     res += me.matrix[x][z] * matrix[z][y];
62104                 }
62105                 out[x][y] = res;
62106             }
62107         }
62108         me.matrix = out;
62109     },
62110
62111     prepend: function(a, b, c, d, e, f) {
62112         var me = this,
62113             out = [[], [], []],
62114             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
62115             x,
62116             y,
62117             z,
62118             res;
62119
62120         for (x = 0; x < 3; x++) {
62121             for (y = 0; y < 3; y++) {
62122                 res = 0;
62123                 for (z = 0; z < 3; z++) {
62124                     res += matrix[x][z] * me.matrix[z][y];
62125                 }
62126                 out[x][y] = res;
62127             }
62128         }
62129         me.matrix = out;
62130     },
62131
62132     invert: function() {
62133         var matrix = this.matrix,
62134             a = matrix[0][0],
62135             b = matrix[1][0],
62136             c = matrix[0][1],
62137             d = matrix[1][1],
62138             e = matrix[0][2],
62139             f = matrix[1][2],
62140             x = a * d - b * c;
62141         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
62142     },
62143
62144     clone: function() {
62145         var matrix = this.matrix,
62146             a = matrix[0][0],
62147             b = matrix[1][0],
62148             c = matrix[0][1],
62149             d = matrix[1][1],
62150             e = matrix[0][2],
62151             f = matrix[1][2];
62152         return new Ext.draw.Matrix(a, b, c, d, e, f);
62153     },
62154
62155     translate: function(x, y) {
62156         this.prepend(1, 0, 0, 1, x, y);
62157     },
62158
62159     scale: function(x, y, cx, cy) {
62160         var me = this;
62161         if (y == null) {
62162             y = x;
62163         }
62164         me.add(1, 0, 0, 1, cx, cy);
62165         me.add(x, 0, 0, y, 0, 0);
62166         me.add(1, 0, 0, 1, -cx, -cy);
62167     },
62168
62169     rotate: function(a, x, y) {
62170         a = Ext.draw.Draw.rad(a);
62171         var me = this,
62172             cos = +Math.cos(a).toFixed(9),
62173             sin = +Math.sin(a).toFixed(9);
62174         me.add(cos, sin, -sin, cos, x, y);
62175         me.add(1, 0, 0, 1, -x, -y);
62176     },
62177
62178     x: function(x, y) {
62179         var matrix = this.matrix;
62180         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
62181     },
62182
62183     y: function(x, y) {
62184         var matrix = this.matrix;
62185         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
62186     },
62187
62188     get: function(i, j) {
62189         return + this.matrix[i][j].toFixed(4);
62190     },
62191
62192     toString: function() {
62193         var me = this;
62194         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
62195     },
62196
62197     toSvg: function() {
62198         var me = this;
62199         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() + ")";
62200     },
62201
62202     toFilter: function() {
62203         var me = this;
62204         return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',FilterType=bilinear,M11=" + me.get(0, 0) +
62205             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
62206             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
62207     },
62208
62209     offset: function() {
62210         var matrix = this.matrix;
62211         return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
62212     },
62213
62214     // Split matrix into Translate Scale, Shear, and Rotate
62215     split: function () {
62216         function norm(a) {
62217             return a[0] * a[0] + a[1] * a[1];
62218         }
62219         function normalize(a) {
62220             var mag = Math.sqrt(norm(a));
62221             a[0] /= mag;
62222             a[1] /= mag;
62223         }
62224         var matrix = this.matrix,
62225             out = {
62226                 translateX: matrix[0][2],
62227                 translateY: matrix[1][2]
62228             },
62229             row;
62230
62231         // scale and shear
62232         row = [[matrix[0][0], matrix[0][1]], [matrix[1][0], matrix[1][1]]];
62233         out.scaleX = Math.sqrt(norm(row[0]));
62234         normalize(row[0]);
62235
62236         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
62237         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
62238
62239         out.scaleY = Math.sqrt(norm(row[1]));
62240         normalize(row[1]);
62241         out.shear /= out.scaleY;
62242
62243         // rotation
62244         out.rotate = Math.asin(-row[0][1]);
62245
62246         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
62247
62248         return out;
62249     }
62250 });
62251
62252 // private - DD implementation for Panels
62253 Ext.define('Ext.draw.SpriteDD', {
62254     extend: 'Ext.dd.DragSource',
62255
62256     constructor : function(sprite, cfg){
62257         var me = this,
62258             el = sprite.el;
62259         me.sprite = sprite;
62260         me.el = el;
62261         me.dragData = {el: el, sprite: sprite};
62262         me.callParent([el, cfg]);
62263         me.sprite.setStyle('cursor', 'move');
62264     },
62265
62266     showFrame: Ext.emptyFn,
62267     createFrame : Ext.emptyFn,
62268
62269     getDragEl : function(e){
62270         return this.el;
62271     },
62272     
62273     getRegion: function() {
62274         var me = this,
62275             el = me.el,
62276             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
62277         
62278         sprite = me.sprite;
62279         bbox = sprite.getBBox();
62280         
62281         try {
62282             pos = Ext.Element.getXY(el);
62283         } catch (e) { }
62284
62285         if (!pos) {
62286             return null;
62287         }
62288
62289         x1 = pos[0];
62290         x2 = x1 + bbox.width;
62291         y1 = pos[1];
62292         y2 = y1 + bbox.height;
62293         
62294         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
62295     },
62296
62297     /*
62298       TODO(nico): Cumulative translations in VML are handled
62299       differently than in SVG. While in SVG we specify the translation
62300       relative to the original x, y position attributes, in VML the translation
62301       is a delta between the last position of the object (modified by the last
62302       translation) and the new one.
62303       
62304       In VML the translation alters the position
62305       of the object, we should change that or alter the SVG impl.
62306     */
62307      
62308     startDrag: function(x, y) {
62309         var me = this,
62310             attr = me.sprite.attr;
62311         me.prev = me.sprite.surface.transformToViewBox(x, y);
62312     },
62313
62314     onDrag: function(e) {
62315         var xy = e.getXY(),
62316             me = this,
62317             sprite = me.sprite,
62318             attr = sprite.attr, dx, dy;
62319         xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
62320         dx = xy[0] - me.prev[0];
62321         dy = xy[1] - me.prev[1];
62322         sprite.setAttributes({
62323             translate: {
62324                 x: attr.translation.x + dx,
62325                 y: attr.translation.y + dy
62326             }
62327         }, true);
62328         me.prev = xy;
62329     },
62330
62331     setDragElPos: function () {
62332         // Disable automatic DOM move in DD that spoils layout of VML engine.
62333         return false;
62334     }
62335 });
62336 /**
62337  * A Sprite is an object rendered in a Drawing surface.
62338  *
62339  * # Translation
62340  *
62341  * For translate, the configuration object contains x and y attributes that indicate where to
62342  * translate the object. For example:
62343  *
62344  *     sprite.setAttributes({
62345  *       translate: {
62346  *        x: 10,
62347  *        y: 10
62348  *       }
62349  *     }, true);
62350  *
62351  *
62352  * # Rotation
62353  *
62354  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
62355  * and a `degrees` attribute that specifies the rotation in degrees. For example:
62356  *
62357  *     sprite.setAttributes({
62358  *       rotate: {
62359  *        degrees: 90
62360  *       }
62361  *     }, true);
62362  *
62363  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
62364  *
62365  *     sprite.setAttributes({
62366  *       rotate: {
62367  *        x: 0,
62368  *        y: 0,
62369  *        degrees: 90
62370  *       }
62371  *     }, true);
62372  *
62373  * will create a rotation around the `(0, 0)` axis.
62374  *
62375  *
62376  * # Scaling
62377  *
62378  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
62379  *
62380  *     sprite.setAttributes({
62381  *       scale: {
62382  *        x: 10,
62383  *        y: 3
62384  *       }
62385  *     }, true);
62386  *
62387  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
62388  *
62389  *     sprite.setAttributes({
62390  *       scale: {
62391  *        cx: 0,
62392  *        cy: 0,
62393  *        x: 10,
62394  *        y: 3
62395  *       }
62396  *     }, true);
62397  *
62398  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
62399  *
62400  *
62401  * # Creating and adding a Sprite to a Surface
62402  *
62403  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
62404  *
62405  *     var drawComponent = Ext.create('Ext.draw.Component', options here...);
62406  *
62407  *     var sprite = Ext.create('Ext.draw.Sprite', {
62408  *         type: 'circle',
62409  *         fill: '#ff0',
62410  *         surface: drawComponent.surface,
62411  *         radius: 5
62412  *     });
62413  *
62414  * Sprites can also be added to the surface as a configuration object:
62415  *
62416  *     var sprite = drawComponent.surface.add({
62417  *         type: 'circle',
62418  *         fill: '#ff0',
62419  *         radius: 5
62420  *     });
62421  *
62422  * In order to properly apply properties and render the sprite we have to
62423  * `show` the sprite setting the option `redraw` to `true`:
62424  *
62425  *     sprite.show(true);
62426  *
62427  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
62428  * add method to append a new sprite to the canvas. For example:
62429  *
62430  *     drawComponent.surface.add({
62431  *         type: 'circle',
62432  *         fill: '#ffc',
62433  *         radius: 100,
62434  *         x: 100,
62435  *         y: 100
62436  *     });
62437  */
62438 Ext.define('Ext.draw.Sprite', {
62439
62440     /* Begin Definitions */
62441
62442     mixins: {
62443         observable: 'Ext.util.Observable',
62444         animate: 'Ext.util.Animate'
62445     },
62446
62447     requires: ['Ext.draw.SpriteDD'],
62448
62449     /* End Definitions */
62450
62451     /**
62452      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
62453      */
62454
62455     /**
62456      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
62457      */
62458
62459     /**
62460      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
62461      */
62462
62463     /**
62464      * @cfg {Number} size Used in square sprites, the dimension of the square
62465      */
62466
62467     /**
62468      * @cfg {Number} radius Used in circle sprites, the radius of the circle
62469      */
62470
62471     /**
62472      * @cfg {Number} x The position along the x-axis
62473      */
62474
62475     /**
62476      * @cfg {Number} y The position along the y-axis
62477      */
62478
62479     /**
62480      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
62481      */
62482
62483     /**
62484      * @cfg {Number} opacity The opacity of the sprite
62485      */
62486
62487     /**
62488      * @cfg {String} fill The fill color
62489      */
62490
62491     /**
62492      * @cfg {String} stroke The stroke color
62493      */
62494
62495     /**
62496      * @cfg {Number} stroke-width The width of the stroke
62497      */
62498
62499     /**
62500      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
62501      */
62502
62503     /**
62504      * @cfg {String} text Used with text type sprites. The text itself
62505      */
62506
62507     /**
62508      * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
62509      * {@link Ext.draw.Surface}
62510      */
62511
62512     /**
62513      * @cfg {Boolean} draggable True to make the sprite draggable.
62514      */
62515
62516     dirty: false,
62517     dirtyHidden: false,
62518     dirtyTransform: false,
62519     dirtyPath: true,
62520     dirtyFont: true,
62521     zIndexDirty: true,
62522     isSprite: true,
62523     zIndex: 0,
62524     fontProperties: [
62525         'font',
62526         'font-size',
62527         'font-weight',
62528         'font-style',
62529         'font-family',
62530         'text-anchor',
62531         'text'
62532     ],
62533     pathProperties: [
62534         'x',
62535         'y',
62536         'd',
62537         'path',
62538         'height',
62539         'width',
62540         'radius',
62541         'r',
62542         'rx',
62543         'ry',
62544         'cx',
62545         'cy'
62546     ],
62547     constructor: function(config) {
62548         var me = this;
62549         config = config || {};
62550         me.id = Ext.id(null, 'ext-sprite-');
62551         me.transformations = [];
62552         Ext.copyTo(this, config, 'surface,group,type,draggable');
62553         //attribute bucket
62554         me.bbox = {};
62555         me.attr = {
62556             zIndex: 0,
62557             translation: {
62558                 x: null,
62559                 y: null
62560             },
62561             rotation: {
62562                 degrees: null,
62563                 x: null,
62564                 y: null
62565             },
62566             scaling: {
62567                 x: null,
62568                 y: null,
62569                 cx: null,
62570                 cy: null
62571             }
62572         };
62573         //delete not bucket attributes
62574         delete config.surface;
62575         delete config.group;
62576         delete config.type;
62577         delete config.draggable;
62578         me.setAttributes(config);
62579         me.addEvents(
62580             'beforedestroy',
62581             'destroy',
62582             'render',
62583             'mousedown',
62584             'mouseup',
62585             'mouseover',
62586             'mouseout',
62587             'mousemove',
62588             'click'
62589         );
62590         me.mixins.observable.constructor.apply(this, arguments);
62591     },
62592
62593     /**
62594      * @property {Ext.dd.DragSource} dd
62595      * If this Sprite is configured {@link #draggable}, this property will contain
62596      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
62597      *
62598      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
62599      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
62600      */
62601
62602     initDraggable: function() {
62603         var me = this;
62604         me.draggable = true;
62605         //create element if it doesn't exist.
62606         if (!me.el) {
62607             me.surface.createSpriteElement(me);
62608         }
62609         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
62610         me.on('beforedestroy', me.dd.destroy, me.dd);
62611     },
62612
62613     /**
62614      * Change the attributes of the sprite.
62615      * @param {Object} attrs attributes to be changed on the sprite.
62616      * @param {Boolean} redraw Flag to immediatly draw the change.
62617      * @return {Ext.draw.Sprite} this
62618      */
62619     setAttributes: function(attrs, redraw) {
62620         var me = this,
62621             fontProps = me.fontProperties,
62622             fontPropsLength = fontProps.length,
62623             pathProps = me.pathProperties,
62624             pathPropsLength = pathProps.length,
62625             hasSurface = !!me.surface,
62626             custom = hasSurface && me.surface.customAttributes || {},
62627             spriteAttrs = me.attr,
62628             attr, i, translate, translation, rotate, rotation, scale, scaling;
62629
62630         attrs = Ext.apply({}, attrs);
62631         for (attr in custom) {
62632             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
62633                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
62634             }
62635         }
62636
62637         // Flag a change in hidden
62638         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
62639             me.dirtyHidden = true;
62640         }
62641
62642         // Flag path change
62643         for (i = 0; i < pathPropsLength; i++) {
62644             attr = pathProps[i];
62645             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
62646                 me.dirtyPath = true;
62647                 break;
62648             }
62649         }
62650
62651         // Flag zIndex change
62652         if ('zIndex' in attrs) {
62653             me.zIndexDirty = true;
62654         }
62655
62656         // Flag font/text change
62657         for (i = 0; i < fontPropsLength; i++) {
62658             attr = fontProps[i];
62659             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
62660                 me.dirtyFont = true;
62661                 break;
62662             }
62663         }
62664
62665         translate = attrs.translate;
62666         translation = spriteAttrs.translation;
62667         if (translate) {
62668             if ((translate.x && translate.x !== translation.x) ||
62669                 (translate.y && translate.y !== translation.y)) {
62670                 Ext.apply(translation, translate);
62671                 me.dirtyTransform = true;
62672             }
62673             delete attrs.translate;
62674         }
62675
62676         rotate = attrs.rotate;
62677         rotation = spriteAttrs.rotation;
62678         if (rotate) {
62679             if ((rotate.x && rotate.x !== rotation.x) ||
62680                 (rotate.y && rotate.y !== rotation.y) ||
62681                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
62682                 Ext.apply(rotation, rotate);
62683                 me.dirtyTransform = true;
62684             }
62685             delete attrs.rotate;
62686         }
62687
62688         scale = attrs.scale;
62689         scaling = spriteAttrs.scaling;
62690         if (scale) {
62691             if ((scale.x && scale.x !== scaling.x) ||
62692                 (scale.y && scale.y !== scaling.y) ||
62693                 (scale.cx && scale.cx !== scaling.cx) ||
62694                 (scale.cy && scale.cy !== scaling.cy)) {
62695                 Ext.apply(scaling, scale);
62696                 me.dirtyTransform = true;
62697             }
62698             delete attrs.scale;
62699         }
62700
62701         Ext.apply(spriteAttrs, attrs);
62702         me.dirty = true;
62703
62704         if (redraw === true && hasSurface) {
62705             me.redraw();
62706         }
62707         return this;
62708     },
62709
62710     /**
62711      * Retrieves the bounding box of the sprite.
62712      * This will be returned as an object with x, y, width, and height properties.
62713      * @return {Object} bbox
62714      */
62715     getBBox: function() {
62716         return this.surface.getBBox(this);
62717     },
62718
62719     setText: function(text) {
62720         return this.surface.setText(this, text);
62721     },
62722
62723     /**
62724      * Hides the sprite.
62725      * @param {Boolean} redraw Flag to immediatly draw the change.
62726      * @return {Ext.draw.Sprite} this
62727      */
62728     hide: function(redraw) {
62729         this.setAttributes({
62730             hidden: true
62731         }, redraw);
62732         return this;
62733     },
62734
62735     /**
62736      * Shows the sprite.
62737      * @param {Boolean} redraw Flag to immediatly draw the change.
62738      * @return {Ext.draw.Sprite} this
62739      */
62740     show: function(redraw) {
62741         this.setAttributes({
62742             hidden: false
62743         }, redraw);
62744         return this;
62745     },
62746
62747     /**
62748      * Removes the sprite.
62749      */
62750     remove: function() {
62751         if (this.surface) {
62752             this.surface.remove(this);
62753             return true;
62754         }
62755         return false;
62756     },
62757
62758     onRemove: function() {
62759         this.surface.onRemove(this);
62760     },
62761
62762     /**
62763      * Removes the sprite and clears all listeners.
62764      */
62765     destroy: function() {
62766         var me = this;
62767         if (me.fireEvent('beforedestroy', me) !== false) {
62768             me.remove();
62769             me.surface.onDestroy(me);
62770             me.clearListeners();
62771             me.fireEvent('destroy');
62772         }
62773     },
62774
62775     /**
62776      * Redraws the sprite.
62777      * @return {Ext.draw.Sprite} this
62778      */
62779     redraw: function() {
62780         this.surface.renderItem(this);
62781         return this;
62782     },
62783
62784     /**
62785      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
62786      * @param {String/Object} property The style property to be set, or an object of multiple styles.
62787      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
62788      * @return {Ext.draw.Sprite} this
62789      */
62790     setStyle: function() {
62791         this.el.setStyle.apply(this.el, arguments);
62792         return this;
62793     },
62794
62795     /**
62796      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
62797      * is severly limited in VML.
62798      * @param {String/String[]} className The CSS class to add, or an array of classes
62799      * @return {Ext.draw.Sprite} this
62800      */
62801     addCls: function(obj) {
62802         this.surface.addCls(this, obj);
62803         return this;
62804     },
62805
62806     /**
62807      * Removes one or more CSS classes from the element.
62808      * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
62809      * is severly limited in VML.
62810      * @return {Ext.draw.Sprite} this
62811      */
62812     removeCls: function(obj) {
62813         this.surface.removeCls(this, obj);
62814         return this;
62815     }
62816 });
62817
62818 /**
62819  * @class Ext.draw.engine.Svg
62820  * @extends Ext.draw.Surface
62821  * Provides specific methods to draw with SVG.
62822  */
62823 Ext.define('Ext.draw.engine.Svg', {
62824
62825     /* Begin Definitions */
62826
62827     extend: 'Ext.draw.Surface',
62828
62829     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
62830
62831     /* End Definitions */
62832
62833     engine: 'Svg',
62834
62835     trimRe: /^\s+|\s+$/g,
62836     spacesRe: /\s+/,
62837     xlink: "http:/" + "/www.w3.org/1999/xlink",
62838
62839     translateAttrs: {
62840         radius: "r",
62841         radiusX: "rx",
62842         radiusY: "ry",
62843         path: "d",
62844         lineWidth: "stroke-width",
62845         fillOpacity: "fill-opacity",
62846         strokeOpacity: "stroke-opacity",
62847         strokeLinejoin: "stroke-linejoin"
62848     },
62849     
62850     parsers: {},
62851
62852     minDefaults: {
62853         circle: {
62854             cx: 0,
62855             cy: 0,
62856             r: 0,
62857             fill: "none",
62858             stroke: null,
62859             "stroke-width": null,
62860             opacity: null,
62861             "fill-opacity": null,
62862             "stroke-opacity": null
62863         },
62864         ellipse: {
62865             cx: 0,
62866             cy: 0,
62867             rx: 0,
62868             ry: 0,
62869             fill: "none",
62870             stroke: null,
62871             "stroke-width": null,
62872             opacity: null,
62873             "fill-opacity": null,
62874             "stroke-opacity": null
62875         },
62876         rect: {
62877             x: 0,
62878             y: 0,
62879             width: 0,
62880             height: 0,
62881             rx: 0,
62882             ry: 0,
62883             fill: "none",
62884             stroke: null,
62885             "stroke-width": null,
62886             opacity: null,
62887             "fill-opacity": null,
62888             "stroke-opacity": null
62889         },
62890         text: {
62891             x: 0,
62892             y: 0,
62893             "text-anchor": "start",
62894             "font-family": null,
62895             "font-size": null,
62896             "font-weight": null,
62897             "font-style": null,
62898             fill: "#000",
62899             stroke: null,
62900             "stroke-width": null,
62901             opacity: null,
62902             "fill-opacity": null,
62903             "stroke-opacity": null
62904         },
62905         path: {
62906             d: "M0,0",
62907             fill: "none",
62908             stroke: null,
62909             "stroke-width": null,
62910             opacity: null,
62911             "fill-opacity": null,
62912             "stroke-opacity": null
62913         },
62914         image: {
62915             x: 0,
62916             y: 0,
62917             width: 0,
62918             height: 0,
62919             preserveAspectRatio: "none",
62920             opacity: null
62921         }
62922     },
62923
62924     createSvgElement: function(type, attrs) {
62925         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
62926             key;
62927         if (attrs) {
62928             for (key in attrs) {
62929                 el.setAttribute(key, String(attrs[key]));
62930             }
62931         }
62932         return el;
62933     },
62934
62935     createSpriteElement: function(sprite) {
62936         // Create svg element and append to the DOM.
62937         var el = this.createSvgElement(sprite.type);
62938         el.id = sprite.id;
62939         if (el.style) {
62940             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
62941         }
62942         sprite.el = Ext.get(el);
62943         this.applyZIndex(sprite); //performs the insertion
62944         sprite.matrix = Ext.create('Ext.draw.Matrix');
62945         sprite.bbox = {
62946             plain: 0,
62947             transform: 0
62948         };
62949         sprite.fireEvent("render", sprite);
62950         return el;
62951     },
62952
62953     getBBox: function (sprite, isWithoutTransform) {
62954         var realPath = this["getPath" + sprite.type](sprite);
62955         if (isWithoutTransform) {
62956             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
62957             return sprite.bbox.plain;
62958         }
62959         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
62960         return sprite.bbox.transform;
62961     },
62962     
62963     getBBoxText: function (sprite) {
62964         var bbox = {},
62965             bb, height, width, i, ln, el;
62966
62967         if (sprite && sprite.el) {
62968             el = sprite.el.dom;
62969             try {
62970                 bbox = el.getBBox();
62971                 return bbox;
62972             } catch(e) {
62973                 // Firefox 3.0.x plays badly here
62974             }
62975             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
62976             ln = el.getNumberOfChars();
62977             for (i = 0; i < ln; i++) {
62978                 bb = el.getExtentOfChar(i);
62979                 bbox.y = Math.min(bb.y, bbox.y);
62980                 height = bb.y + bb.height - bbox.y;
62981                 bbox.height = Math.max(bbox.height, height);
62982                 width = bb.x + bb.width - bbox.x;
62983                 bbox.width = Math.max(bbox.width, width);
62984             }
62985             return bbox;
62986         }
62987     },
62988
62989     hide: function() {
62990         Ext.get(this.el).hide();
62991     },
62992
62993     show: function() {
62994         Ext.get(this.el).show();
62995     },
62996
62997     hidePrim: function(sprite) {
62998         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
62999     },
63000
63001     showPrim: function(sprite) {
63002         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
63003     },
63004
63005     getDefs: function() {
63006         return this._defs || (this._defs = this.createSvgElement("defs"));
63007     },
63008
63009     transform: function(sprite) {
63010         var me = this,
63011             matrix = Ext.create('Ext.draw.Matrix'),
63012             transforms = sprite.transformations,
63013             transformsLength = transforms.length,
63014             i = 0,
63015             transform, type;
63016             
63017         for (; i < transformsLength; i++) {
63018             transform = transforms[i];
63019             type = transform.type;
63020             if (type == "translate") {
63021                 matrix.translate(transform.x, transform.y);
63022             }
63023             else if (type == "rotate") {
63024                 matrix.rotate(transform.degrees, transform.x, transform.y);
63025             }
63026             else if (type == "scale") {
63027                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
63028             }
63029         }
63030         sprite.matrix = matrix;
63031         sprite.el.set({transform: matrix.toSvg()});
63032     },
63033
63034     setSize: function(w, h) {
63035         var me = this,
63036             el = me.el;
63037         
63038         w = +w || me.width;
63039         h = +h || me.height;
63040         me.width = w;
63041         me.height = h;
63042
63043         el.setSize(w, h);
63044         el.set({
63045             width: w,
63046             height: h
63047         });
63048         me.callParent([w, h]);
63049     },
63050
63051     /**
63052      * Get the region for the surface's canvas area
63053      * @returns {Ext.util.Region}
63054      */
63055     getRegion: function() {
63056         // Mozilla requires using the background rect because the svg element returns an
63057         // incorrect region. Webkit gives no region for the rect and must use the svg element.
63058         var svgXY = this.el.getXY(),
63059             rectXY = this.bgRect.getXY(),
63060             max = Math.max,
63061             x = max(svgXY[0], rectXY[0]),
63062             y = max(svgXY[1], rectXY[1]);
63063         return {
63064             left: x,
63065             top: y,
63066             right: x + this.width,
63067             bottom: y + this.height
63068         };
63069     },
63070
63071     onRemove: function(sprite) {
63072         if (sprite.el) {
63073             sprite.el.remove();
63074             delete sprite.el;
63075         }
63076         this.callParent(arguments);
63077     },
63078     
63079     setViewBox: function(x, y, width, height) {
63080         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
63081             this.callParent(arguments);
63082             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
63083         }
63084     },
63085
63086     render: function (container) {
63087         var me = this;
63088         if (!me.el) {
63089             var width = me.width || 10,
63090                 height = me.height || 10,
63091                 el = me.createSvgElement('svg', {
63092                     xmlns: "http:/" + "/www.w3.org/2000/svg",
63093                     version: 1.1,
63094                     width: width,
63095                     height: height
63096                 }),
63097                 defs = me.getDefs(),
63098
63099                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
63100                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
63101                 // use it rather than the svg element for retrieving the correct client rect of the
63102                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
63103                 bgRect = me.createSvgElement("rect", {
63104                     width: "100%",
63105                     height: "100%",
63106                     fill: "#000",
63107                     stroke: "none",
63108                     opacity: 0
63109                 }),
63110                 webkitRect;
63111             
63112                 if (Ext.isSafari3) {
63113                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
63114                     webkitRect = me.createSvgElement("rect", {
63115                         x: -10,
63116                         y: -10,
63117                         width: "110%",
63118                         height: "110%",
63119                         fill: "none",
63120                         stroke: "#000"
63121                     });
63122                 }
63123             el.appendChild(defs);
63124             if (Ext.isSafari3) {
63125                 el.appendChild(webkitRect);
63126             }
63127             el.appendChild(bgRect);
63128             container.appendChild(el);
63129             me.el = Ext.get(el);
63130             me.bgRect = Ext.get(bgRect);
63131             if (Ext.isSafari3) {
63132                 me.webkitRect = Ext.get(webkitRect);
63133                 me.webkitRect.hide();
63134             }
63135             me.el.on({
63136                 scope: me,
63137                 mouseup: me.onMouseUp,
63138                 mousedown: me.onMouseDown,
63139                 mouseover: me.onMouseOver,
63140                 mouseout: me.onMouseOut,
63141                 mousemove: me.onMouseMove,
63142                 mouseenter: me.onMouseEnter,
63143                 mouseleave: me.onMouseLeave,
63144                 click: me.onClick
63145             });
63146         }
63147         me.renderAll();
63148     },
63149
63150     // private
63151     onMouseEnter: function(e) {
63152         if (this.el.parent().getRegion().contains(e.getPoint())) {
63153             this.fireEvent('mouseenter', e);
63154         }
63155     },
63156
63157     // private
63158     onMouseLeave: function(e) {
63159         if (!this.el.parent().getRegion().contains(e.getPoint())) {
63160             this.fireEvent('mouseleave', e);
63161         }
63162     },
63163     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
63164     processEvent: function(name, e) {
63165         var target = e.getTarget(),
63166             surface = this.surface,
63167             sprite;
63168
63169         this.fireEvent(name, e);
63170         // We wrap text types in a tspan, sprite is the parent.
63171         if (target.nodeName == "tspan" && target.parentNode) {
63172             target = target.parentNode;
63173         }
63174         sprite = this.items.get(target.id);
63175         if (sprite) {
63176             sprite.fireEvent(name, sprite, e);
63177         }
63178     },
63179
63180     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
63181      * the baseline for text the vertical middle of the text to be the same as VML.
63182      */
63183     tuneText: function (sprite, attrs) {
63184         var el = sprite.el.dom,
63185             tspans = [],
63186             height, tspan, text, i, ln, texts, factor;
63187
63188         if (attrs.hasOwnProperty("text")) {
63189            tspans = this.setText(sprite, attrs.text);
63190         }
63191         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
63192         if (tspans.length) {
63193             height = this.getBBoxText(sprite).height;
63194             for (i = 0, ln = tspans.length; i < ln; i++) {
63195                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
63196                 // so we are going to normalize that here
63197                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
63198                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
63199             }
63200             sprite.dirty = true;
63201         }
63202     },
63203
63204     setText: function(sprite, textString) {
63205          var me = this,
63206              el = sprite.el.dom,
63207              x = el.getAttribute("x"),
63208              tspans = [],
63209              height, tspan, text, i, ln, texts;
63210         
63211         while (el.firstChild) {
63212             el.removeChild(el.firstChild);
63213         }
63214         // Wrap each row into tspan to emulate rows
63215         texts = String(textString).split("\n");
63216         for (i = 0, ln = texts.length; i < ln; i++) {
63217             text = texts[i];
63218             if (text) {
63219                 tspan = me.createSvgElement("tspan");
63220                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
63221                 tspan.setAttribute("x", x);
63222                 el.appendChild(tspan);
63223                 tspans[i] = tspan;
63224             }
63225         }
63226         return tspans;
63227     },
63228
63229     renderAll: function() {
63230         this.items.each(this.renderItem, this);
63231     },
63232
63233     renderItem: function (sprite) {
63234         if (!this.el) {
63235             return;
63236         }
63237         if (!sprite.el) {
63238             this.createSpriteElement(sprite);
63239         }
63240         if (sprite.zIndexDirty) {
63241             this.applyZIndex(sprite);
63242         }
63243         if (sprite.dirty) {
63244             this.applyAttrs(sprite);
63245             this.applyTransformations(sprite);
63246         }
63247     },
63248
63249     redraw: function(sprite) {
63250         sprite.dirty = sprite.zIndexDirty = true;
63251         this.renderItem(sprite);
63252     },
63253
63254     applyAttrs: function (sprite) {
63255         var me = this,
63256             el = sprite.el,
63257             group = sprite.group,
63258             sattr = sprite.attr,
63259             parsers = me.parsers,
63260             //Safari does not handle linear gradients correctly in quirksmode
63261             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
63262             //ref: EXTJSIV-1472
63263             gradientsMap = me.gradientsMap || {},
63264             safariFix = Ext.isSafari && !Ext.isStrict,
63265             groups, i, ln, attrs, font, key, style, name, rect;
63266
63267         if (group) {
63268             groups = [].concat(group);
63269             ln = groups.length;
63270             for (i = 0; i < ln; i++) {
63271                 group = groups[i];
63272                 me.getGroup(group).add(sprite);
63273             }
63274             delete sprite.group;
63275         }
63276         attrs = me.scrubAttrs(sprite) || {};
63277
63278         // if (sprite.dirtyPath) {
63279             sprite.bbox.plain = 0;
63280             sprite.bbox.transform = 0;
63281             if (sprite.type == "circle" || sprite.type == "ellipse") {
63282                 attrs.cx = attrs.cx || attrs.x;
63283                 attrs.cy = attrs.cy || attrs.y;
63284             }
63285             else if (sprite.type == "rect") {
63286                 attrs.rx = attrs.ry = attrs.r;
63287             }
63288             else if (sprite.type == "path" && attrs.d) {
63289                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
63290             }
63291             sprite.dirtyPath = false;
63292         // }
63293         // else {
63294         //     delete attrs.d;
63295         // }
63296
63297         if (attrs['clip-rect']) {
63298             me.setClip(sprite, attrs);
63299             delete attrs['clip-rect'];
63300         }
63301         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
63302             el.set({ style: "font: " + attrs.font});
63303             sprite.dirtyFont = false;
63304         }
63305         if (sprite.type == "image") {
63306             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
63307         }
63308         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
63309
63310         if (sprite.dirtyHidden) {
63311             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
63312             sprite.dirtyHidden = false;
63313         }
63314         for (key in attrs) {
63315             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
63316                 //Safari does not handle linear gradients correctly in quirksmode
63317                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
63318                 //ref: EXTJSIV-1472
63319                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
63320                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
63321                 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
63322                     attrs[key] = gradientsMap[attrs[key]];
63323                 }
63324                 if (key in parsers) {
63325                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
63326                 } else {
63327                     el.dom.setAttribute(key, attrs[key]);
63328                 }
63329             }
63330         }
63331         
63332         if (sprite.type == 'text') {
63333             me.tuneText(sprite, attrs);
63334         }
63335
63336         //set styles
63337         style = sattr.style;
63338         if (style) {
63339             el.setStyle(style);
63340         }
63341
63342         sprite.dirty = false;
63343
63344         if (Ext.isSafari3) {
63345             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
63346             me.webkitRect.show();
63347             setTimeout(function () {
63348                 me.webkitRect.hide();
63349             });
63350         }
63351     },
63352
63353     setClip: function(sprite, params) {
63354         var me = this,
63355             rect = params["clip-rect"],
63356             clipEl, clipPath;
63357         if (rect) {
63358             if (sprite.clip) {
63359                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
63360             }
63361             clipEl = me.createSvgElement('clipPath');
63362             clipPath = me.createSvgElement('rect');
63363             clipEl.id = Ext.id(null, 'ext-clip-');
63364             clipPath.setAttribute("x", rect.x);
63365             clipPath.setAttribute("y", rect.y);
63366             clipPath.setAttribute("width", rect.width);
63367             clipPath.setAttribute("height", rect.height);
63368             clipEl.appendChild(clipPath);
63369             me.getDefs().appendChild(clipEl);
63370             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
63371             sprite.clip = clipPath;
63372         }
63373         // if (!attrs[key]) {
63374         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
63375         //     clip && clip.parentNode.removeChild(clip);
63376         //     sprite.el.setAttribute("clip-path", "");
63377         //     delete attrss.clip;
63378         // }
63379     },
63380
63381     /**
63382      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
63383      * @param {Ext.draw.Sprite} sprite
63384      */
63385     applyZIndex: function(sprite) {
63386         var me = this,
63387             items = me.items,
63388             idx = items.indexOf(sprite),
63389             el = sprite.el,
63390             prevEl;
63391         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
63392             if (idx > 0) {
63393                 // Find the first previous sprite which has its DOM element created already
63394                 do {
63395                     prevEl = items.getAt(--idx).el;
63396                 } while (!prevEl && idx > 0);
63397             }
63398             el.insertAfter(prevEl || me.bgRect);
63399         }
63400         sprite.zIndexDirty = false;
63401     },
63402
63403     createItem: function (config) {
63404         var sprite = Ext.create('Ext.draw.Sprite', config);
63405         sprite.surface = this;
63406         return sprite;
63407     },
63408
63409     addGradient: function(gradient) {
63410         gradient = Ext.draw.Draw.parseGradient(gradient);
63411         var me = this,
63412             ln = gradient.stops.length,
63413             vector = gradient.vector,
63414             //Safari does not handle linear gradients correctly in quirksmode
63415             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
63416             //ref: EXTJSIV-1472
63417             usePlain = Ext.isSafari && !Ext.isStrict,
63418             gradientEl, stop, stopEl, i, gradientsMap;
63419             
63420         gradientsMap = me.gradientsMap || {};
63421         
63422         if (!usePlain) {
63423             if (gradient.type == "linear") {
63424                 gradientEl = me.createSvgElement("linearGradient");
63425                 gradientEl.setAttribute("x1", vector[0]);
63426                 gradientEl.setAttribute("y1", vector[1]);
63427                 gradientEl.setAttribute("x2", vector[2]);
63428                 gradientEl.setAttribute("y2", vector[3]);
63429             }
63430             else {
63431                 gradientEl = me.createSvgElement("radialGradient");
63432                 gradientEl.setAttribute("cx", gradient.centerX);
63433                 gradientEl.setAttribute("cy", gradient.centerY);
63434                 gradientEl.setAttribute("r", gradient.radius);
63435                 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
63436                     gradientEl.setAttribute("fx", gradient.focalX);
63437                     gradientEl.setAttribute("fy", gradient.focalY);
63438                 }
63439             }
63440             gradientEl.id = gradient.id;
63441             me.getDefs().appendChild(gradientEl);
63442             for (i = 0; i < ln; i++) {
63443                 stop = gradient.stops[i];
63444                 stopEl = me.createSvgElement("stop");
63445                 stopEl.setAttribute("offset", stop.offset + "%");
63446                 stopEl.setAttribute("stop-color", stop.color);
63447                 stopEl.setAttribute("stop-opacity",stop.opacity);
63448                 gradientEl.appendChild(stopEl);
63449             }
63450         } else {
63451             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
63452         }
63453         me.gradientsMap = gradientsMap;
63454     },
63455
63456     /**
63457      * Checks if the specified CSS class exists on this element's DOM node.
63458      * @param {String} className The CSS class to check for
63459      * @return {Boolean} True if the class exists, else false
63460      */
63461     hasCls: function(sprite, className) {
63462         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
63463     },
63464
63465     addCls: function(sprite, className) {
63466         var el = sprite.el,
63467             i,
63468             len,
63469             v,
63470             cls = [],
63471             curCls =  el.getAttribute('class') || '';
63472         // Separate case is for speed
63473         if (!Ext.isArray(className)) {
63474             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
63475                 el.set({ 'class': curCls + ' ' + className });
63476             }
63477         }
63478         else {
63479             for (i = 0, len = className.length; i < len; i++) {
63480                 v = className[i];
63481                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
63482                     cls.push(v);
63483                 }
63484             }
63485             if (cls.length) {
63486                 el.set({ 'class': ' ' + cls.join(' ') });
63487             }
63488         }
63489     },
63490
63491     removeCls: function(sprite, className) {
63492         var me = this,
63493             el = sprite.el,
63494             curCls =  el.getAttribute('class') || '',
63495             i, idx, len, cls, elClasses;
63496         if (!Ext.isArray(className)){
63497             className = [className];
63498         }
63499         if (curCls) {
63500             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
63501             for (i = 0, len = className.length; i < len; i++) {
63502                 cls = className[i];
63503                 if (typeof cls == 'string') {
63504                     cls = cls.replace(me.trimRe, '');
63505                     idx = Ext.Array.indexOf(elClasses, cls);
63506                     if (idx != -1) {
63507                         Ext.Array.erase(elClasses, idx, 1);
63508                     }
63509                 }
63510             }
63511             el.set({ 'class': elClasses.join(' ') });
63512         }
63513     },
63514
63515     destroy: function() {
63516         var me = this;
63517         
63518         me.callParent();
63519         if (me.el) {
63520             me.el.remove();
63521         }
63522         delete me.el;
63523     }
63524 });
63525 /**
63526  * @class Ext.draw.engine.Vml
63527  * @extends Ext.draw.Surface
63528  * Provides specific methods to draw with VML.
63529  */
63530
63531 Ext.define('Ext.draw.engine.Vml', {
63532
63533     /* Begin Definitions */
63534
63535     extend: 'Ext.draw.Surface',
63536
63537     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
63538
63539     /* End Definitions */
63540
63541     engine: 'Vml',
63542
63543     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
63544     bitesRe: /([clmz]),?([^clmz]*)/gi,
63545     valRe: /-?[^,\s-]+/g,
63546     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
63547     pathlike: /^(path|rect)$/,
63548     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
63549     partialPathRe: /[clmz]/g,
63550     fontFamilyRe: /^['"]+|['"]+$/g,
63551     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
63552     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
63553     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
63554     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
63555     zoom: 21600,
63556     coordsize: 1000,
63557     coordorigin: '0 0',
63558
63559     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
63560     orderSpritesByZIndex: false,
63561
63562     // @private
63563     // Convert an SVG standard path into a VML path
63564     path2vml: function (path) {
63565         var me = this,
63566             nonVML =  me.NonVmlPathRe,
63567             map = me.map,
63568             val = me.valRe,
63569             zoom = me.zoom,
63570             bites = me.bitesRe,
63571             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
63572             res, pa, p, r, i, ii, j, jj;
63573         if (String(path).match(nonVML)) {
63574             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
63575         } else if (!String(path).match(me.partialPathRe)) {
63576             res = String(path).replace(bites, function (all, command, args) {
63577                 var vals = [],
63578                     isMove = command.toLowerCase() == "m",
63579                     res = map[command];
63580                 args.replace(val, function (value) {
63581                     if (isMove && vals[length] == 2) {
63582                         res += vals + map[command == "m" ? "l" : "L"];
63583                         vals = [];
63584                     }
63585                     vals.push(Math.round(value * zoom));
63586                 });
63587                 return res + vals;
63588             });
63589             return res;
63590         }
63591         pa = command(path);
63592         res = [];
63593         for (i = 0, ii = pa.length; i < ii; i++) {
63594             p = pa[i];
63595             r = pa[i][0].toLowerCase();
63596             if (r == "z") {
63597                 r = "x";
63598             }
63599             for (j = 1, jj = p.length; j < jj; j++) {
63600                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
63601             }
63602             res.push(r);
63603         }
63604         return res.join(" ");
63605     },
63606
63607     // @private - set of attributes which need to be translated from the sprite API to the native browser API
63608     translateAttrs: {
63609         radius: "r",
63610         radiusX: "rx",
63611         radiusY: "ry",
63612         lineWidth: "stroke-width",
63613         fillOpacity: "fill-opacity",
63614         strokeOpacity: "stroke-opacity",
63615         strokeLinejoin: "stroke-linejoin"
63616     },
63617
63618     // @private - Minimun set of defaults for different types of sprites.
63619     minDefaults: {
63620         circle: {
63621             fill: "none",
63622             stroke: null,
63623             "stroke-width": null,
63624             opacity: null,
63625             "fill-opacity": null,
63626             "stroke-opacity": null
63627         },
63628         ellipse: {
63629             cx: 0,
63630             cy: 0,
63631             rx: 0,
63632             ry: 0,
63633             fill: "none",
63634             stroke: null,
63635             "stroke-width": null,
63636             opacity: null,
63637             "fill-opacity": null,
63638             "stroke-opacity": null
63639         },
63640         rect: {
63641             x: 0,
63642             y: 0,
63643             width: 0,
63644             height: 0,
63645             rx: 0,
63646             ry: 0,
63647             fill: "none",
63648             stroke: null,
63649             "stroke-width": null,
63650             opacity: null,
63651             "fill-opacity": null,
63652             "stroke-opacity": null
63653         },
63654         text: {
63655             x: 0,
63656             y: 0,
63657             "text-anchor": "start",
63658             font: '10px "Arial"',
63659             fill: "#000",
63660             stroke: null,
63661             "stroke-width": null,
63662             opacity: null,
63663             "fill-opacity": null,
63664             "stroke-opacity": null
63665         },
63666         path: {
63667             d: "M0,0",
63668             fill: "none",
63669             stroke: null,
63670             "stroke-width": null,
63671             opacity: null,
63672             "fill-opacity": null,
63673             "stroke-opacity": null
63674         },
63675         image: {
63676             x: 0,
63677             y: 0,
63678             width: 0,
63679             height: 0,
63680             preserveAspectRatio: "none",
63681             opacity: null
63682         }
63683     },
63684
63685     // private
63686     onMouseEnter: function(e) {
63687         this.fireEvent("mouseenter", e);
63688     },
63689
63690     // private
63691     onMouseLeave: function(e) {
63692         this.fireEvent("mouseleave", e);
63693     },
63694
63695     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
63696     processEvent: function(name, e) {
63697         var target = e.getTarget(),
63698             surface = this.surface,
63699             sprite;
63700         this.fireEvent(name, e);
63701         sprite = this.items.get(target.id);
63702         if (sprite) {
63703             sprite.fireEvent(name, sprite, e);
63704         }
63705     },
63706
63707     // Create the VML element/elements and append them to the DOM
63708     createSpriteElement: function(sprite) {
63709         var me = this,
63710             attr = sprite.attr,
63711             type = sprite.type,
63712             zoom = me.zoom,
63713             vml = sprite.vml || (sprite.vml = {}),
63714             round = Math.round,
63715             el = me.createNode('shape'),
63716             path, skew, textPath;
63717
63718         el.coordsize = zoom + ' ' + zoom;
63719         el.coordorigin = attr.coordorigin || "0 0";
63720         Ext.get(el).addCls(me.spriteCls);
63721         if (type == "text") {
63722             vml.path = path = me.createNode("path");
63723             path.textpathok = true;
63724             vml.textpath = textPath = me.createNode("textpath");
63725             textPath.on = true;
63726             el.appendChild(textPath);
63727             el.appendChild(path);
63728         }
63729         el.id = sprite.id;
63730         sprite.el = Ext.get(el);
63731         me.el.appendChild(el);
63732         if (type !== 'image') {
63733             skew = me.createNode("skew");
63734             skew.on = true;
63735             el.appendChild(skew);
63736             sprite.skew = skew;
63737         }
63738         sprite.matrix = Ext.create('Ext.draw.Matrix');
63739         sprite.bbox = {
63740             plain: null,
63741             transform: null
63742         };
63743         sprite.fireEvent("render", sprite);
63744         return sprite.el;
63745     },
63746
63747     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
63748     getBBox: function (sprite, isWithoutTransform) {
63749         var realPath = this["getPath" + sprite.type](sprite);
63750         if (isWithoutTransform) {
63751             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
63752             return sprite.bbox.plain;
63753         }
63754         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
63755         return sprite.bbox.transform;
63756     },
63757
63758     getBBoxText: function (sprite) {
63759         var vml = sprite.vml;
63760         return {
63761             x: vml.X + (vml.bbx || 0) - vml.W / 2,
63762             y: vml.Y - vml.H / 2,
63763             width: vml.W,
63764             height: vml.H
63765         };
63766     },
63767
63768     applyAttrs: function (sprite) {
63769         var me = this,
63770             vml = sprite.vml,
63771             group = sprite.group,
63772             spriteAttr = sprite.attr,
63773             el = sprite.el,
63774             dom = el.dom,
63775             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
63776
63777         if (group) {
63778             groups = [].concat(group);
63779             ln = groups.length;
63780             for (i = 0; i < ln; i++) {
63781                 group = groups[i];
63782                 me.getGroup(group).add(sprite);
63783             }
63784             delete sprite.group;
63785         }
63786         scrubbedAttrs = me.scrubAttrs(sprite) || {};
63787
63788         if (sprite.zIndexDirty) {
63789             me.setZIndex(sprite);
63790         }
63791
63792         // Apply minimum default attributes
63793         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
63794
63795         if (dom.href) {
63796             dom.href = scrubbedAttrs.href;
63797         }
63798         if (dom.title) {
63799             dom.title = scrubbedAttrs.title;
63800         }
63801         if (dom.target) {
63802             dom.target = scrubbedAttrs.target;
63803         }
63804         if (dom.cursor) {
63805             dom.cursor = scrubbedAttrs.cursor;
63806         }
63807
63808         // Change visibility
63809         if (sprite.dirtyHidden) {
63810             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
63811             sprite.dirtyHidden = false;
63812         }
63813
63814         // Update path
63815         if (sprite.dirtyPath) {
63816             if (sprite.type == "circle" || sprite.type == "ellipse") {
63817                 var cx = scrubbedAttrs.x,
63818                     cy = scrubbedAttrs.y,
63819                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
63820                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
63821                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
63822                             Math.round((cx - rx) * me.zoom),
63823                             Math.round((cy - ry) * me.zoom),
63824                             Math.round((cx + rx) * me.zoom),
63825                             Math.round((cy + ry) * me.zoom),
63826                             Math.round(cx * me.zoom));
63827                 sprite.dirtyPath = false;
63828             }
63829             else if (sprite.type !== "text") {
63830                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
63831                 dom.path = me.path2vml(scrubbedAttrs.path);
63832                 sprite.dirtyPath = false;
63833             }
63834         }
63835
63836         // Apply clipping
63837         if ("clip-rect" in scrubbedAttrs) {
63838             me.setClip(sprite, scrubbedAttrs);
63839         }
63840
63841         // Handle text (special handling required)
63842         if (sprite.type == "text") {
63843             me.setTextAttributes(sprite, scrubbedAttrs);
63844         }
63845
63846         // Handle fill and opacity
63847         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
63848             me.setFill(sprite, scrubbedAttrs);
63849         }
63850
63851         // Handle stroke (all fills require a stroke element)
63852         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
63853             me.setStroke(sprite, scrubbedAttrs);
63854         }
63855         
63856         //set styles
63857         style = spriteAttr.style;
63858         if (style) {
63859             el.setStyle(style);
63860         }
63861
63862         sprite.dirty = false;
63863     },
63864
63865     setZIndex: function(sprite) {
63866         if (sprite.el) {
63867             if (sprite.attr.zIndex != undefined) {
63868                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
63869             }
63870             sprite.zIndexDirty = false;
63871         }
63872     },
63873
63874     // Normalize all virtualized types into paths.
63875     setPaths: function(sprite, params) {
63876         var spriteAttr = sprite.attr;
63877         // Clear bbox cache
63878         sprite.bbox.plain = null;
63879         sprite.bbox.transform = null;
63880         if (sprite.type == 'circle') {
63881             spriteAttr.rx = spriteAttr.ry = params.r;
63882             return Ext.draw.Draw.ellipsePath(sprite);
63883         }
63884         else if (sprite.type == 'ellipse') {
63885             spriteAttr.rx = params.rx;
63886             spriteAttr.ry = params.ry;
63887             return Ext.draw.Draw.ellipsePath(sprite);
63888         }
63889         else if (sprite.type == 'rect' || sprite.type == 'image') {
63890             spriteAttr.rx = spriteAttr.ry = params.r;
63891             return Ext.draw.Draw.rectPath(sprite);
63892         }
63893         else if (sprite.type == 'path' && spriteAttr.path) {
63894             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
63895         }
63896         return false;
63897     },
63898
63899     setFill: function(sprite, params) {
63900         var me = this,
63901             el = sprite.el,
63902             dom = el.dom,
63903             fillEl = dom.getElementsByTagName('fill')[0],
63904             opacity, gradient, fillUrl, rotation, angle;
63905
63906         if (fillEl) {
63907             dom.removeChild(fillEl);
63908         } else {
63909             fillEl = me.createNode('fill');
63910         }
63911         if (Ext.isArray(params.fill)) {
63912             params.fill = params.fill[0];
63913         }
63914         if (sprite.type == 'image') {
63915             fillEl.on = true;
63916             fillEl.src = params.src;
63917             fillEl.type = "tile";
63918             fillEl.rotate = true;
63919         } else if (params.fill == "none") {
63920             fillEl.on = false;
63921         } else {
63922             if (typeof params.opacity == "number") {
63923                 fillEl.opacity = params.opacity;
63924             }
63925             if (typeof params["fill-opacity"] == "number") {
63926                 fillEl.opacity = params["fill-opacity"];
63927             }
63928             fillEl.on = true;
63929             if (typeof params.fill == "string") {
63930                 fillUrl = params.fill.match(me.fillUrlRe);
63931                 if (fillUrl) {
63932                     fillUrl = fillUrl[1];
63933                     // If the URL matches one of the registered gradients, render that gradient
63934                     if (fillUrl.charAt(0) == "#") {
63935                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
63936                     }
63937                     if (gradient) {
63938                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
63939                         rotation = params.rotation;
63940                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
63941                         // IE will flip the angle at 0 degrees...
63942                         if (angle === 0) {
63943                             angle = 180;
63944                         }
63945                         fillEl.angle = angle;
63946                         fillEl.type = "gradient";
63947                         fillEl.method = "sigma";
63948                         fillEl.colors = gradient.colors;
63949                     }
63950                     // Otherwise treat it as an image
63951                     else {
63952                         fillEl.src = fillUrl;
63953                         fillEl.type = "tile";
63954                         fillEl.rotate = true;
63955                     }
63956                 }
63957                 else {
63958                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
63959                     fillEl.src = "";
63960                     fillEl.type = "solid";
63961                 }
63962             }
63963         }
63964         dom.appendChild(fillEl);
63965     },
63966
63967     setStroke: function(sprite, params) {
63968         var me = this,
63969             el = sprite.el.dom,
63970             strokeEl = sprite.strokeEl,
63971             newStroke = false,
63972             width, opacity;
63973
63974         if (!strokeEl) {
63975             strokeEl = sprite.strokeEl = me.createNode("stroke");
63976             newStroke = true;
63977         }
63978         if (Ext.isArray(params.stroke)) {
63979             params.stroke = params.stroke[0];
63980         }
63981         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
63982             strokeEl.on = false;
63983         }
63984         else {
63985             strokeEl.on = true;
63986             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
63987                 // VML does NOT support a gradient stroke :(
63988                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
63989             }
63990             strokeEl.joinstyle = params["stroke-linejoin"];
63991             strokeEl.endcap = params["stroke-linecap"] || "round";
63992             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
63993             width = parseFloat(params["stroke-width"] || 1) * 0.75;
63994             opacity = params["stroke-opacity"] || 1;
63995             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
63996             if (Ext.isNumber(width) && width < 1) {
63997                 strokeEl.weight = 1;
63998                 strokeEl.opacity = opacity * width;
63999             }
64000             else {
64001                 strokeEl.weight = width;
64002                 strokeEl.opacity = opacity;
64003             }
64004         }
64005         if (newStroke) {
64006             el.appendChild(strokeEl);
64007         }
64008     },
64009
64010     setClip: function(sprite, params) {
64011         var me = this,
64012             el = sprite.el,
64013             clipEl = sprite.clipEl,
64014             rect = String(params["clip-rect"]).split(me.separatorRe);
64015         if (!clipEl) {
64016             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
64017             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
64018         }
64019         if (rect.length == 4) {
64020             rect[2] = +rect[2] + (+rect[0]);
64021             rect[3] = +rect[3] + (+rect[1]);
64022             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
64023             clipEl.setSize(me.el.width, me.el.height);
64024         }
64025         else {
64026             clipEl.setStyle("clip", "");
64027         }
64028     },
64029
64030     setTextAttributes: function(sprite, params) {
64031         var me = this,
64032             vml = sprite.vml,
64033             textStyle = vml.textpath.style,
64034             spanCacheStyle = me.span.style,
64035             zoom = me.zoom,
64036             round = Math.round,
64037             fontObj = {
64038                 fontSize: "font-size",
64039                 fontWeight: "font-weight",
64040                 fontStyle: "font-style"
64041             },
64042             fontProp,
64043             paramProp;
64044         if (sprite.dirtyFont) {
64045             if (params.font) {
64046                 textStyle.font = spanCacheStyle.font = params.font;
64047             }
64048             if (params["font-family"]) {
64049                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
64050                 spanCacheStyle.fontFamily = params["font-family"];
64051             }
64052
64053             for (fontProp in fontObj) {
64054                 paramProp = params[fontObj[fontProp]];
64055                 if (paramProp) {
64056                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
64057                 }
64058             }
64059
64060             me.setText(sprite, params.text);
64061             
64062             if (vml.textpath.string) {
64063                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
64064             }
64065             vml.W = me.span.offsetWidth;
64066             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
64067
64068             // text-anchor emulation
64069             if (params["text-anchor"] == "middle") {
64070                 textStyle["v-text-align"] = "center";
64071             }
64072             else if (params["text-anchor"] == "end") {
64073                 textStyle["v-text-align"] = "right";
64074                 vml.bbx = -Math.round(vml.W / 2);
64075             }
64076             else {
64077                 textStyle["v-text-align"] = "left";
64078                 vml.bbx = Math.round(vml.W / 2);
64079             }
64080         }
64081         vml.X = params.x;
64082         vml.Y = params.y;
64083         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);
64084         // Clear bbox cache
64085         sprite.bbox.plain = null;
64086         sprite.bbox.transform = null;
64087         sprite.dirtyFont = false;
64088     },
64089     
64090     setText: function(sprite, text) {
64091         sprite.vml.textpath.string = Ext.htmlDecode(text);
64092     },
64093
64094     hide: function() {
64095         this.el.hide();
64096     },
64097
64098     show: function() {
64099         this.el.show();
64100     },
64101
64102     hidePrim: function(sprite) {
64103         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
64104     },
64105
64106     showPrim: function(sprite) {
64107         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
64108     },
64109
64110     setSize: function(width, height) {
64111         var me = this;
64112         width = width || me.width;
64113         height = height || me.height;
64114         me.width = width;
64115         me.height = height;
64116
64117         if (me.el) {
64118             // Size outer div
64119             if (width != undefined) {
64120                 me.el.setWidth(width);
64121             }
64122             if (height != undefined) {
64123                 me.el.setHeight(height);
64124             }
64125
64126             // Handle viewBox sizing
64127             me.applyViewBox();
64128
64129             me.callParent(arguments);
64130         }
64131     },
64132
64133     setViewBox: function(x, y, width, height) {
64134         this.callParent(arguments);
64135         this.viewBox = {
64136             x: x,
64137             y: y,
64138             width: width,
64139             height: height
64140         };
64141         this.applyViewBox();
64142     },
64143
64144     /**
64145      * @private Using the current viewBox property and the surface's width and height, calculate the
64146      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
64147      */
64148     applyViewBox: function() {
64149         var me = this,
64150             viewBox = me.viewBox,
64151             width = me.width,
64152             height = me.height,
64153             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
64154             relativeHeight, relativeWidth, size;
64155
64156         if (viewBox && (width || height)) {
64157             viewBoxX = viewBox.x;
64158             viewBoxY = viewBox.y;
64159             viewBoxWidth = viewBox.width;
64160             viewBoxHeight = viewBox.height;
64161             relativeHeight = height / viewBoxHeight;
64162             relativeWidth = width / viewBoxWidth;
64163
64164             if (viewBoxWidth * relativeHeight < width) {
64165                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
64166             }
64167             if (viewBoxHeight * relativeWidth < height) {
64168                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
64169             }
64170
64171             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
64172
64173             me.viewBoxShift = {
64174                 dx: -viewBoxX,
64175                 dy: -viewBoxY,
64176                 scale: size
64177             };
64178             me.items.each(function(item) {
64179                 me.transform(item);
64180             });
64181         }
64182     },
64183
64184     onAdd: function(item) {
64185         this.callParent(arguments);
64186         if (this.el) {
64187             this.renderItem(item);
64188         }
64189     },
64190
64191     onRemove: function(sprite) {
64192         if (sprite.el) {
64193             sprite.el.remove();
64194             delete sprite.el;
64195         }
64196         this.callParent(arguments);
64197     },
64198
64199     // VML Node factory method (createNode)
64200     createNode : (function () {
64201         try {
64202             var doc = Ext.getDoc().dom;
64203             if (!doc.namespaces.rvml) {
64204                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
64205             }
64206             return function (tagName) {
64207                 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
64208             };
64209         } catch (e) {
64210             return function (tagName) {
64211                 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
64212             };
64213         }
64214     })(),
64215
64216     render: function (container) {
64217         var me = this,
64218             doc = Ext.getDoc().dom;
64219
64220         if (!me.el) {
64221             var el = doc.createElement("div");
64222             me.el = Ext.get(el);
64223             me.el.addCls(me.baseVmlCls);
64224
64225             // Measuring span (offscrren)
64226             me.span = doc.createElement("span");
64227             Ext.get(me.span).addCls(me.measureSpanCls);
64228             el.appendChild(me.span);
64229             me.el.setSize(me.width || 10, me.height || 10);
64230             container.appendChild(el);
64231             me.el.on({
64232                 scope: me,
64233                 mouseup: me.onMouseUp,
64234                 mousedown: me.onMouseDown,
64235                 mouseover: me.onMouseOver,
64236                 mouseout: me.onMouseOut,
64237                 mousemove: me.onMouseMove,
64238                 mouseenter: me.onMouseEnter,
64239                 mouseleave: me.onMouseLeave,
64240                 click: me.onClick
64241             });
64242         }
64243         me.renderAll();
64244     },
64245
64246     renderAll: function() {
64247         this.items.each(this.renderItem, this);
64248     },
64249
64250     redraw: function(sprite) {
64251         sprite.dirty = true;
64252         this.renderItem(sprite);
64253     },
64254
64255     renderItem: function (sprite) {
64256         // Does the surface element exist?
64257         if (!this.el) {
64258             return;
64259         }
64260
64261         // Create sprite element if necessary
64262         if (!sprite.el) {
64263             this.createSpriteElement(sprite);
64264         }
64265
64266         if (sprite.dirty) {
64267             this.applyAttrs(sprite);
64268             if (sprite.dirtyTransform) {
64269                 this.applyTransformations(sprite);
64270             }
64271         }
64272     },
64273
64274     rotationCompensation: function (deg, dx, dy) {
64275         var matrix = Ext.create('Ext.draw.Matrix');
64276         matrix.rotate(-deg, 0.5, 0.5);
64277         return {
64278             x: matrix.x(dx, dy),
64279             y: matrix.y(dx, dy)
64280         };
64281     },
64282
64283     extractTransform: function (sprite) {
64284         var me = this,
64285             matrix = Ext.create('Ext.draw.Matrix'), scale,
64286             transformstions, tranformationsLength,
64287             transform, i = 0,
64288             shift = me.viewBoxShift;
64289
64290         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
64291             i < tranformationsLength; i ++) {
64292             transform = transformstions[i];
64293             switch (transform.type) {
64294                 case 'translate' :
64295                     matrix.translate(transform.x, transform.y);
64296                     break;
64297                 case 'rotate':
64298                     matrix.rotate(transform.degrees, transform.x, transform.y);
64299                     break;
64300                 case 'scale':
64301                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
64302                     break;
64303             }
64304         }
64305
64306         if (shift) {
64307             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
64308             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
64309         }
64310         
64311         return sprite.matrix = matrix;
64312     },
64313
64314     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
64315         var me = this,
64316             matrix = sprite.matrix,
64317             dom = sprite.el.dom,
64318             style = dom.style,
64319             yFlipper = 1,
64320             flip = "",
64321             fill = dom.getElementsByTagName('fill')[0],
64322             kx = me.zoom / sx,
64323             ky = me.zoom / sy,
64324             rotationCompensation;
64325         if (!sx || !sy) {
64326             return;
64327         }
64328         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
64329         style.rotation = rotate * (sx * sy < 0 ? -1 : 1);
64330         if (rotate) {
64331             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
64332             dx = rotationCompensation.x;
64333             dy = rotationCompensation.y;
64334         }
64335         if (sx < 0) {
64336             flip += "x"
64337         }
64338         if (sy < 0) {
64339             flip += " y";
64340             yFlipper = -1;
64341         }
64342         style.flip = flip;
64343         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
64344         if (fill) {
64345             dom.removeChild(fill);
64346             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
64347             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
64348             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
64349             dom.appendChild(fill);
64350         }
64351     },
64352
64353     transform : function (sprite) {
64354         var me = this,
64355             el = sprite.el,
64356             skew = sprite.skew,
64357             dom = el.dom,
64358             domStyle = dom.style,
64359             matrix = me.extractTransform(sprite).clone(),
64360             split, zoom = me.zoom,
64361             fill = dom.getElementsByTagName('fill')[0],
64362             isPatt = !String(sprite.fill).indexOf("url("),
64363             offset, c;
64364
64365
64366         // Hide element while we transform
64367
64368         if (sprite.type != "image" && skew && !isPatt) {
64369             // matrix transform via VML skew
64370             skew.matrix = matrix.toString();
64371             // skew.offset = '32767,1' OK
64372             // skew.offset = '32768,1' Crash
64373             // M$, R U kidding??
64374             offset = matrix.offset();
64375             if (offset[0] > 32767) {
64376                 offset[0] = 32767;
64377             } else if (offset[0] < -32768) {
64378                 offset[0] = -32768
64379             }
64380             if (offset[1] > 32767) {
64381                 offset[1] = 32767;
64382             } else if (offset[1] < -32768) {
64383                 offset[1] = -32768
64384             }
64385             skew.offset = offset;
64386         } else {
64387             if (skew) {
64388                 skew.matrix = "1 0 0 1";
64389                 skew.offset = "0 0";
64390             }
64391             split = matrix.split();
64392             if (split.isSimple) {
64393                 domStyle.filter = '';
64394                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
64395             } else {
64396                 domStyle.filter = matrix.toFilter();
64397                 var bb = me.getBBox(sprite),
64398                     dx = bb.x - sprite.x,
64399                     dy = bb.y - sprite.y;
64400                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
64401                 if (fill) {
64402                     dom.removeChild(fill);
64403                     fill.position = dx + ' ' + dy;
64404                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
64405                     dom.appendChild(fill);
64406                 }
64407             }
64408         }
64409     },
64410
64411     createItem: function (config) {
64412         return Ext.create('Ext.draw.Sprite', config);
64413     },
64414
64415     getRegion: function() {
64416         return this.el.getRegion();
64417     },
64418
64419     addCls: function(sprite, className) {
64420         if (sprite && sprite.el) {
64421             sprite.el.addCls(className);
64422         }
64423     },
64424
64425     removeCls: function(sprite, className) {
64426         if (sprite && sprite.el) {
64427             sprite.el.removeCls(className);
64428         }
64429     },
64430
64431     /**
64432      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
64433      * to its corresponding VML attributes and store it for later use by individual sprites.
64434      * @param {Object} gradient
64435      */
64436     addGradient: function(gradient) {
64437         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
64438             colors = [],
64439             stops = Ext.create('Ext.util.MixedCollection');
64440
64441         // Build colors string
64442         stops.addAll(gradient.stops);
64443         stops.sortByKey("ASC", function(a, b) {
64444             a = parseInt(a, 10);
64445             b = parseInt(b, 10);
64446             return a > b ? 1 : (a < b ? -1 : 0);
64447         });
64448         stops.eachKey(function(k, v) {
64449             colors.push(k + "% " + v.color);
64450         });
64451
64452         gradients.add(gradient.id, {
64453             colors: colors.join(","),
64454             angle: gradient.angle
64455         });
64456     },
64457
64458     destroy: function() {
64459         var me = this;
64460         
64461         me.callParent(arguments);
64462         if (me.el) {
64463             me.el.remove();
64464         }
64465         delete me.el;
64466     }
64467 });
64468
64469 /**
64470  * @class Ext.fx.target.ElementCSS
64471  * @extends Ext.fx.target.Element
64472  * 
64473  * This class represents a animation target for an {@link Ext.Element} that supports CSS
64474  * based animation. In general this class will not be created directly, the {@link Ext.Element} 
64475  * will be passed to the animation and the appropriate target will be created.
64476  */
64477 Ext.define('Ext.fx.target.ElementCSS', {
64478
64479     /* Begin Definitions */
64480
64481     extend: 'Ext.fx.target.Element',
64482
64483     /* End Definitions */
64484
64485     setAttr: function(targetData, isFirstFrame) {
64486         var cssArr = {
64487                 attrs: [],
64488                 duration: [],
64489                 easing: []
64490             },
64491             ln = targetData.length,
64492             attributes,
64493             attrs,
64494             attr,
64495             easing,
64496             duration,
64497             o,
64498             i,
64499             j,
64500             ln2;
64501         for (i = 0; i < ln; i++) {
64502             attrs = targetData[i];
64503             duration = attrs.duration;
64504             easing = attrs.easing;
64505             attrs = attrs.attrs;
64506             for (attr in attrs) {
64507                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
64508                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
64509                         return '-' + v.toLowerCase();
64510                     }));
64511                     cssArr.duration.push(duration + 'ms');
64512                     cssArr.easing.push(easing);
64513                 }
64514             }
64515         }
64516         attributes = cssArr.attrs.join(',');
64517         duration = cssArr.duration.join(',');
64518         easing = cssArr.easing.join(', ');
64519         for (i = 0; i < ln; i++) {
64520             attrs = targetData[i].attrs;
64521             for (attr in attrs) {
64522                 ln2 = attrs[attr].length;
64523                 for (j = 0; j < ln2; j++) {
64524                     o = attrs[attr][j];
64525                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
64526                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
64527                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
64528                     o[0].setStyle(attr, o[1]);
64529
64530                     // Must trigger reflow to make this get used as the start point for the transition that follows
64531                     if (isFirstFrame) {
64532                         o = o[0].dom.offsetWidth;
64533                     }
64534                     else {
64535                         // Remove transition properties when completed.
64536                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
64537                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
64538                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
64539                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
64540                         }, o[0], { single: true });
64541                     }
64542                 }
64543             }
64544         }
64545     }
64546 });
64547 /**
64548  * @class Ext.fx.target.CompositeElementCSS
64549  * @extends Ext.fx.target.CompositeElement
64550  * 
64551  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
64552  * constituent elements support CSS based animation. It allows each {@link Ext.Element} in 
64553  * the group to be animated as a whole. In general this class will not be created directly, 
64554  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
64555  * will be created.
64556  */
64557 Ext.define('Ext.fx.target.CompositeElementCSS', {
64558
64559     /* Begin Definitions */
64560
64561     extend: 'Ext.fx.target.CompositeElement',
64562
64563     requires: ['Ext.fx.target.ElementCSS'],
64564
64565     /* End Definitions */
64566     setAttr: function() {
64567         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
64568     }
64569 });
64570 /**
64571  * @class Ext.layout.container.AbstractFit
64572  * @extends Ext.layout.container.Container
64573  * @private
64574  */
64575 Ext.define('Ext.layout.container.AbstractFit', {
64576
64577     /* Begin Definitions */
64578
64579     extend: 'Ext.layout.container.Container',
64580
64581     /* End Definitions */
64582
64583     itemCls: Ext.baseCSSPrefix + 'fit-item',
64584     targetCls: Ext.baseCSSPrefix + 'layout-fit',
64585     type: 'fit'
64586 });
64587 /**
64588  * This is a base class for layouts that contain **a single item** that automatically expands to fill the layout's
64589  * container. This class is intended to be extended or created via the `layout: 'fit'`
64590  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
64591  *
64592  * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
64593  * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it. If the container has multiple
64594  * panels, only the first one will be displayed.
64595  *
64596  *     @example
64597  *     Ext.create('Ext.panel.Panel', {
64598  *         title: 'Fit Layout',
64599  *         width: 300,
64600  *         height: 150,
64601  *         layout:'fit',
64602  *         items: {
64603  *             title: 'Inner Panel',
64604  *             html: 'This is the inner panel content',
64605  *             bodyPadding: 20,
64606  *             border: false
64607  *         },
64608  *         renderTo: Ext.getBody()
64609  *     });
64610  */
64611 Ext.define('Ext.layout.container.Fit', {
64612
64613     /* Begin Definitions */
64614
64615     extend: 'Ext.layout.container.AbstractFit',
64616     alias: 'layout.fit',
64617     alternateClassName: 'Ext.layout.FitLayout',
64618     requires: ['Ext.layout.container.Box'],
64619
64620     /* End Definitions */
64621
64622     /**
64623      * @cfg {Object} defaultMargins
64624      * <p>If the individual contained items do not have a <tt>margins</tt>
64625      * property specified or margin specified via CSS, the default margins from this property will be
64626      * applied to each item.</p>
64627      * <br><p>This property may be specified as an object containing margins
64628      * to apply in the format:</p><pre><code>
64629 {
64630     top: (top margin),
64631     right: (right margin),
64632     bottom: (bottom margin),
64633     left: (left margin)
64634 }</code></pre>
64635      * <p>This property may also be specified as a string containing
64636      * space-separated, numeric margin values. The order of the sides associated
64637      * with each value matches the way CSS processes margin values:</p>
64638      * <div class="mdetail-params"><ul>
64639      * <li>If there is only one value, it applies to all sides.</li>
64640      * <li>If there are two values, the top and bottom borders are set to the
64641      * first value and the right and left are set to the second.</li>
64642      * <li>If there are three values, the top is set to the first value, the left
64643      * and right are set to the second, and the bottom is set to the third.</li>
64644      * <li>If there are four values, they apply to the top, right, bottom, and
64645      * left, respectively.</li>
64646      * </ul></div>
64647      * <p>Defaults to:</p><pre><code>
64648      * {top:0, right:0, bottom:0, left:0}
64649      * </code></pre>
64650      */
64651     defaultMargins: {
64652         top: 0,
64653         right: 0,
64654         bottom: 0,
64655         left: 0
64656     },
64657
64658     // @private
64659     onLayout : function() {
64660         var me = this,
64661             size,
64662             item,
64663             margins;
64664         me.callParent();
64665
64666         if (me.owner.items.length) {
64667             item = me.owner.items.get(0);
64668             margins = item.margins || me.defaultMargins;
64669             size = me.getLayoutTargetSize();
64670             size.width  -= margins.width;
64671             size.height -= margins.height;
64672             me.setItemBox(item, size);
64673
64674             // If any margins were configure either through the margins config, or in the CSS style,
64675             // Then positioning will be used.
64676             if (margins.left || margins.top) {
64677                 item.setPosition(margins.left, margins.top);
64678             }
64679         }
64680     },
64681
64682     getTargetBox : function() {
64683         return this.getLayoutTargetSize();
64684     },
64685
64686     setItemBox : function(item, box) {
64687         var me = this;
64688         if (item && box.height > 0) {
64689             if (!me.owner.isFixedWidth()) {
64690                box.width = undefined;
64691             }
64692             if (!me.owner.isFixedHeight()) {
64693                box.height = undefined;
64694             }
64695             me.setItemSize(item, box.width, box.height);
64696         }
64697     },
64698
64699     configureItem: function(item) {
64700
64701         // Card layout only controls dimensions which IT has controlled.
64702         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
64703         item.layoutManagedHeight = 0;
64704         item.layoutManagedWidth = 0;
64705
64706         this.callParent(arguments);
64707     }
64708 }, function() {
64709     // Use Box layout's renderItem which reads CSS margins, and adds them to any configured item margins
64710     // (Defaulting to "0 0 0 0")
64711     this.prototype.renderItem = Ext.layout.container.Box.prototype.renderItem;
64712 });
64713 /**
64714  * Abstract base class for {@link Ext.layout.container.Card Card layout}.
64715  * @private
64716  */
64717 Ext.define('Ext.layout.container.AbstractCard', {
64718
64719     /* Begin Definitions */
64720
64721     extend: 'Ext.layout.container.Fit',
64722
64723     /* End Definitions */
64724
64725     type: 'card',
64726
64727     sizeAllCards: false,
64728
64729     hideInactive: true,
64730
64731     /**
64732      * @cfg {Boolean} deferredRender
64733      * True to render each contained item at the time it becomes active, false to render all contained items
64734      * as soon as the layout is rendered.  If there is a significant amount of content or
64735      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
64736      * true might improve performance.
64737      */
64738     deferredRender : false,
64739
64740     beforeLayout: function() {
64741         var me = this;
64742         me.getActiveItem();
64743         if (me.activeItem && me.deferredRender) {
64744             me.renderItems([me.activeItem], me.getRenderTarget());
64745             return true;
64746         }
64747         else {
64748             return this.callParent(arguments);
64749         }
64750     },
64751
64752     renderChildren: function () {
64753         if (!this.deferredRender) {
64754             this.getActiveItem();
64755             this.callParent();
64756         }
64757     },
64758
64759     onLayout: function() {
64760         var me = this,
64761             activeItem = me.activeItem,
64762             items = me.getVisibleItems(),
64763             ln = items.length,
64764             targetBox = me.getTargetBox(),
64765             i, item;
64766
64767         for (i = 0; i < ln; i++) {
64768             item = items[i];
64769             me.setItemBox(item, targetBox);
64770         }
64771
64772         if (!me.firstActivated && activeItem) {
64773             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
64774                 activeItem.fireEvent('activate', activeItem);
64775             }
64776             me.firstActivated = true;
64777         }
64778     },
64779
64780     isValidParent : function(item, target, position) {
64781         // Note: Card layout does not care about order within the target because only one is ever visible.
64782         // We only care whether the item is a direct child of the target.
64783         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
64784         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
64785     },
64786
64787     /**
64788      * Return the active (visible) component in the layout.
64789      * @returns {Ext.Component}
64790      */
64791     getActiveItem: function() {
64792         var me = this;
64793         if (!me.activeItem && me.owner) {
64794             me.activeItem = me.parseActiveItem(me.owner.activeItem);
64795         }
64796
64797         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
64798             return me.activeItem;
64799         }
64800
64801         return null;
64802     },
64803
64804     // @private
64805     parseActiveItem: function(item) {
64806         if (item && item.isComponent) {
64807             return item;
64808         }
64809         else if (typeof item == 'number' || item === undefined) {
64810             return this.getLayoutItems()[item || 0];
64811         }
64812         else {
64813             return this.owner.getComponent(item);
64814         }
64815     },
64816
64817     // @private
64818     configureItem: function(item, position) {
64819         this.callParent([item, position]);
64820         if (this.hideInactive && this.activeItem !== item) {
64821             item.hide();
64822         }
64823         else {
64824             item.show();
64825         }
64826     },
64827
64828     onRemove: function(component) {
64829         if (component === this.activeItem) {
64830             this.activeItem = null;
64831             if (this.owner.items.getCount() === 0) {
64832                 this.firstActivated = false;
64833             }
64834         }
64835     },
64836
64837     // @private
64838     getAnimation: function(newCard, owner) {
64839         var newAnim = (newCard || {}).cardSwitchAnimation;
64840         if (newAnim === false) {
64841             return false;
64842         }
64843         return newAnim || owner.cardSwitchAnimation;
64844     },
64845
64846     /**
64847      * Return the active (visible) component in the layout to the next card
64848      * @returns {Ext.Component} The next component or false.
64849      */
64850     getNext: function() {
64851         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
64852         //should come back in 4.1
64853         var wrap = arguments[0];
64854         var items = this.getLayoutItems(),
64855             index = Ext.Array.indexOf(items, this.activeItem);
64856         return items[index + 1] || (wrap ? items[0] : false);
64857     },
64858
64859     /**
64860      * Sets the active (visible) component in the layout to the next card
64861      * @return {Ext.Component} the activated component or false when nothing activated.
64862      */
64863     next: function() {
64864         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
64865         //should come back in 4.1
64866         var anim = arguments[0], wrap = arguments[1];
64867         return this.setActiveItem(this.getNext(wrap), anim);
64868     },
64869
64870     /**
64871      * Return the active (visible) component in the layout to the previous card
64872      * @returns {Ext.Component} The previous component or false.
64873      */
64874     getPrev: function() {
64875         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
64876         //should come back in 4.1
64877         var wrap = arguments[0];
64878         var items = this.getLayoutItems(),
64879             index = Ext.Array.indexOf(items, this.activeItem);
64880         return items[index - 1] || (wrap ? items[items.length - 1] : false);
64881     },
64882
64883     /**
64884      * Sets the active (visible) component in the layout to the previous card
64885      * @return {Ext.Component} the activated component or false when nothing activated.
64886      */
64887     prev: function() {
64888         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
64889         //should come back in 4.1
64890         var anim = arguments[0], wrap = arguments[1];
64891         return this.setActiveItem(this.getPrev(wrap), anim);
64892     }
64893 });
64894
64895 /**
64896  * Tracks what records are currently selected in a databound component.
64897  *
64898  * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
64899  * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
64900  * and provide a way to binding to the component.
64901  *
64902  * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
64903  * subclasses to update the UI widget.
64904  */
64905 Ext.define('Ext.selection.Model', {
64906     extend: 'Ext.util.Observable',
64907     alternateClassName: 'Ext.AbstractSelectionModel',
64908     requires: ['Ext.data.StoreManager'],
64909     // lastSelected
64910
64911     /**
64912      * @cfg {String} mode
64913      * Mode of selection.  Valid values are:
64914      *
64915      * - **SINGLE** - Only allows selecting one item at a time.  Use {@link #allowDeselect} to allow
64916      *   deselecting that item.  This is the default.
64917      * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
64918      *   select or deselect an item.
64919      * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
64920      */
64921
64922     /**
64923      * @cfg {Boolean} allowDeselect
64924      * Allow users to deselect a record in a DataView, List or Grid.
64925      * Only applicable when the {@link #mode} is 'SINGLE'.
64926      */
64927     allowDeselect: false,
64928
64929     /**
64930      * @property {Ext.util.MixedCollection} selected
64931      * A MixedCollection that maintains all of the currently selected records. Read-only.
64932      */
64933     selected: null,
64934
64935     /**
64936      * Prune records when they are removed from the store from the selection.
64937      * This is a private flag. For an example of its usage, take a look at
64938      * Ext.selection.TreeModel.
64939      * @private
64940      */
64941     pruneRemoved: true,
64942
64943     constructor: function(cfg) {
64944         var me = this;
64945
64946         cfg = cfg || {};
64947         Ext.apply(me, cfg);
64948
64949         me.addEvents(
64950             /**
64951              * @event
64952              * Fired after a selection change has occurred
64953              * @param {Ext.selection.Model} this
64954              * @param {Ext.data.Model[]} selected The selected records
64955              */
64956             'selectionchange'
64957         );
64958
64959         me.modes = {
64960             SINGLE: true,
64961             SIMPLE: true,
64962             MULTI: true
64963         };
64964
64965         // sets this.selectionMode
64966         me.setSelectionMode(cfg.mode || me.mode);
64967
64968         // maintains the currently selected records.
64969         me.selected = Ext.create('Ext.util.MixedCollection');
64970
64971         me.callParent(arguments);
64972     },
64973
64974     // binds the store to the selModel.
64975     bind : function(store, initial){
64976         var me = this;
64977
64978         if(!initial && me.store){
64979             if(store !== me.store && me.store.autoDestroy){
64980                 me.store.destroyStore();
64981             }else{
64982                 me.store.un("add", me.onStoreAdd, me);
64983                 me.store.un("clear", me.onStoreClear, me);
64984                 me.store.un("remove", me.onStoreRemove, me);
64985                 me.store.un("update", me.onStoreUpdate, me);
64986             }
64987         }
64988         if(store){
64989             store = Ext.data.StoreManager.lookup(store);
64990             store.on({
64991                 add: me.onStoreAdd,
64992                 clear: me.onStoreClear,
64993                 remove: me.onStoreRemove,
64994                 update: me.onStoreUpdate,
64995                 scope: me
64996             });
64997         }
64998         me.store = store;
64999         if(store && !initial) {
65000             me.refresh();
65001         }
65002     },
65003
65004     /**
65005      * Selects all records in the view.
65006      * @param {Boolean} suppressEvent True to suppress any select events
65007      */
65008     selectAll: function(suppressEvent) {
65009         var me = this,
65010             selections = me.store.getRange(),
65011             i = 0,
65012             len = selections.length,
65013             start = me.getSelection().length;
65014
65015         me.bulkChange = true;
65016         for (; i < len; i++) {
65017             me.doSelect(selections[i], true, suppressEvent);
65018         }
65019         delete me.bulkChange;
65020         // fire selection change only if the number of selections differs
65021         me.maybeFireSelectionChange(me.getSelection().length !== start);
65022     },
65023
65024     /**
65025      * Deselects all records in the view.
65026      * @param {Boolean} suppressEvent True to suppress any deselect events
65027      */
65028     deselectAll: function(suppressEvent) {
65029         var me = this,
65030             selections = me.getSelection(),
65031             i = 0,
65032             len = selections.length,
65033             start = me.getSelection().length;
65034
65035         me.bulkChange = true;
65036         for (; i < len; i++) {
65037             me.doDeselect(selections[i], suppressEvent);
65038         }
65039         delete me.bulkChange;
65040         // fire selection change only if the number of selections differs
65041         me.maybeFireSelectionChange(me.getSelection().length !== start);
65042     },
65043
65044     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
65045     // selection modes. Requires that an event be passed so that we can know
65046     // if user held ctrl or shift.
65047     selectWithEvent: function(record, e, keepExisting) {
65048         var me = this;
65049
65050         switch (me.selectionMode) {
65051             case 'MULTI':
65052                 if (e.ctrlKey && me.isSelected(record)) {
65053                     me.doDeselect(record, false);
65054                 } else if (e.shiftKey && me.lastFocused) {
65055                     me.selectRange(me.lastFocused, record, e.ctrlKey);
65056                 } else if (e.ctrlKey) {
65057                     me.doSelect(record, true, false);
65058                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
65059                     me.doSelect(record, keepExisting, false);
65060                 } else {
65061                     me.doSelect(record, false);
65062                 }
65063                 break;
65064             case 'SIMPLE':
65065                 if (me.isSelected(record)) {
65066                     me.doDeselect(record);
65067                 } else {
65068                     me.doSelect(record, true);
65069                 }
65070                 break;
65071             case 'SINGLE':
65072                 // if allowDeselect is on and this record isSelected, deselect it
65073                 if (me.allowDeselect && me.isSelected(record)) {
65074                     me.doDeselect(record);
65075                 // select the record and do NOT maintain existing selections
65076                 } else {
65077                     me.doSelect(record, false);
65078                 }
65079                 break;
65080         }
65081     },
65082
65083     /**
65084      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
65085      * All rows in between startRow and endRow are also selected.
65086      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
65087      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
65088      * @param {Boolean} [keepExisting] True to retain existing selections
65089      */
65090     selectRange : function(startRow, endRow, keepExisting, dir){
65091         var me = this,
65092             store = me.store,
65093             selectedCount = 0,
65094             i,
65095             tmp,
65096             dontDeselect,
65097             records = [];
65098
65099         if (me.isLocked()){
65100             return;
65101         }
65102
65103         if (!keepExisting) {
65104             me.deselectAll(true);
65105         }
65106
65107         if (!Ext.isNumber(startRow)) {
65108             startRow = store.indexOf(startRow);
65109         }
65110         if (!Ext.isNumber(endRow)) {
65111             endRow = store.indexOf(endRow);
65112         }
65113
65114         // swap values
65115         if (startRow > endRow){
65116             tmp = endRow;
65117             endRow = startRow;
65118             startRow = tmp;
65119         }
65120
65121         for (i = startRow; i <= endRow; i++) {
65122             if (me.isSelected(store.getAt(i))) {
65123                 selectedCount++;
65124             }
65125         }
65126
65127         if (!dir) {
65128             dontDeselect = -1;
65129         } else {
65130             dontDeselect = (dir == 'up') ? startRow : endRow;
65131         }
65132
65133         for (i = startRow; i <= endRow; i++){
65134             if (selectedCount == (endRow - startRow + 1)) {
65135                 if (i != dontDeselect) {
65136                     me.doDeselect(i, true);
65137                 }
65138             } else {
65139                 records.push(store.getAt(i));
65140             }
65141         }
65142         me.doMultiSelect(records, true);
65143     },
65144
65145     /**
65146      * Selects a record instance by record instance or index.
65147      * @param {Ext.data.Model[]/Number} records An array of records or an index
65148      * @param {Boolean} [keepExisting] True to retain existing selections
65149      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
65150      */
65151     select: function(records, keepExisting, suppressEvent) {
65152         // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
65153         if (Ext.isDefined(records)) {
65154             this.doSelect(records, keepExisting, suppressEvent);
65155         }
65156     },
65157
65158     /**
65159      * Deselects a record instance by record instance or index.
65160      * @param {Ext.data.Model[]/Number} records An array of records or an index
65161      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
65162      */
65163     deselect: function(records, suppressEvent) {
65164         this.doDeselect(records, suppressEvent);
65165     },
65166
65167     doSelect: function(records, keepExisting, suppressEvent) {
65168         var me = this,
65169             record;
65170
65171         if (me.locked) {
65172             return;
65173         }
65174         if (typeof records === "number") {
65175             records = [me.store.getAt(records)];
65176         }
65177         if (me.selectionMode == "SINGLE" && records) {
65178             record = records.length ? records[0] : records;
65179             me.doSingleSelect(record, suppressEvent);
65180         } else {
65181             me.doMultiSelect(records, keepExisting, suppressEvent);
65182         }
65183     },
65184
65185     doMultiSelect: function(records, keepExisting, suppressEvent) {
65186         var me = this,
65187             selected = me.selected,
65188             change = false,
65189             i = 0,
65190             len, record;
65191
65192         if (me.locked) {
65193             return;
65194         }
65195
65196
65197         records = !Ext.isArray(records) ? [records] : records;
65198         len = records.length;
65199         if (!keepExisting && selected.getCount() > 0) {
65200             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
65201                 return;
65202             }
65203             // TODO - coalesce the selectionchange event in deselect w/the one below...
65204         }
65205
65206         function commit () {
65207             selected.add(record);
65208             change = true;
65209         }
65210
65211         for (; i < len; i++) {
65212             record = records[i];
65213             if (keepExisting && me.isSelected(record)) {
65214                 continue;
65215             }
65216             me.lastSelected = record;
65217
65218             me.onSelectChange(record, true, suppressEvent, commit);
65219         }
65220         me.setLastFocused(record, suppressEvent);
65221         // fire selchange if there was a change and there is no suppressEvent flag
65222         me.maybeFireSelectionChange(change && !suppressEvent);
65223     },
65224
65225     // records can be an index, a record or an array of records
65226     doDeselect: function(records, suppressEvent) {
65227         var me = this,
65228             selected = me.selected,
65229             i = 0,
65230             len, record,
65231             attempted = 0,
65232             accepted = 0;
65233
65234         if (me.locked) {
65235             return false;
65236         }
65237
65238         if (typeof records === "number") {
65239             records = [me.store.getAt(records)];
65240         } else if (!Ext.isArray(records)) {
65241             records = [records];
65242         }
65243
65244         function commit () {
65245             ++accepted;
65246             selected.remove(record);
65247         }
65248
65249         len = records.length;
65250
65251         for (; i < len; i++) {
65252             record = records[i];
65253             if (me.isSelected(record)) {
65254                 if (me.lastSelected == record) {
65255                     me.lastSelected = selected.last();
65256                 }
65257                 ++attempted;
65258                 me.onSelectChange(record, false, suppressEvent, commit);
65259             }
65260         }
65261
65262         // fire selchange if there was a change and there is no suppressEvent flag
65263         me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
65264         return accepted === attempted;
65265     },
65266
65267     doSingleSelect: function(record, suppressEvent) {
65268         var me = this,
65269             changed = false,
65270             selected = me.selected;
65271
65272         if (me.locked) {
65273             return;
65274         }
65275         // already selected.
65276         // should we also check beforeselect?
65277         if (me.isSelected(record)) {
65278             return;
65279         }
65280
65281         function commit () {
65282             me.bulkChange = true;
65283             if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
65284                 delete me.bulkChange;
65285                 return false;
65286             }
65287             delete me.bulkChange;
65288
65289             selected.add(record);
65290             me.lastSelected = record;
65291             changed = true;
65292         }
65293
65294         me.onSelectChange(record, true, suppressEvent, commit);
65295
65296         if (changed) {
65297             if (!suppressEvent) {
65298                 me.setLastFocused(record);
65299             }
65300             me.maybeFireSelectionChange(!suppressEvent);
65301         }
65302     },
65303
65304     /**
65305      * Sets a record as the last focused record. This does NOT mean
65306      * that the record has been selected.
65307      * @param {Ext.data.Model} record
65308      */
65309     setLastFocused: function(record, supressFocus) {
65310         var me = this,
65311             recordBeforeLast = me.lastFocused;
65312         me.lastFocused = record;
65313         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
65314     },
65315
65316     /**
65317      * Determines if this record is currently focused.
65318      * @param {Ext.data.Model} record
65319      */
65320     isFocused: function(record) {
65321         return record === this.getLastFocused();
65322     },
65323
65324
65325     // fire selection change as long as true is not passed
65326     // into maybeFireSelectionChange
65327     maybeFireSelectionChange: function(fireEvent) {
65328         var me = this;
65329         if (fireEvent && !me.bulkChange) {
65330             me.fireEvent('selectionchange', me, me.getSelection());
65331         }
65332     },
65333
65334     /**
65335      * Returns the last selected record.
65336      */
65337     getLastSelected: function() {
65338         return this.lastSelected;
65339     },
65340
65341     getLastFocused: function() {
65342         return this.lastFocused;
65343     },
65344
65345     /**
65346      * Returns an array of the currently selected records.
65347      * @return {Ext.data.Model[]} The selected records
65348      */
65349     getSelection: function() {
65350         return this.selected.getRange();
65351     },
65352
65353     /**
65354      * Returns the current selectionMode.
65355      * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
65356      */
65357     getSelectionMode: function() {
65358         return this.selectionMode;
65359     },
65360
65361     /**
65362      * Sets the current selectionMode.
65363      * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
65364      */
65365     setSelectionMode: function(selMode) {
65366         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
65367         // set to mode specified unless it doesnt exist, in that case
65368         // use single.
65369         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
65370     },
65371
65372     /**
65373      * Returns true if the selections are locked.
65374      * @return {Boolean}
65375      */
65376     isLocked: function() {
65377         return this.locked;
65378     },
65379
65380     /**
65381      * Locks the current selection and disables any changes from happening to the selection.
65382      * @param {Boolean} locked  True to lock, false to unlock.
65383      */
65384     setLocked: function(locked) {
65385         this.locked = !!locked;
65386     },
65387
65388     /**
65389      * Returns true if the specified row is selected.
65390      * @param {Ext.data.Model/Number} record The record or index of the record to check
65391      * @return {Boolean}
65392      */
65393     isSelected: function(record) {
65394         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
65395         return this.selected.indexOf(record) !== -1;
65396     },
65397
65398     /**
65399      * Returns true if there are any a selected records.
65400      * @return {Boolean}
65401      */
65402     hasSelection: function() {
65403         return this.selected.getCount() > 0;
65404     },
65405
65406     refresh: function() {
65407         var me = this,
65408             toBeSelected = [],
65409             oldSelections = me.getSelection(),
65410             len = oldSelections.length,
65411             selection,
65412             change,
65413             i = 0,
65414             lastFocused = this.getLastFocused();
65415
65416         // check to make sure that there are no records
65417         // missing after the refresh was triggered, prune
65418         // them from what is to be selected if so
65419         for (; i < len; i++) {
65420             selection = oldSelections[i];
65421             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
65422                 toBeSelected.push(selection);
65423             }
65424         }
65425
65426         // there was a change from the old selected and
65427         // the new selection
65428         if (me.selected.getCount() != toBeSelected.length) {
65429             change = true;
65430         }
65431
65432         me.clearSelections();
65433
65434         if (me.store.indexOf(lastFocused) !== -1) {
65435             // restore the last focus but supress restoring focus
65436             this.setLastFocused(lastFocused, true);
65437         }
65438
65439         if (toBeSelected.length) {
65440             // perform the selection again
65441             me.doSelect(toBeSelected, false, true);
65442         }
65443
65444         me.maybeFireSelectionChange(change);
65445     },
65446
65447     /**
65448      * A fast reset of the selections without firing events, updating the ui, etc.
65449      * For private usage only.
65450      * @private
65451      */
65452     clearSelections: function() {
65453         // reset the entire selection to nothing
65454         this.selected.clear();
65455         this.lastSelected = null;
65456         this.setLastFocused(null);
65457     },
65458
65459     // when a record is added to a store
65460     onStoreAdd: function() {
65461
65462     },
65463
65464     // when a store is cleared remove all selections
65465     // (if there were any)
65466     onStoreClear: function() {
65467         if (this.selected.getCount > 0) {
65468             this.clearSelections();
65469             this.maybeFireSelectionChange(true);
65470         }
65471     },
65472
65473     // prune records from the SelectionModel if
65474     // they were selected at the time they were
65475     // removed.
65476     onStoreRemove: function(store, record) {
65477         var me = this,
65478             selected = me.selected;
65479
65480         if (me.locked || !me.pruneRemoved) {
65481             return;
65482         }
65483
65484         if (selected.remove(record)) {
65485             if (me.lastSelected == record) {
65486                 me.lastSelected = null;
65487             }
65488             if (me.getLastFocused() == record) {
65489                 me.setLastFocused(null);
65490             }
65491             me.maybeFireSelectionChange(true);
65492         }
65493     },
65494
65495     /**
65496      * Returns the count of selected records.
65497      * @return {Number} The number of selected records
65498      */
65499     getCount: function() {
65500         return this.selected.getCount();
65501     },
65502
65503     // cleanup.
65504     destroy: function() {
65505
65506     },
65507
65508     // if records are updated
65509     onStoreUpdate: function() {
65510
65511     },
65512
65513     // @abstract
65514     onSelectChange: function(record, isSelected, suppressEvent) {
65515
65516     },
65517
65518     // @abstract
65519     onLastFocusChanged: function(oldFocused, newFocused) {
65520
65521     },
65522
65523     // @abstract
65524     onEditorKey: function(field, e) {
65525
65526     },
65527
65528     // @abstract
65529     bindComponent: function(cmp) {
65530
65531     }
65532 });
65533 /**
65534  * @class Ext.selection.DataViewModel
65535  * @ignore
65536  */
65537 Ext.define('Ext.selection.DataViewModel', {
65538     extend: 'Ext.selection.Model',
65539
65540     requires: ['Ext.util.KeyNav'],
65541
65542     deselectOnContainerClick: true,
65543
65544     /**
65545      * @cfg {Boolean} enableKeyNav
65546      *
65547      * Turns on/off keyboard navigation within the DataView.
65548      */
65549     enableKeyNav: true,
65550
65551     constructor: function(cfg){
65552         this.addEvents(
65553             /**
65554              * @event beforedeselect
65555              * Fired before a record is deselected. If any listener returns false, the
65556              * deselection is cancelled.
65557              * @param {Ext.selection.DataViewModel} this
65558              * @param {Ext.data.Model} record The deselected record
65559              */
65560             'beforedeselect',
65561
65562             /**
65563              * @event beforeselect
65564              * Fired before a record is selected. If any listener returns false, the
65565              * selection is cancelled.
65566              * @param {Ext.selection.DataViewModel} this
65567              * @param {Ext.data.Model} record The selected record
65568              */
65569             'beforeselect',
65570
65571             /**
65572              * @event deselect
65573              * Fired after a record is deselected
65574              * @param {Ext.selection.DataViewModel} this
65575              * @param  {Ext.data.Model} record The deselected record
65576              */
65577             'deselect',
65578
65579             /**
65580              * @event select
65581              * Fired after a record is selected
65582              * @param {Ext.selection.DataViewModel} this
65583              * @param  {Ext.data.Model} record The selected record
65584              */
65585             'select'
65586         );
65587         this.callParent(arguments);
65588     },
65589
65590     bindComponent: function(view) {
65591         var me = this,
65592             eventListeners = {
65593                 refresh: me.refresh,
65594                 scope: me
65595             };
65596
65597         me.view = view;
65598         me.bind(view.getStore());
65599
65600         view.on(view.triggerEvent, me.onItemClick, me);
65601         view.on(view.triggerCtEvent, me.onContainerClick, me);
65602
65603         view.on(eventListeners);
65604
65605         if (me.enableKeyNav) {
65606             me.initKeyNav(view);
65607         }
65608     },
65609
65610     onItemClick: function(view, record, item, index, e) {
65611         this.selectWithEvent(record, e);
65612     },
65613
65614     onContainerClick: function() {
65615         if (this.deselectOnContainerClick) {
65616             this.deselectAll();
65617         }
65618     },
65619
65620     initKeyNav: function(view) {
65621         var me = this;
65622
65623         if (!view.rendered) {
65624             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
65625             return;
65626         }
65627
65628         view.el.set({
65629             tabIndex: -1
65630         });
65631         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
65632             down: Ext.pass(me.onNavKey, [1], me),
65633             right: Ext.pass(me.onNavKey, [1], me),
65634             left: Ext.pass(me.onNavKey, [-1], me),
65635             up: Ext.pass(me.onNavKey, [-1], me),
65636             scope: me
65637         });
65638     },
65639
65640     onNavKey: function(step) {
65641         step = step || 1;
65642         var me = this,
65643             view = me.view,
65644             selected = me.getSelection()[0],
65645             numRecords = me.view.store.getCount(),
65646             idx;
65647
65648         if (selected) {
65649             idx = view.indexOf(view.getNode(selected)) + step;
65650         } else {
65651             idx = 0;
65652         }
65653
65654         if (idx < 0) {
65655             idx = numRecords - 1;
65656         } else if (idx >= numRecords) {
65657             idx = 0;
65658         }
65659
65660         me.select(idx);
65661     },
65662
65663     // Allow the DataView to update the ui
65664     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
65665         var me = this,
65666             view = me.view,
65667             eventName = isSelected ? 'select' : 'deselect';
65668
65669         if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
65670                 commitFn() !== false) {
65671
65672             if (isSelected) {
65673                 view.onItemSelect(record);
65674             } else {
65675                 view.onItemDeselect(record);
65676             }
65677
65678             if (!suppressEvent) {
65679                 me.fireEvent(eventName, me, record);
65680             }
65681         }
65682     },
65683     
65684     destroy: function(){
65685         Ext.destroy(this.keyNav);
65686         this.callParent();
65687     }
65688 });
65689
65690 /**
65691  * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
65692  * options, such as:
65693  *
65694  * - {@link #path}
65695  * - {@link #expires}
65696  * - {@link #domain}
65697  * - {@link #secure}
65698  *
65699  * Example:
65700  *
65701  *     Ext.create('Ext.state.CookieProvider', {
65702  *         path: "/cgi-bin/",
65703  *         expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
65704  *         domain: "sencha.com"
65705  *     });
65706  *
65707  *     Ext.state.Manager.setProvider(cp);
65708  *
65709  * @constructor
65710  * Creates a new CookieProvider.
65711  * @param {Object} config (optional) Config object.
65712  * @return {Object}
65713  */
65714 Ext.define('Ext.state.CookieProvider', {
65715     extend: 'Ext.state.Provider',
65716
65717     /**
65718      * @cfg {String} path
65719      * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
65720      */
65721
65722     /**
65723      * @cfg {Date} expires
65724      * The cookie expiration date. Defaults to 7 days from now.
65725      */
65726
65727     /**
65728      * @cfg {String} domain
65729      * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
65730      * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
65731      * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
65732      * the 'www' like 'www.sencha.com'.
65733      */
65734
65735     /**
65736      * @cfg {Boolean} [secure=false]
65737      * True if the site is using SSL
65738      */
65739
65740     /**
65741      * Creates a new CookieProvider.
65742      * @param {Object} [config] Config object.
65743      */
65744     constructor : function(config){
65745         var me = this;
65746         me.path = "/";
65747         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
65748         me.domain = null;
65749         me.secure = false;
65750         me.callParent(arguments);
65751         me.state = me.readCookies();
65752     },
65753
65754     // private
65755     set : function(name, value){
65756         var me = this;
65757
65758         if(typeof value == "undefined" || value === null){
65759             me.clear(name);
65760             return;
65761         }
65762         me.setCookie(name, value);
65763         me.callParent(arguments);
65764     },
65765
65766     // private
65767     clear : function(name){
65768         this.clearCookie(name);
65769         this.callParent(arguments);
65770     },
65771
65772     // private
65773     readCookies : function(){
65774         var cookies = {},
65775             c = document.cookie + ";",
65776             re = /\s?(.*?)=(.*?);/g,
65777             prefix = this.prefix,
65778             len = prefix.length,
65779             matches,
65780             name,
65781             value;
65782
65783         while((matches = re.exec(c)) != null){
65784             name = matches[1];
65785             value = matches[2];
65786             if (name && name.substring(0, len) == prefix){
65787                 cookies[name.substr(len)] = this.decodeValue(value);
65788             }
65789         }
65790         return cookies;
65791     },
65792
65793     // private
65794     setCookie : function(name, value){
65795         var me = this;
65796
65797         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
65798            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
65799            ((me.path == null) ? "" : ("; path=" + me.path)) +
65800            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
65801            ((me.secure == true) ? "; secure" : "");
65802     },
65803
65804     // private
65805     clearCookie : function(name){
65806         var me = this;
65807
65808         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
65809            ((me.path == null) ? "" : ("; path=" + me.path)) +
65810            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
65811            ((me.secure == true) ? "; secure" : "");
65812     }
65813 });
65814
65815 /**
65816  * @class Ext.state.LocalStorageProvider
65817  * @extends Ext.state.Provider
65818  * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
65819  * If the browser does not support local storage, an exception will be thrown upon instantiating
65820  * this class.
65821  */
65822
65823 Ext.define('Ext.state.LocalStorageProvider', {
65824     /* Begin Definitions */
65825     
65826     extend: 'Ext.state.Provider',
65827     
65828     alias: 'state.localstorage',
65829     
65830     /* End Definitions */
65831    
65832     constructor: function(){
65833         var me = this;
65834         me.callParent(arguments);
65835         me.store = me.getStorageObject();
65836         me.state = me.readLocalStorage();
65837     },
65838     
65839     readLocalStorage: function(){
65840         var store = this.store,
65841             i = 0,
65842             len = store.length,
65843             prefix = this.prefix,
65844             prefixLen = prefix.length,
65845             data = {},
65846             key;
65847             
65848         for (; i < len; ++i) {
65849             key = store.key(i);
65850             if (key.substring(0, prefixLen) == prefix) {
65851                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
65852             }            
65853         }
65854         return data;
65855     },
65856     
65857     set : function(name, value){
65858         var me = this;
65859         
65860         me.clear(name);
65861         if (typeof value == "undefined" || value === null) {
65862             return;
65863         }
65864         me.store.setItem(me.prefix + name, me.encodeValue(value));
65865         me.callParent(arguments);
65866     },
65867
65868     // private
65869     clear : function(name){
65870         this.store.removeItem(this.prefix + name);
65871         this.callParent(arguments);
65872     },
65873     
65874     getStorageObject: function(){
65875         try {
65876             var supports = 'localStorage' in window && window['localStorage'] !== null;
65877             if (supports) {
65878                 return window.localStorage;
65879             }
65880         } catch (e) {
65881             return false;
65882         }
65883         //<debug>
65884         Ext.Error.raise('LocalStorage is not supported by the current browser');
65885         //</debug>
65886     }    
65887 });
65888
65889 /**
65890  * Represents a 2D point with x and y properties, useful for comparison and instantiation
65891  * from an event:
65892  *
65893  *     var point = Ext.util.Point.fromEvent(e);
65894  *
65895  */
65896 Ext.define('Ext.util.Point', {
65897
65898     /* Begin Definitions */
65899     extend: 'Ext.util.Region',
65900
65901     statics: {
65902
65903         /**
65904          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
65905          * @static
65906          * @param {Event} e The event
65907          * @return {Ext.util.Point}
65908          */
65909         fromEvent: function(e) {
65910             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
65911             return new this(e.pageX, e.pageY);
65912         }
65913     },
65914
65915     /* End Definitions */
65916
65917     /**
65918      * Creates a point from two coordinates.
65919      * @param {Number} x X coordinate.
65920      * @param {Number} y Y coordinate.
65921      */
65922     constructor: function(x, y) {
65923         this.callParent([y, x, y, x]);
65924     },
65925
65926     /**
65927      * Returns a human-eye-friendly string that represents this point,
65928      * useful for debugging
65929      * @return {String}
65930      */
65931     toString: function() {
65932         return "Point[" + this.x + "," + this.y + "]";
65933     },
65934
65935     /**
65936      * Compare this point and another point
65937      * @param {Ext.util.Point/Object} The point to compare with, either an instance
65938      * of Ext.util.Point or an object with left and top properties
65939      * @return {Boolean} Returns whether they are equivalent
65940      */
65941     equals: function(p) {
65942         return (this.x == p.x && this.y == p.y);
65943     },
65944
65945     /**
65946      * Whether the given point is not away from this point within the given threshold amount.
65947      * @param {Ext.util.Point/Object} p The point to check with, either an instance
65948      * of Ext.util.Point or an object with left and top properties
65949      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
65950      * @return {Boolean}
65951      */
65952     isWithin: function(p, threshold) {
65953         if (!Ext.isObject(threshold)) {
65954             threshold = {
65955                 x: threshold,
65956                 y: threshold
65957             };
65958         }
65959
65960         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
65961                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
65962     },
65963
65964     /**
65965      * Compare this point with another point when the x and y values of both points are rounded. E.g:
65966      * [100.3,199.8] will equals to [100, 200]
65967      * @param {Ext.util.Point/Object} p The point to compare with, either an instance
65968      * of Ext.util.Point or an object with x and y properties
65969      * @return {Boolean}
65970      */
65971     roundedEquals: function(p) {
65972         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
65973     }
65974 }, function() {
65975     /**
65976      * @method
65977      * Alias for {@link #translateBy}
65978      * @alias Ext.util.Region#translateBy
65979      */
65980     this.prototype.translate = Ext.util.Region.prototype.translateBy;
65981 });
65982
65983 /**
65984  * @class Ext.LoadMask
65985  * <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
65986  * {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
65987  * be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
65988  * <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
65989  * the Store's loading process.</p>
65990  * <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
65991  * object, and upon show, it will place itsef at the top of the hierarchy.</p>
65992  * <p>Example usage:</p>
65993  * <pre><code>
65994 // Basic mask:
65995 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
65996 myMask.show();
65997 </code></pre>
65998
65999  */
66000
66001 Ext.define('Ext.LoadMask', {
66002
66003     extend: 'Ext.Component',
66004
66005     alias: 'widget.loadmask',
66006
66007     /* Begin Definitions */
66008
66009     mixins: {
66010         floating: 'Ext.util.Floating'
66011     },
66012
66013     uses: ['Ext.data.StoreManager'],
66014
66015     /* End Definitions */
66016
66017     /**
66018      * @cfg {Ext.data.Store} store
66019      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
66020      * hidden on either load success, or load fail.
66021      */
66022
66023     /**
66024      * @cfg {String} msg
66025      * The text to display in a centered loading message box.
66026      */
66027     msg : 'Loading...',
66028     /**
66029      * @cfg {String} [msgCls="x-mask-loading"]
66030      * The CSS class to apply to the loading message element.
66031      */
66032     msgCls : Ext.baseCSSPrefix + 'mask-loading',
66033     
66034     /**
66035      * @cfg {Boolean} useMsg
66036      * Whether or not to use a loading message class or simply mask the bound element.
66037      */
66038     useMsg: true,
66039
66040     /**
66041      * Read-only. True if the mask is currently disabled so that it will not be displayed
66042      * @type Boolean
66043      */
66044     disabled: false,
66045
66046     baseCls: Ext.baseCSSPrefix + 'mask-msg',
66047
66048     renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
66049
66050     // Private. The whole point is that there's a mask.
66051     modal: true,
66052
66053     // Private. Obviously, it's floating.
66054     floating: {
66055         shadow: 'frame'
66056     },
66057
66058     // Private. Masks are not focusable
66059     focusOnToFront: false,
66060
66061     /**
66062      * Creates new LoadMask.
66063      * @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
66064      * <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
66065      * the mask will be automatically sized upon Component resize, the message box will be kept centered,
66066      * and the mask only be visible when the Component is.</p>
66067      * @param {Object} [config] The config object
66068      */
66069     constructor : function(el, config) {
66070         var me = this;
66071
66072         // If a Component passed, bind to it.
66073         if (el.isComponent) {
66074             me.ownerCt = el;
66075             me.bindComponent(el);
66076         }
66077         // Create a dumy Component encapsulating the specified Element
66078         else {
66079             me.ownerCt = new Ext.Component({
66080                 el: Ext.get(el),
66081                 rendered: true,
66082                 componentLayoutCounter: 1
66083             });
66084             me.container = el;
66085         }
66086         me.callParent([config]);
66087
66088         if (me.store) {
66089             me.bindStore(me.store, true);
66090         }
66091         me.renderData = {
66092             msgCls: me.msgCls
66093         };
66094         me.renderSelectors = {
66095             msgEl: 'div'
66096         };
66097     },
66098
66099     bindComponent: function(comp) {
66100         this.mon(comp, {
66101             resize: this.onComponentResize,
66102             scope: this
66103         });
66104     },
66105
66106     afterRender: function() {
66107         this.callParent(arguments);
66108         this.container = this.floatParent.getContentTarget();
66109     },
66110
66111     /**
66112      * @private
66113      * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
66114      */
66115     onComponentResize: function() {
66116         var me = this;
66117         if (me.rendered && me.isVisible()) {
66118             me.toFront();
66119             me.center();
66120         }
66121     },
66122
66123     /**
66124      * Changes the data store bound to this LoadMask.
66125      * @param {Ext.data.Store} store The store to bind to this LoadMask
66126      */
66127     bindStore : function(store, initial) {
66128         var me = this;
66129
66130         if (!initial && me.store) {
66131             me.mun(me.store, {
66132                 scope: me,
66133                 beforeload: me.onBeforeLoad,
66134                 load: me.onLoad,
66135                 exception: me.onLoad
66136             });
66137             if (!store) {
66138                 me.store = null;
66139             }
66140         }
66141         if (store) {
66142             store = Ext.data.StoreManager.lookup(store);
66143             me.mon(store, {
66144                 scope: me,
66145                 beforeload: me.onBeforeLoad,
66146                 load: me.onLoad,
66147                 exception: me.onLoad
66148             });
66149
66150         }
66151         me.store = store;
66152         if (store && store.isLoading()) {
66153             me.onBeforeLoad();
66154         }
66155     },
66156
66157     onDisable : function() {
66158         this.callParent(arguments);
66159         if (this.loading) {
66160             this.onLoad();
66161         }
66162     },
66163
66164     // private
66165     onBeforeLoad : function() {
66166         var me = this,
66167             owner = me.ownerCt || me.floatParent,
66168             origin;
66169         if (!this.disabled) {
66170             // If the owning Component has not been layed out, defer so that the ZIndexManager
66171             // gets to read its layed out size when sizing the modal mask
66172             if (owner.componentLayoutCounter) {
66173                 Ext.Component.prototype.show.call(me);
66174             } else {
66175                 // The code below is a 'run-once' interceptor.
66176                 origin = owner.afterComponentLayout;
66177                 owner.afterComponentLayout = function() {
66178                     owner.afterComponentLayout = origin;
66179                     origin.apply(owner, arguments);
66180                     if(me.loading) {
66181                         Ext.Component.prototype.show.call(me);
66182                     }
66183                 };
66184             }
66185         }
66186     },
66187
66188     onHide: function(){
66189         var me = this;
66190         me.callParent(arguments);
66191         me.showOnParentShow = true;
66192     },
66193
66194     onShow: function() {
66195         var me = this,
66196             msgEl = me.msgEl;
66197             
66198         me.callParent(arguments);
66199         me.loading = true;
66200         if (me.useMsg) {
66201             msgEl.show().update(me.msg);
66202         } else {
66203             msgEl.parent().hide();
66204         }
66205     },
66206
66207     afterShow: function() {
66208         this.callParent(arguments);
66209         this.center();
66210     },
66211
66212     // private
66213     onLoad : function() {
66214         this.loading = false;
66215         Ext.Component.prototype.hide.call(this);
66216     }
66217 });
66218 /**
66219  * @class Ext.view.AbstractView
66220  * @extends Ext.Component
66221  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
66222  * @private
66223  */
66224 Ext.define('Ext.view.AbstractView', {
66225     extend: 'Ext.Component',
66226     alternateClassName: 'Ext.view.AbstractView',
66227     requires: [
66228         'Ext.LoadMask',
66229         'Ext.data.StoreManager',
66230         'Ext.CompositeElementLite',
66231         'Ext.DomQuery',
66232         'Ext.selection.DataViewModel'
66233     ],
66234
66235     inheritableStatics: {
66236         getRecord: function(node) {
66237             return this.getBoundView(node).getRecord(node);
66238         },
66239
66240         getBoundView: function(node) {
66241             return Ext.getCmp(node.boundView);
66242         }
66243     },
66244
66245     /**
66246      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
66247      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
66248      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
66249      */
66250     /**
66251      * @cfg {Ext.data.Store} store (required)
66252      * The {@link Ext.data.Store} to bind this DataView to.
66253      */
66254
66255     /**
66256      * @cfg {Boolean} deferInitialRefresh
66257      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
66258      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
66259      * by the expensive update of the view structure.</p>
66260      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
66261      * <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.
66262      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
66263      */
66264     deferInitialRefresh: true,
66265
66266     /**
66267      * @cfg {String} itemSelector (required)
66268      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
66269      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
66270      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
66271      * only be one root level element that matches the selector for each record.
66272      */
66273
66274     /**
66275      * @cfg {String} itemCls
66276      * Specifies the class to be assigned to each element in the view when used in conjunction with the
66277      * {@link #itemTpl} configuration.
66278      */
66279     itemCls: Ext.baseCSSPrefix + 'dataview-item',
66280
66281     /**
66282      * @cfg {String/String[]/Ext.XTemplate} itemTpl
66283      * The inner portion of the item template to be rendered. Follows an XTemplate
66284      * structure and will be placed inside of a tpl.
66285      */
66286
66287     /**
66288      * @cfg {String} overItemCls
66289      * A CSS class to apply to each item in the view on mouseover.
66290      * Ensure {@link #trackOver} is set to `true` to make use of this.
66291      */
66292
66293     /**
66294      * @cfg {String} loadingText
66295      * A string to display during data load operations.  If specified, this text will be
66296      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
66297      * contents will continue to display normally until the new data is loaded and the contents are replaced.
66298      */
66299     loadingText: 'Loading...',
66300
66301     /**
66302      * @cfg {Boolean/Object} loadMask
66303      * False to disable a load mask from displaying will the view is loading. This can also be a
66304      * {@link Ext.LoadMask} configuration object.
66305      */
66306     loadMask: true,
66307
66308     /**
66309      * @cfg {String} loadingCls
66310      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
66311      */
66312
66313     /**
66314      * @cfg {Boolean} loadingUseMsg
66315      * Whether or not to use the loading message.
66316      * @private
66317      */
66318     loadingUseMsg: true,
66319
66320
66321     /**
66322      * @cfg {Number} loadingHeight
66323      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
66324      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
66325      * loading mask is applied and there are no other contents in the data view.
66326      */
66327
66328     /**
66329      * @cfg {String} [selectedItemCls='x-view-selected']
66330      * A CSS class to apply to each selected item in the view.
66331      */
66332     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
66333
66334     /**
66335      * @cfg {String} emptyText
66336      * The text to display in the view when there is no data to display.
66337      * Note that when using local data the emptyText will not be displayed unless you set
66338      * the {@link #deferEmptyText} option to false.
66339      */
66340     emptyText: "",
66341
66342     /**
66343      * @cfg {Boolean} deferEmptyText
66344      * True to defer emptyText being applied until the store's first load.
66345      */
66346     deferEmptyText: true,
66347
66348     /**
66349      * @cfg {Boolean} trackOver
66350      * True to enable mouseenter and mouseleave events
66351      */
66352     trackOver: false,
66353
66354     /**
66355      * @cfg {Boolean} blockRefresh
66356      * Set this to true to ignore datachanged events on the bound store. This is useful if
66357      * you wish to provide custom transition animations via a plugin
66358      */
66359     blockRefresh: false,
66360
66361     /**
66362      * @cfg {Boolean} disableSelection
66363      * True to disable selection within the DataView. This configuration will lock the selection model
66364      * that the DataView uses.
66365      */
66366
66367
66368     //private
66369     last: false,
66370
66371     triggerEvent: 'itemclick',
66372     triggerCtEvent: 'containerclick',
66373
66374     addCmpEvents: function() {
66375
66376     },
66377
66378     // private
66379     initComponent : function(){
66380         var me = this,
66381             isDef = Ext.isDefined,
66382             itemTpl = me.itemTpl,
66383             memberFn = {};
66384
66385         if (itemTpl) {
66386             if (Ext.isArray(itemTpl)) {
66387                 // string array
66388                 itemTpl = itemTpl.join('');
66389             } else if (Ext.isObject(itemTpl)) {
66390                 // tpl instance
66391                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
66392                 itemTpl = itemTpl.html;
66393             }
66394
66395             if (!me.itemSelector) {
66396                 me.itemSelector = '.' + me.itemCls;
66397             }
66398
66399             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
66400             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
66401         }
66402
66403         //<debug>
66404         if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
66405             Ext.Error.raise({
66406                 sourceClass: 'Ext.view.View',
66407                 tpl: me.tpl,
66408                 itemSelector: me.itemSelector,
66409                 msg: "DataView requires both tpl and itemSelector configurations to be defined."
66410             });
66411         }
66412         //</debug>
66413
66414         me.callParent();
66415         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
66416             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
66417         }
66418
66419         //<debug>
66420         // backwards compat alias for overClass/selectedClass
66421         // TODO: Consider support for overCls generation Ext.Component config
66422         if (isDef(me.overCls) || isDef(me.overClass)) {
66423             if (Ext.isDefined(Ext.global.console)) {
66424                 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
66425             }
66426             me.overItemCls = me.overCls || me.overClass;
66427             delete me.overCls;
66428             delete me.overClass;
66429         }
66430
66431         if (me.overItemCls) {
66432             me.trackOver = true;
66433         }
66434
66435         if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
66436             if (Ext.isDefined(Ext.global.console)) {
66437                 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
66438             }
66439             me.selectedItemCls = me.selectedCls || me.selectedClass;
66440             delete me.selectedCls;
66441             delete me.selectedClass;
66442         }
66443         //</debug>
66444
66445         me.addEvents(
66446             /**
66447              * @event beforerefresh
66448              * Fires before the view is refreshed
66449              * @param {Ext.view.View} this The DataView object
66450              */
66451             'beforerefresh',
66452             /**
66453              * @event refresh
66454              * Fires when the view is refreshed
66455              * @param {Ext.view.View} this The DataView object
66456              */
66457             'refresh',
66458             /**
66459              * @event viewready
66460              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
66461              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
66462              * for selection until this event fires.
66463              * @param {Ext.view.View} this
66464              */
66465             'viewready',
66466             /**
66467              * @event itemupdate
66468              * Fires when the node associated with an individual record is updated
66469              * @param {Ext.data.Model} record The model instance
66470              * @param {Number} index The index of the record/node
66471              * @param {HTMLElement} node The node that has just been updated
66472              */
66473             'itemupdate',
66474             /**
66475              * @event itemadd
66476              * Fires when the nodes associated with an recordset have been added to the underlying store
66477              * @param {Ext.data.Model[]} records The model instance
66478              * @param {Number} index The index at which the set of record/nodes starts
66479              * @param {HTMLElement[]} node The node that has just been updated
66480              */
66481             'itemadd',
66482             /**
66483              * @event itemremove
66484              * Fires when the node associated with an individual record is removed
66485              * @param {Ext.data.Model} record The model instance
66486              * @param {Number} index The index of the record/node
66487              */
66488             'itemremove'
66489         );
66490
66491         me.addCmpEvents();
66492
66493         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
66494         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
66495         me.all = new Ext.CompositeElementLite();
66496     },
66497
66498     onRender: function() {
66499         var me = this,
66500             mask = me.loadMask,
66501             cfg = {
66502                 msg: me.loadingText,
66503                 msgCls: me.loadingCls,
66504                 useMsg: me.loadingUseMsg
66505             };
66506
66507         me.callParent(arguments);
66508
66509         if (mask) {
66510             // either a config object
66511             if (Ext.isObject(mask)) {
66512                 cfg = Ext.apply(cfg, mask);
66513             }
66514             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
66515             // If this DataView is floating, then mask this DataView.
66516             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
66517             // LoadMask captures the element upon render.
66518             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
66519             me.loadMask.on({
66520                 scope: me,
66521                 beforeshow: me.onMaskBeforeShow,
66522                 hide: me.onMaskHide
66523             });
66524         }
66525     },
66526
66527     onMaskBeforeShow: function(){
66528         var loadingHeight = this.loadingHeight;
66529         
66530         this.getSelectionModel().deselectAll();
66531         if (loadingHeight) {
66532             this.setCalculatedSize(undefined, loadingHeight);
66533         }
66534     },
66535
66536     onMaskHide: function(){
66537         var me = this;
66538         
66539         if (!me.destroying && me.loadingHeight) {
66540             me.setHeight(me.height);
66541         }
66542     },
66543
66544     afterRender: function() {
66545         this.callParent(arguments);
66546
66547         // Init the SelectionModel after any on('render') listeners have been added.
66548         // Drag plugins create a DragDrop instance in a render listener, and that needs
66549         // to see an itemmousedown event first.
66550         this.getSelectionModel().bindComponent(this);
66551     },
66552
66553     /**
66554      * Gets the selection model for this view.
66555      * @return {Ext.selection.Model} The selection model
66556      */
66557     getSelectionModel: function(){
66558         var me = this,
66559             mode = 'SINGLE';
66560
66561         if (!me.selModel) {
66562             me.selModel = {};
66563         }
66564
66565         if (me.simpleSelect) {
66566             mode = 'SIMPLE';
66567         } else if (me.multiSelect) {
66568             mode = 'MULTI';
66569         }
66570
66571         Ext.applyIf(me.selModel, {
66572             allowDeselect: me.allowDeselect,
66573             mode: mode
66574         });
66575
66576         if (!me.selModel.events) {
66577             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
66578         }
66579
66580         if (!me.selModel.hasRelaySetup) {
66581             me.relayEvents(me.selModel, [
66582                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
66583             ]);
66584             me.selModel.hasRelaySetup = true;
66585         }
66586
66587         // lock the selection model if user
66588         // has disabled selection
66589         if (me.disableSelection) {
66590             me.selModel.locked = true;
66591         }
66592
66593         return me.selModel;
66594     },
66595
66596     /**
66597      * Refreshes the view by reloading the data from the store and re-rendering the template.
66598      */
66599     refresh: function() {
66600         var me = this,
66601             el,
66602             records;
66603
66604         if (!me.rendered || me.isDestroyed) {
66605             return;
66606         }
66607
66608         me.fireEvent('beforerefresh', me);
66609         el = me.getTargetEl();
66610         records = me.store.getRange();
66611
66612         el.update('');
66613         if (records.length < 1) {
66614             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
66615                 el.update(me.emptyText);
66616             }
66617             me.all.clear();
66618         } else {
66619             me.tpl.overwrite(el, me.collectData(records, 0));
66620             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
66621             me.updateIndexes(0);
66622         }
66623
66624         me.selModel.refresh();
66625         me.hasSkippedEmptyText = true;
66626         me.fireEvent('refresh', me);
66627
66628         // Upon first refresh, fire the viewready event.
66629         // Reconfiguring the grid "renews" this event.
66630         if (!me.viewReady) {
66631             // Fire an event when deferred content becomes available.
66632             // This supports grid Panel's deferRowRender capability
66633             me.viewReady = true;
66634             me.fireEvent('viewready', me);
66635         }
66636     },
66637
66638     /**
66639      * Function which can be overridden to provide custom formatting for each Record that is used by this
66640      * DataView's {@link #tpl template} to render each node.
66641      * @param {Object/Object[]} data The raw data object that was used to create the Record.
66642      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
66643      * @param {Ext.data.Model} record The Record being prepared for rendering.
66644      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
66645      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
66646      */
66647     prepareData: function(data, index, record) {
66648         if (record) {
66649             Ext.apply(data, record.getAssociatedData());
66650         }
66651         return data;
66652     },
66653
66654     /**
66655      * <p>Function which can be overridden which returns the data object passed to this
66656      * DataView's {@link #tpl template} to render the whole DataView.</p>
66657      * <p>This is usually an Array of data objects, each element of which is processed by an
66658      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
66659      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
66660      * provide non-repeating data such as headings, totals etc.</p>
66661      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
66662      * @param {Number} startIndex the index number of the Record being prepared for rendering.
66663      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
66664      * contain <i>named</i> properties.
66665      */
66666     collectData : function(records, startIndex){
66667         var r = [],
66668             i = 0,
66669             len = records.length,
66670             record;
66671
66672         for(; i < len; i++){
66673             record = records[i];
66674             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
66675         }
66676         return r;
66677     },
66678
66679     // private
66680     bufferRender : function(records, index){
66681         var div = document.createElement('div');
66682         this.tpl.overwrite(div, this.collectData(records, index));
66683         return Ext.query(this.getItemSelector(), div);
66684     },
66685
66686     // private
66687     onUpdate : function(ds, record){
66688         var me = this,
66689             index = me.store.indexOf(record),
66690             node;
66691
66692         if (index > -1){
66693             node = me.bufferRender([record], index)[0];
66694             // ensure the node actually exists in the DOM
66695             if (me.getNode(record)) {
66696                 me.all.replaceElement(index, node, true);
66697                 me.updateIndexes(index, index);
66698                 // Maintain selection after update
66699                 // TODO: Move to approriate event handler.
66700                 me.selModel.refresh();
66701                 me.fireEvent('itemupdate', record, index, node);
66702             }
66703         }
66704
66705     },
66706
66707     // private
66708     onAdd : function(ds, records, index) {
66709         var me = this,
66710             nodes;
66711
66712         if (me.all.getCount() === 0) {
66713             me.refresh();
66714             return;
66715         }
66716
66717         nodes = me.bufferRender(records, index);
66718         me.doAdd(nodes, records, index);
66719
66720         me.selModel.refresh();
66721         me.updateIndexes(index);
66722         me.fireEvent('itemadd', records, index, nodes);
66723     },
66724
66725     doAdd: function(nodes, records, index) {
66726         var all = this.all;
66727
66728         if (index < all.getCount()) {
66729             all.item(index).insertSibling(nodes, 'before', true);
66730         } else {
66731             all.last().insertSibling(nodes, 'after', true);
66732         }
66733
66734         Ext.Array.insert(all.elements, index, nodes);
66735     },
66736
66737     // private
66738     onRemove : function(ds, record, index) {
66739         var me = this;
66740
66741         me.doRemove(record, index);
66742         me.updateIndexes(index);
66743         if (me.store.getCount() === 0){
66744             me.refresh();
66745         }
66746         me.fireEvent('itemremove', record, index);
66747     },
66748
66749     doRemove: function(record, index) {
66750         this.all.removeElement(index, true);
66751     },
66752
66753     /**
66754      * Refreshes an individual node's data from the store.
66755      * @param {Number} index The item's data index in the store
66756      */
66757     refreshNode : function(index){
66758         this.onUpdate(this.store, this.store.getAt(index));
66759     },
66760
66761     // private
66762     updateIndexes : function(startIndex, endIndex) {
66763         var ns = this.all.elements,
66764             records = this.store.getRange(),
66765             i;
66766             
66767         startIndex = startIndex || 0;
66768         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
66769         for(i = startIndex; i <= endIndex; i++){
66770             ns[i].viewIndex = i;
66771             ns[i].viewRecordId = records[i].internalId;
66772             if (!ns[i].boundView) {
66773                 ns[i].boundView = this.id;
66774             }
66775         }
66776     },
66777
66778     /**
66779      * Returns the store associated with this DataView.
66780      * @return {Ext.data.Store} The store
66781      */
66782     getStore : function(){
66783         return this.store;
66784     },
66785
66786     /**
66787      * Changes the data store bound to this view and refreshes it.
66788      * @param {Ext.data.Store} store The store to bind to this view
66789      */
66790     bindStore : function(store, initial) {
66791         var me = this,
66792             maskStore;
66793
66794         if (!initial && me.store) {
66795             if (store !== me.store && me.store.autoDestroy) {
66796                 me.store.destroyStore();
66797             }
66798             else {
66799                 me.mun(me.store, {
66800                     scope: me,
66801                     datachanged: me.onDataChanged,
66802                     add: me.onAdd,
66803                     remove: me.onRemove,
66804                     update: me.onUpdate,
66805                     clear: me.refresh
66806                 });
66807             }
66808             if (!store) {
66809                 // Ensure we have an instantiated LoadMask before we unbind it.
66810                 if (me.loadMask && me.loadMask.bindStore) {
66811                     me.loadMask.bindStore(null);
66812                 }
66813                 me.store = null;
66814             }
66815         }
66816         if (store) {
66817             store = Ext.data.StoreManager.lookup(store);
66818             me.mon(store, {
66819                 scope: me,
66820                 datachanged: me.onDataChanged,
66821                 add: me.onAdd,
66822                 remove: me.onRemove,
66823                 update: me.onUpdate,
66824                 clear: me.refresh
66825             });
66826             // Ensure we have an instantiated LoadMask before we bind it.
66827             if (me.loadMask && me.loadMask.bindStore) {
66828                 // View's store is a NodeStore, use owning TreePanel's Store
66829                 if (Ext.Array.contains(store.alias, 'store.node')) {
66830                     maskStore = this.ownerCt.store;
66831                 } else {
66832                     maskStore = store;
66833                 }
66834                 me.loadMask.bindStore(maskStore);
66835             }
66836         }
66837
66838         // Flag to say that initial refresh has not been performed.
66839         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
66840         me.viewReady = false;
66841
66842         me.store = store;
66843         // Bind the store to our selection model
66844         me.getSelectionModel().bind(store);
66845
66846         /*
66847          * This code used to have checks for:
66848          * if (store && (!initial || store.getCount() || me.emptyText)) {
66849          * Instead, just trigger a refresh and let the view itself figure out
66850          * what needs to happen. It can cause incorrect display if our store
66851          * has no data.
66852          */
66853         if (store) {
66854             if (initial && me.deferInitialRefresh) {
66855                 Ext.Function.defer(function () {
66856                     if (!me.isDestroyed) {
66857                         me.refresh(true);
66858                     }
66859                 }, 1);
66860             } else {
66861                 me.refresh(true);
66862             }
66863         }
66864     },
66865
66866     /**
66867      * @private
66868      * Calls this.refresh if this.blockRefresh is not true
66869      */
66870     onDataChanged: function() {
66871         if (this.blockRefresh !== true) {
66872             this.refresh.apply(this, arguments);
66873         }
66874     },
66875
66876     /**
66877      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
66878      * @param {HTMLElement} node
66879      * @return {HTMLElement} The template node
66880      */
66881     findItemByChild: function(node){
66882         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
66883     },
66884
66885     /**
66886      * Returns the template node by the Ext.EventObject or null if it is not found.
66887      * @param {Ext.EventObject} e
66888      */
66889     findTargetByEvent: function(e) {
66890         return e.getTarget(this.getItemSelector(), this.getTargetEl());
66891     },
66892
66893
66894     /**
66895      * Gets the currently selected nodes.
66896      * @return {HTMLElement[]} An array of HTMLElements
66897      */
66898     getSelectedNodes: function(){
66899         var nodes   = [],
66900             records = this.selModel.getSelection(),
66901             ln = records.length,
66902             i  = 0;
66903
66904         for (; i < ln; i++) {
66905             nodes.push(this.getNode(records[i]));
66906         }
66907
66908         return nodes;
66909     },
66910
66911     /**
66912      * Gets an array of the records from an array of nodes
66913      * @param {HTMLElement[]} nodes The nodes to evaluate
66914      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
66915      */
66916     getRecords: function(nodes) {
66917         var records = [],
66918             i = 0,
66919             len = nodes.length,
66920             data = this.store.data;
66921
66922         for (; i < len; i++) {
66923             records[records.length] = data.getByKey(nodes[i].viewRecordId);
66924         }
66925
66926         return records;
66927     },
66928
66929     /**
66930      * Gets a record from a node
66931      * @param {Ext.Element/HTMLElement} node The node to evaluate
66932      *
66933      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
66934      */
66935     getRecord: function(node){
66936         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
66937     },
66938
66939
66940     /**
66941      * Returns true if the passed node is selected, else false.
66942      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
66943      * @return {Boolean} True if selected, else false
66944      */
66945     isSelected : function(node) {
66946         // TODO: El/Idx/Record
66947         var r = this.getRecord(node);
66948         return this.selModel.isSelected(r);
66949     },
66950
66951     /**
66952      * Selects a record instance by record instance or index.
66953      * @param {Ext.data.Model[]/Number} records An array of records or an index
66954      * @param {Boolean} [keepExisting] True to keep existing selections
66955      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
66956      */
66957     select: function(records, keepExisting, suppressEvent) {
66958         this.selModel.select(records, keepExisting, suppressEvent);
66959     },
66960
66961     /**
66962      * Deselects a record instance by record instance or index.
66963      * @param {Ext.data.Model[]/Number} records An array of records or an index
66964      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
66965      */
66966     deselect: function(records, suppressEvent) {
66967         this.selModel.deselect(records, suppressEvent);
66968     },
66969
66970     /**
66971      * Gets a template node.
66972      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
66973      * the id of a template node or the record associated with the node.
66974      * @return {HTMLElement} The node or null if it wasn't found
66975      */
66976     getNode : function(nodeInfo) {
66977         if (!this.rendered) {
66978             return null;
66979         }
66980         if (Ext.isString(nodeInfo)) {
66981             return document.getElementById(nodeInfo);
66982         }
66983         if (Ext.isNumber(nodeInfo)) {
66984             return this.all.elements[nodeInfo];
66985         }
66986         if (nodeInfo instanceof Ext.data.Model) {
66987             return this.getNodeByRecord(nodeInfo);
66988         }
66989         return nodeInfo; // already an HTMLElement
66990     },
66991
66992     /**
66993      * @private
66994      */
66995     getNodeByRecord: function(record) {
66996         var ns = this.all.elements,
66997             ln = ns.length,
66998             i = 0;
66999
67000         for (; i < ln; i++) {
67001             if (ns[i].viewRecordId === record.internalId) {
67002                 return ns[i];
67003             }
67004         }
67005
67006         return null;
67007     },
67008
67009     /**
67010      * Gets a range nodes.
67011      * @param {Number} start (optional) The index of the first node in the range
67012      * @param {Number} end (optional) The index of the last node in the range
67013      * @return {HTMLElement[]} An array of nodes
67014      */
67015     getNodes: function(start, end) {
67016         var ns = this.all.elements,
67017             nodes = [],
67018             i;
67019
67020         start = start || 0;
67021         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
67022         if (start <= end) {
67023             for (i = start; i <= end && ns[i]; i++) {
67024                 nodes.push(ns[i]);
67025             }
67026         } else {
67027             for (i = start; i >= end && ns[i]; i--) {
67028                 nodes.push(ns[i]);
67029             }
67030         }
67031         return nodes;
67032     },
67033
67034     /**
67035      * Finds the index of the passed node.
67036      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
67037      * or a record associated with a node.
67038      * @return {Number} The index of the node or -1
67039      */
67040     indexOf: function(node) {
67041         node = this.getNode(node);
67042         if (Ext.isNumber(node.viewIndex)) {
67043             return node.viewIndex;
67044         }
67045         return this.all.indexOf(node);
67046     },
67047
67048     onDestroy : function() {
67049         var me = this;
67050
67051         me.all.clear();
67052         me.callParent();
67053         me.bindStore(null);
67054         me.selModel.destroy();
67055     },
67056
67057     // invoked by the selection model to maintain visual UI cues
67058     onItemSelect: function(record) {
67059         var node = this.getNode(record);
67060         
67061         if (node) {
67062             Ext.fly(node).addCls(this.selectedItemCls);
67063         }
67064     },
67065
67066     // invoked by the selection model to maintain visual UI cues
67067     onItemDeselect: function(record) {
67068         var node = this.getNode(record);
67069         
67070         if (node) {
67071             Ext.fly(node).removeCls(this.selectedItemCls);
67072         }
67073     },
67074
67075     getItemSelector: function() {
67076         return this.itemSelector;
67077     }
67078 }, function() {
67079     // all of this information is available directly
67080     // from the SelectionModel itself, the only added methods
67081     // to DataView regarding selection will perform some transformation/lookup
67082     // between HTMLElement/Nodes to records and vice versa.
67083     Ext.deprecate('extjs', '4.0', function() {
67084         Ext.view.AbstractView.override({
67085             /**
67086              * @cfg {Boolean} [multiSelect=false]
67087              * True to allow selection of more than one item at a time, false to allow selection of only a single item
67088              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
67089              */
67090             /**
67091              * @cfg {Boolean} [singleSelect=false]
67092              * True to allow selection of exactly one item at a time, false to allow no selection at all.
67093              * Note that if {@link #multiSelect} = true, this value will be ignored.
67094              */
67095             /**
67096              * @cfg {Boolean} [simpleSelect=false]
67097              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
67098              * false to force the user to hold Ctrl or Shift to select more than on item.
67099              */
67100
67101             /**
67102              * Gets the number of selected nodes.
67103              * @return {Number} The node count
67104              */
67105             getSelectionCount : function(){
67106                 if (Ext.global.console) {
67107                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
67108                 }
67109                 return this.selModel.getSelection().length;
67110             },
67111
67112             /**
67113              * Gets an array of the selected records
67114              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
67115              */
67116             getSelectedRecords : function(){
67117                 if (Ext.global.console) {
67118                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
67119                 }
67120                 return this.selModel.getSelection();
67121             },
67122
67123             select: function(records, keepExisting, supressEvents) {
67124                 if (Ext.global.console) {
67125                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
67126                 }
67127                 var sm = this.getSelectionModel();
67128                 return sm.select.apply(sm, arguments);
67129             },
67130
67131             clearSelections: function() {
67132                 if (Ext.global.console) {
67133                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
67134                 }
67135                 var sm = this.getSelectionModel();
67136                 return sm.deselectAll();
67137             }
67138         });
67139     });
67140 });
67141
67142 /**
67143  * @class Ext.Action
67144  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
67145  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
67146  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
67147  * and {@link Ext.menu.Menu} components).</p>
67148  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
67149  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
67150  * called at once through a single call to the Action.</p>
67151  * <p>Any Component that is to be configured with an Action must also support
67152  * the following methods:<ul>
67153  * <li><code>setText(string)</code></li>
67154  * <li><code>setIconCls(string)</code></li>
67155  * <li><code>setDisabled(boolean)</code></li>
67156  * <li><code>setVisible(boolean)</code></li>
67157  * <li><code>setHandler(function)</code></li></ul></p>
67158  * <p>This allows the Action to control its associated Components.</p>
67159  * Example usage:<br>
67160  * <pre><code>
67161 // Define the shared Action.  Each Component below will have the same
67162 // display text and icon, and will display the same message on click.
67163 var action = new Ext.Action({
67164     {@link #text}: 'Do something',
67165     {@link #handler}: function(){
67166         Ext.Msg.alert('Click', 'You did something.');
67167     },
67168     {@link #iconCls}: 'do-something',
67169     {@link #itemId}: 'myAction'
67170 });
67171
67172 var panel = new Ext.panel.Panel({
67173     title: 'Actions',
67174     width: 500,
67175     height: 300,
67176     tbar: [
67177         // Add the Action directly to a toolbar as a menu button
67178         action,
67179         {
67180             text: 'Action Menu',
67181             // Add the Action to a menu as a text item
67182             menu: [action]
67183         }
67184     ],
67185     items: [
67186         // Add the Action to the panel body as a standard button
67187         new Ext.button.Button(action)
67188     ],
67189     renderTo: Ext.getBody()
67190 });
67191
67192 // Change the text for all components using the Action
67193 action.setText('Something else');
67194
67195 // Reference an Action through a container using the itemId
67196 var btn = panel.getComponent('myAction');
67197 var aRef = btn.baseAction;
67198 aRef.setText('New text');
67199 </code></pre>
67200  */
67201 Ext.define('Ext.Action', {
67202
67203     /* Begin Definitions */
67204
67205     /* End Definitions */
67206
67207     /**
67208      * @cfg {String} [text='']
67209      * The text to set for all components configured by this Action.
67210      */
67211     /**
67212      * @cfg {String} [iconCls='']
67213      * The CSS class selector that specifies a background image to be used as the header icon for
67214      * all components configured by this Action.
67215      * <p>An example of specifying a custom icon class would be something like:
67216      * </p><pre><code>
67217 // specify the property in the config for the class:
67218      ...
67219      iconCls: 'do-something'
67220
67221 // css class that specifies background image to be used as the icon image:
67222 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
67223 </code></pre>
67224      */
67225     /**
67226      * @cfg {Boolean} [disabled=false]
67227      * True to disable all components configured by this Action, false to enable them.
67228      */
67229     /**
67230      * @cfg {Boolean} [hidden=false]
67231      * True to hide all components configured by this Action, false to show them.
67232      */
67233     /**
67234      * @cfg {Function} handler
67235      * The function that will be invoked by each component tied to this Action
67236      * when the component's primary event is triggered.
67237      */
67238     /**
67239      * @cfg {String} itemId
67240      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
67241      */
67242     /**
67243      * @cfg {Object} scope
67244      * The scope (this reference) in which the {@link #handler} is executed.
67245      * Defaults to the browser window.
67246      */
67247
67248     /**
67249      * Creates new Action.
67250      * @param {Object} config Config object.
67251      */
67252     constructor : function(config){
67253         this.initialConfig = config;
67254         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
67255         this.items = [];
67256     },
67257
67258     // private
67259     isAction : true,
67260
67261     /**
67262      * Sets the text to be displayed by all components configured by this Action.
67263      * @param {String} text The text to display
67264      */
67265     setText : function(text){
67266         this.initialConfig.text = text;
67267         this.callEach('setText', [text]);
67268     },
67269
67270     /**
67271      * Gets the text currently displayed by all components configured by this Action.
67272      */
67273     getText : function(){
67274         return this.initialConfig.text;
67275     },
67276
67277     /**
67278      * Sets the icon CSS class for all components configured by this Action.  The class should supply
67279      * a background image that will be used as the icon image.
67280      * @param {String} cls The CSS class supplying the icon image
67281      */
67282     setIconCls : function(cls){
67283         this.initialConfig.iconCls = cls;
67284         this.callEach('setIconCls', [cls]);
67285     },
67286
67287     /**
67288      * Gets the icon CSS class currently used by all components configured by this Action.
67289      */
67290     getIconCls : function(){
67291         return this.initialConfig.iconCls;
67292     },
67293
67294     /**
67295      * Sets the disabled state of all components configured by this Action.  Shortcut method
67296      * for {@link #enable} and {@link #disable}.
67297      * @param {Boolean} disabled True to disable the component, false to enable it
67298      */
67299     setDisabled : function(v){
67300         this.initialConfig.disabled = v;
67301         this.callEach('setDisabled', [v]);
67302     },
67303
67304     /**
67305      * Enables all components configured by this Action.
67306      */
67307     enable : function(){
67308         this.setDisabled(false);
67309     },
67310
67311     /**
67312      * Disables all components configured by this Action.
67313      */
67314     disable : function(){
67315         this.setDisabled(true);
67316     },
67317
67318     /**
67319      * Returns true if the components using this Action are currently disabled, else returns false.
67320      */
67321     isDisabled : function(){
67322         return this.initialConfig.disabled;
67323     },
67324
67325     /**
67326      * Sets the hidden state of all components configured by this Action.  Shortcut method
67327      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
67328      * @param {Boolean} hidden True to hide the component, false to show it
67329      */
67330     setHidden : function(v){
67331         this.initialConfig.hidden = v;
67332         this.callEach('setVisible', [!v]);
67333     },
67334
67335     /**
67336      * Shows all components configured by this Action.
67337      */
67338     show : function(){
67339         this.setHidden(false);
67340     },
67341
67342     /**
67343      * Hides all components configured by this Action.
67344      */
67345     hide : function(){
67346         this.setHidden(true);
67347     },
67348
67349     /**
67350      * Returns true if the components configured by this Action are currently hidden, else returns false.
67351      */
67352     isHidden : function(){
67353         return this.initialConfig.hidden;
67354     },
67355
67356     /**
67357      * Sets the function that will be called by each Component using this action when its primary event is triggered.
67358      * @param {Function} fn The function that will be invoked by the action's components.  The function
67359      * will be called with no arguments.
67360      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
67361      */
67362     setHandler : function(fn, scope){
67363         this.initialConfig.handler = fn;
67364         this.initialConfig.scope = scope;
67365         this.callEach('setHandler', [fn, scope]);
67366     },
67367
67368     /**
67369      * Executes the specified function once for each Component currently tied to this Action.  The function passed
67370      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
67371      * @param {Function} fn The function to execute for each component
67372      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
67373      */
67374     each : function(fn, scope){
67375         Ext.each(this.items, fn, scope);
67376     },
67377
67378     // private
67379     callEach : function(fnName, args){
67380         var items = this.items,
67381             i = 0,
67382             len = items.length;
67383
67384         for(; i < len; i++){
67385             items[i][fnName].apply(items[i], args);
67386         }
67387     },
67388
67389     // private
67390     addComponent : function(comp){
67391         this.items.push(comp);
67392         comp.on('destroy', this.removeComponent, this);
67393     },
67394
67395     // private
67396     removeComponent : function(comp){
67397         Ext.Array.remove(this.items, comp);
67398     },
67399
67400     /**
67401      * Executes this Action manually using the handler function specified in the original config object
67402      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
67403      * function will be passed on to the handler function.
67404      * @param {Object...} args (optional) Variable number of arguments passed to the handler function
67405      */
67406     execute : function(){
67407         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
67408     }
67409 });
67410
67411 /**
67412  * Component layout for editors
67413  * @class Ext.layout.component.Editor
67414  * @extends Ext.layout.component.Component
67415  * @private
67416  */
67417 Ext.define('Ext.layout.component.Editor', {
67418
67419     /* Begin Definitions */
67420
67421     alias: ['layout.editor'],
67422
67423     extend: 'Ext.layout.component.Component',
67424
67425     /* End Definitions */
67426
67427     onLayout: function(width, height) {
67428         var me = this,
67429             owner = me.owner,
67430             autoSize = owner.autoSize;
67431             
67432         if (autoSize === true) {
67433             autoSize = {
67434                 width: 'field',
67435                 height: 'field'    
67436             };
67437         }
67438         
67439         if (autoSize) {
67440             width = me.getDimension(owner, autoSize.width, 'Width', width);
67441             height = me.getDimension(owner, autoSize.height, 'Height', height);
67442         }
67443         me.setTargetSize(width, height);
67444         owner.field.setSize(width, height);
67445     },
67446     
67447     getDimension: function(owner, type, dimension, actual){
67448         var method = 'get' + dimension;
67449         switch (type) {
67450             case 'boundEl':
67451                 return owner.boundEl[method]();
67452             case 'field':
67453                 return owner.field[method]();
67454             default:
67455                 return actual;
67456         }
67457     }
67458 });
67459 /**
67460  * @class Ext.Editor
67461  * @extends Ext.Component
67462  *
67463  * <p>
67464  * The Editor class is used to provide inline editing for elements on the page. The editor
67465  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
67466  * The editor is a floating Component, when the editor is shown it is automatically aligned to
67467  * display over the top of the bound element it is editing. The Editor contains several options
67468  * for how to handle key presses:
67469  * <ul>
67470  * <li>{@link #completeOnEnter}</li>
67471  * <li>{@link #cancelOnEsc}</li>
67472  * <li>{@link #swallowKeys}</li>
67473  * </ul>
67474  * It also has options for how to use the value once the editor has been activated:
67475  * <ul>
67476  * <li>{@link #revertInvalid}</li>
67477  * <li>{@link #ignoreNoChange}</li>
67478  * <li>{@link #updateEl}</li>
67479  * </ul>
67480  * Sample usage:
67481  * </p>
67482  * <pre><code>
67483 var editor = new Ext.Editor({
67484     updateEl: true, // update the innerHTML of the bound element when editing completes
67485     field: {
67486         xtype: 'textfield'
67487     }
67488 });
67489 var el = Ext.get('my-text'); // The element to 'edit'
67490 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
67491  * </code></pre>
67492  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
67493  *
67494  */
67495 Ext.define('Ext.Editor', {
67496
67497     /* Begin Definitions */
67498
67499     extend: 'Ext.Component',
67500
67501     alias: 'widget.editor',
67502
67503     requires: ['Ext.layout.component.Editor'],
67504
67505     /* End Definitions */
67506
67507    componentLayout: 'editor',
67508
67509     /**
67510     * @cfg {Ext.form.field.Field} field
67511     * The Field object (or descendant) or config object for field
67512     */
67513
67514     /**
67515      * @cfg {Boolean} allowBlur
67516      * True to {@link #completeEdit complete the editing process} if in edit mode when the
67517      * field is blurred.
67518      */
67519     allowBlur: true,
67520
67521     /**
67522      * @cfg {Boolean/Object} autoSize
67523      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
67524      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
67525      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
67526      * the editor object.
67527      * Examples:
67528      * <pre><code>
67529 autoSize: true // The editor will be sized to the height/width of the field
67530
67531 height: 21,
67532 autoSize: {
67533     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
67534 }
67535
67536 autoSize: {
67537     width: 'field', // Width from the field
67538     height: 'boundEl' // Height from the boundEl
67539 }
67540      * </pre></code>
67541      */
67542
67543     /**
67544      * @cfg {Boolean} revertInvalid
67545      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
67546      * validation fails
67547      */
67548     revertInvalid: true,
67549
67550     /**
67551      * @cfg {Boolean} [ignoreNoChange=false]
67552      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
67553      * the value has not changed.  Applies only to string values - edits for other data types
67554      * will never be ignored.
67555      */
67556
67557     /**
67558      * @cfg {Boolean} [hideEl=true]
67559      * False to keep the bound element visible while the editor is displayed
67560      */
67561
67562     /**
67563      * @cfg {Object} value
67564      * The data value of the underlying field
67565      */
67566     value : '',
67567
67568     /**
67569      * @cfg {String} alignment
67570      * The position to align to (see {@link Ext.Element#alignTo} for more details).
67571      */
67572     alignment: 'c-c?',
67573
67574     /**
67575      * @cfg {Number[]} offsets
67576      * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
67577      */
67578     offsets: [0, 0],
67579
67580     /**
67581      * @cfg {Boolean/String} shadow
67582      * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
67583      */
67584     shadow : 'frame',
67585
67586     /**
67587      * @cfg {Boolean} constrain
67588      * True to constrain the editor to the viewport
67589      */
67590     constrain : false,
67591
67592     /**
67593      * @cfg {Boolean} swallowKeys
67594      * Handle the keydown/keypress events so they don't propagate
67595      */
67596     swallowKeys : true,
67597
67598     /**
67599      * @cfg {Boolean} completeOnEnter
67600      * True to complete the edit when the enter key is pressed.
67601      */
67602     completeOnEnter : true,
67603
67604     /**
67605      * @cfg {Boolean} cancelOnEsc
67606      * True to cancel the edit when the escape key is pressed.
67607      */
67608     cancelOnEsc : true,
67609
67610     /**
67611      * @cfg {Boolean} updateEl
67612      * True to update the innerHTML of the bound element when the update completes
67613      */
67614     updateEl : false,
67615
67616     /**
67617      * @cfg {String/HTMLElement/Ext.Element} parentEl
67618      * An element to render to. Defaults to the <tt>document.body</tt>.
67619      */
67620
67621     // private overrides
67622     hidden: true,
67623     baseCls: Ext.baseCSSPrefix + 'editor',
67624
67625     initComponent : function() {
67626         var me = this,
67627             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
67628
67629         Ext.apply(field, {
67630             inEditor: true,
67631             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
67632         });
67633         me.mon(field, {
67634             scope: me,
67635             blur: {
67636                 fn: me.onBlur,
67637                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
67638                 delay: 1
67639             },
67640             specialkey: me.onSpecialKey
67641         });
67642
67643         if (field.grow) {
67644             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
67645         }
67646         me.floating = {
67647             constrain: me.constrain
67648         };
67649
67650         me.callParent(arguments);
67651
67652         me.addEvents(
67653             /**
67654              * @event beforestartedit
67655              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
67656              * false from the handler of this event.
67657              * @param {Ext.Editor} this
67658              * @param {Ext.Element} boundEl The underlying element bound to this editor
67659              * @param {Object} value The field value being set
67660              */
67661             'beforestartedit',
67662
67663             /**
67664              * @event startedit
67665              * Fires when this editor is displayed
67666              * @param {Ext.Editor} this
67667              * @param {Ext.Element} boundEl The underlying element bound to this editor
67668              * @param {Object} value The starting field value
67669              */
67670             'startedit',
67671
67672             /**
67673              * @event beforecomplete
67674              * Fires after a change has been made to the field, but before the change is reflected in the underlying
67675              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
67676              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
67677              * event will not fire since no edit actually occurred.
67678              * @param {Ext.Editor} this
67679              * @param {Object} value The current field value
67680              * @param {Object} startValue The original field value
67681              */
67682             'beforecomplete',
67683             /**
67684              * @event complete
67685              * Fires after editing is complete and any changed value has been written to the underlying field.
67686              * @param {Ext.Editor} this
67687              * @param {Object} value The current field value
67688              * @param {Object} startValue The original field value
67689              */
67690             'complete',
67691             /**
67692              * @event canceledit
67693              * Fires after editing has been canceled and the editor's value has been reset.
67694              * @param {Ext.Editor} this
67695              * @param {Object} value The user-entered field value that was discarded
67696              * @param {Object} startValue The original field value that was set back into the editor after cancel
67697              */
67698             'canceledit',
67699             /**
67700              * @event specialkey
67701              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
67702              * {@link Ext.EventObject#getKey} to determine which key was pressed.
67703              * @param {Ext.Editor} this
67704              * @param {Ext.form.field.Field} The field attached to this editor
67705              * @param {Ext.EventObject} event The event object
67706              */
67707             'specialkey'
67708         );
67709     },
67710
67711     // private
67712     onAutoSize: function(){
67713         this.doComponentLayout();
67714     },
67715
67716     // private
67717     onRender : function(ct, position) {
67718         var me = this,
67719             field = me.field,
67720             inputEl = field.inputEl;
67721
67722         me.callParent(arguments);
67723
67724         field.render(me.el);
67725         //field.hide();
67726         // Ensure the field doesn't get submitted as part of any form
67727         if (inputEl) {
67728             inputEl.dom.name = '';
67729             if (me.swallowKeys) {
67730                 inputEl.swallowEvent([
67731                     'keypress', // *** Opera
67732                     'keydown'   // *** all other browsers
67733                 ]);
67734             }
67735         }
67736     },
67737
67738     // private
67739     onSpecialKey : function(field, event) {
67740         var me = this,
67741             key = event.getKey(),
67742             complete = me.completeOnEnter && key == event.ENTER,
67743             cancel = me.cancelOnEsc && key == event.ESC;
67744
67745         if (complete || cancel) {
67746             event.stopEvent();
67747             // Must defer this slightly to prevent exiting edit mode before the field's own
67748             // key nav can handle the enter key, e.g. selecting an item in a combobox list
67749             Ext.defer(function() {
67750                 if (complete) {
67751                     me.completeEdit();
67752                 } else {
67753                     me.cancelEdit();
67754                 }
67755                 if (field.triggerBlur) {
67756                     field.triggerBlur();
67757                 }
67758             }, 10);
67759         }
67760
67761         this.fireEvent('specialkey', this, field, event);
67762     },
67763
67764     /**
67765      * Starts the editing process and shows the editor.
67766      * @param {String/HTMLElement/Ext.Element} el The element to edit
67767      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
67768       * to the innerHTML of el.
67769      */
67770     startEdit : function(el, value) {
67771         var me = this,
67772             field = me.field;
67773
67774         me.completeEdit();
67775         me.boundEl = Ext.get(el);
67776         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
67777
67778         if (!me.rendered) {
67779             me.render(me.parentEl || document.body);
67780         }
67781
67782         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
67783             me.startValue = value;
67784             me.show();
67785             field.reset();
67786             field.setValue(value);
67787             me.realign(true);
67788             field.focus(false, 10);
67789             if (field.autoSize) {
67790                 field.autoSize();
67791             }
67792             me.editing = true;
67793         }
67794     },
67795
67796     /**
67797      * Realigns the editor to the bound field based on the current alignment config value.
67798      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
67799      */
67800     realign : function(autoSize) {
67801         var me = this;
67802         if (autoSize === true) {
67803             me.doComponentLayout();
67804         }
67805         me.alignTo(me.boundEl, me.alignment, me.offsets);
67806     },
67807
67808     /**
67809      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
67810      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
67811      */
67812     completeEdit : function(remainVisible) {
67813         var me = this,
67814             field = me.field,
67815             value;
67816
67817         if (!me.editing) {
67818             return;
67819         }
67820
67821         // Assert combo values first
67822         if (field.assertValue) {
67823             field.assertValue();
67824         }
67825
67826         value = me.getValue();
67827         if (!field.isValid()) {
67828             if (me.revertInvalid !== false) {
67829                 me.cancelEdit(remainVisible);
67830             }
67831             return;
67832         }
67833
67834         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
67835             me.hideEdit(remainVisible);
67836             return;
67837         }
67838
67839         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
67840             // Grab the value again, may have changed in beforecomplete
67841             value = me.getValue();
67842             if (me.updateEl && me.boundEl) {
67843                 me.boundEl.update(value);
67844             }
67845             me.hideEdit(remainVisible);
67846             me.fireEvent('complete', me, value, me.startValue);
67847         }
67848     },
67849
67850     // private
67851     onShow : function() {
67852         var me = this;
67853
67854         me.callParent(arguments);
67855         if (me.hideEl !== false) {
67856             me.boundEl.hide();
67857         }
67858         me.fireEvent("startedit", me.boundEl, me.startValue);
67859     },
67860
67861     /**
67862      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
67863      * reverted to the original starting value.
67864      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
67865      */
67866     cancelEdit : function(remainVisible) {
67867         var me = this,
67868             startValue = me.startValue,
67869             value;
67870
67871         if (me.editing) {
67872             value = me.getValue();
67873             me.setValue(startValue);
67874             me.hideEdit(remainVisible);
67875             me.fireEvent('canceledit', me, value, startValue);
67876         }
67877     },
67878
67879     // private
67880     hideEdit: function(remainVisible) {
67881         if (remainVisible !== true) {
67882             this.editing = false;
67883             this.hide();
67884         }
67885     },
67886
67887     // private
67888     onBlur : function() {
67889         var me = this;
67890
67891         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
67892         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
67893             me.completeEdit();
67894         }
67895     },
67896
67897     // private
67898     onHide : function() {
67899         var me = this,
67900             field = me.field;
67901
67902         if (me.editing) {
67903             me.completeEdit();
67904             return;
67905         }
67906         field.blur();
67907         if (field.collapse) {
67908             field.collapse();
67909         }
67910
67911         //field.hide();
67912         if (me.hideEl !== false) {
67913             me.boundEl.show();
67914         }
67915         me.callParent(arguments);
67916     },
67917
67918     /**
67919      * Sets the data value of the editor
67920      * @param {Object} value Any valid value supported by the underlying field
67921      */
67922     setValue : function(value) {
67923         this.field.setValue(value);
67924     },
67925
67926     /**
67927      * Gets the data value of the editor
67928      * @return {Object} The data value
67929      */
67930     getValue : function() {
67931         return this.field.getValue();
67932     },
67933
67934     beforeDestroy : function() {
67935         var me = this;
67936
67937         Ext.destroy(me.field);
67938         delete me.field;
67939         delete me.parentEl;
67940         delete me.boundEl;
67941
67942         me.callParent(arguments);
67943     }
67944 });
67945 /**
67946  * @class Ext.Img
67947  * @extends Ext.Component
67948  *
67949  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
67950  * with the configured src.
67951  *
67952  * {@img Ext.Img/Ext.Img.png Ext.Img component}
67953  *
67954  * ## Example usage: 
67955  *
67956  *     var changingImage = Ext.create('Ext.Img', {
67957  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
67958  *         renderTo: Ext.getBody()
67959  *     });
67960  *      
67961  *     // change the src of the image programmatically
67962  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
67963 */
67964 Ext.define('Ext.Img', {
67965     extend: 'Ext.Component',
67966     alias: ['widget.image', 'widget.imagecomponent'],
67967     /** @cfg {String} src The image src */
67968     src: '',
67969
67970     getElConfig: function() {
67971         return {
67972             tag: 'img',
67973             src: this.src
67974         };
67975     },
67976     
67977     // null out this function, we can't set any html inside the image
67978     initRenderTpl: Ext.emptyFn,
67979     
67980     /**
67981      * Updates the {@link #src} of the image
67982      */
67983     setSrc: function(src) {
67984         var me = this,
67985             img = me.el;
67986         me.src = src;
67987         if (img) {
67988             img.dom.src = src;
67989         }
67990     }
67991 });
67992
67993 /**
67994  * @class Ext.Layer
67995  * @extends Ext.Element
67996  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
67997  * automatic maintaining of shadow/shim positions.
67998  *
67999  * @cfg {Boolean} [shim=true]
68000  * False to disable the iframe shim in browsers which need one.
68001  *
68002  * @cfg {String/Boolean} [shadow=false]
68003  * True to automatically create an {@link Ext.Shadow}, or a string indicating the
68004  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
68005  *
68006  * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
68007  * DomHelper object config to create element with.
68008  *
68009  * @cfg {Boolean} [constrain=true]
68010  * False to disable constrain to viewport.
68011  *
68012  * @cfg {String} cls
68013  * CSS class to add to the element
68014  *
68015  * @cfg {Number} [zindex=11000]
68016  * Starting z-index.
68017  *
68018  * @cfg {Number} [shadowOffset=4]
68019  * Number of pixels to offset the shadow
68020  *
68021  * @cfg {Boolean} [useDisplay=false]
68022  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
68023  * to use css style <tt>'display:none;'</tt> to hide the Layer.
68024  *
68025  * @cfg {String} visibilityCls
68026  * The CSS class name to add in order to hide this Layer if this layer
68027  * is configured with <code>{@link #hideMode}: 'asclass'</code>
68028  *
68029  * @cfg {String} hideMode
68030  * A String which specifies how this Layer will be hidden.
68031  * Values may be<div class="mdetail-params"><ul>
68032  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
68033  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
68034  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
68035  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
68036  * in a Component having zero dimensions.</li></ul></div>
68037  */
68038 Ext.define('Ext.Layer', {
68039     uses: ['Ext.Shadow'],
68040
68041     // shims are shared among layer to keep from having 100 iframes
68042     statics: {
68043         shims: []
68044     },
68045
68046     extend: 'Ext.Element',
68047
68048     /**
68049      * Creates new Layer.
68050      * @param {Object} config (optional) An object with config options.
68051      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
68052      * If the element is not found it creates it.
68053      */
68054     constructor: function(config, existingEl) {
68055         config = config || {};
68056         var me = this,
68057             dh = Ext.DomHelper,
68058             cp = config.parentEl,
68059             pel = cp ? Ext.getDom(cp) : document.body,
68060         hm = config.hideMode;
68061
68062         if (existingEl) {
68063             me.dom = Ext.getDom(existingEl);
68064         }
68065         if (!me.dom) {
68066             me.dom = dh.append(pel, config.dh || {
68067                 tag: 'div',
68068                 cls: Ext.baseCSSPrefix + 'layer'
68069             });
68070         } else {
68071             me.addCls(Ext.baseCSSPrefix + 'layer');
68072             if (!me.dom.parentNode) {
68073                 pel.appendChild(me.dom);
68074             }
68075         }
68076
68077         if (config.cls) {
68078             me.addCls(config.cls);
68079         }
68080         me.constrain = config.constrain !== false;
68081
68082         // Allow Components to pass their hide mode down to the Layer if they are floating.
68083         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
68084         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
68085         if (hm) {
68086             me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
68087             if (me.visibilityMode == Ext.Element.ASCLASS) {
68088                 me.visibilityCls = config.visibilityCls;
68089             }
68090         } else if (config.useDisplay) {
68091             me.setVisibilityMode(Ext.Element.DISPLAY);
68092         } else {
68093             me.setVisibilityMode(Ext.Element.VISIBILITY);
68094         }
68095
68096         if (config.id) {
68097             me.id = me.dom.id = config.id;
68098         } else {
68099             me.id = Ext.id(me.dom);
68100         }
68101         me.position('absolute');
68102         if (config.shadow) {
68103             me.shadowOffset = config.shadowOffset || 4;
68104             me.shadow = Ext.create('Ext.Shadow', {
68105                 offset: me.shadowOffset,
68106                 mode: config.shadow
68107             });
68108             me.disableShadow();
68109         } else {
68110             me.shadowOffset = 0;
68111         }
68112         me.useShim = config.shim !== false && Ext.useShims;
68113         if (config.hidden === true) {
68114             me.hide();
68115         } else {
68116             me.show();
68117         }
68118     },
68119
68120     getZIndex: function() {
68121         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
68122     },
68123
68124     getShim: function() {
68125         var me = this,
68126             shim, pn;
68127
68128         if (!me.useShim) {
68129             return null;
68130         }
68131         if (!me.shim) {
68132             shim = me.self.shims.shift();
68133             if (!shim) {
68134                 shim = me.createShim();
68135                 shim.enableDisplayMode('block');
68136                 shim.hide();
68137             }
68138             pn = me.dom.parentNode;
68139             if (shim.dom.parentNode != pn) {
68140                 pn.insertBefore(shim.dom, me.dom);
68141             }
68142             me.shim = shim;
68143         }
68144         return me.shim;
68145     },
68146
68147     hideShim: function() {
68148         var me = this;
68149         
68150         if (me.shim) {
68151             me.shim.setDisplayed(false);
68152             me.self.shims.push(me.shim);
68153             delete me.shim;
68154         }
68155     },
68156
68157     disableShadow: function() {
68158         var me = this;
68159         
68160         if (me.shadow && !me.shadowDisabled) {
68161             me.shadowDisabled = true;
68162             me.shadow.hide();
68163             me.lastShadowOffset = me.shadowOffset;
68164             me.shadowOffset = 0;
68165         }
68166     },
68167
68168     enableShadow: function(show) {
68169         var me = this;
68170         
68171         if (me.shadow && me.shadowDisabled) {
68172             me.shadowDisabled = false;
68173             me.shadowOffset = me.lastShadowOffset;
68174             delete me.lastShadowOffset;
68175             if (show) {
68176                 me.sync(true);
68177             }
68178         }
68179     },
68180
68181     /**
68182      * @private
68183      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
68184      * <p>This code can execute repeatedly in milliseconds,
68185      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
68186      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
68187      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
68188      */
68189     sync: function(doShow) {
68190         var me = this,
68191             shadow = me.shadow,
68192             shadowPos, shimStyle, shadowSize;
68193
68194         if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
68195             var shim = me.getShim(),
68196                 l = me.getLeft(true),
68197                 t = me.getTop(true),
68198                 w = me.dom.offsetWidth,
68199                 h = me.dom.offsetHeight,
68200                 shimIndex;
68201
68202             if (shadow && !me.shadowDisabled) {
68203                 if (doShow && !shadow.isVisible()) {
68204                     shadow.show(me);
68205                 } else {
68206                     shadow.realign(l, t, w, h);
68207                 }
68208                 if (shim) {
68209                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
68210                     shimIndex = shim.getStyle('z-index');
68211                     if (shimIndex > me.zindex) {
68212                         me.shim.setStyle('z-index', me.zindex - 2);
68213                     }
68214                     shim.show();
68215                     // fit the shim behind the shadow, so it is shimmed too
68216                     if (shadow.isVisible()) {
68217                         shadowPos = shadow.el.getXY();
68218                         shimStyle = shim.dom.style;
68219                         shadowSize = shadow.el.getSize();
68220                         if (Ext.supports.CSS3BoxShadow) {
68221                             shadowSize.height += 6;
68222                             shadowSize.width += 4;
68223                             shadowPos[0] -= 2;
68224                             shadowPos[1] -= 4;
68225                         }
68226                         shimStyle.left = (shadowPos[0]) + 'px';
68227                         shimStyle.top = (shadowPos[1]) + 'px';
68228                         shimStyle.width = (shadowSize.width) + 'px';
68229                         shimStyle.height = (shadowSize.height) + 'px';
68230                     } else {
68231                         shim.setSize(w, h);
68232                         shim.setLeftTop(l, t);
68233                     }
68234                 }
68235             } else if (shim) {
68236                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
68237                 shimIndex = shim.getStyle('z-index');
68238                 if (shimIndex > me.zindex) {
68239                     me.shim.setStyle('z-index', me.zindex - 2);
68240                 }
68241                 shim.show();
68242                 shim.setSize(w, h);
68243                 shim.setLeftTop(l, t);
68244             }
68245         }
68246         return me;
68247     },
68248
68249     remove: function() {
68250         this.hideUnders();
68251         this.callParent();
68252     },
68253
68254     // private
68255     beginUpdate: function() {
68256         this.updating = true;
68257     },
68258
68259     // private
68260     endUpdate: function() {
68261         this.updating = false;
68262         this.sync(true);
68263     },
68264
68265     // private
68266     hideUnders: function() {
68267         if (this.shadow) {
68268             this.shadow.hide();
68269         }
68270         this.hideShim();
68271     },
68272
68273     // private
68274     constrainXY: function() {
68275         if (this.constrain) {
68276             var vw = Ext.Element.getViewWidth(),
68277                 vh = Ext.Element.getViewHeight(),
68278                 s = Ext.getDoc().getScroll(),
68279                 xy = this.getXY(),
68280                 x = xy[0],
68281                 y = xy[1],
68282                 so = this.shadowOffset,
68283                 w = this.dom.offsetWidth + so,
68284                 h = this.dom.offsetHeight + so,
68285                 moved = false; // only move it if it needs it
68286             // first validate right/bottom
68287             if ((x + w) > vw + s.left) {
68288                 x = vw - w - so;
68289                 moved = true;
68290             }
68291             if ((y + h) > vh + s.top) {
68292                 y = vh - h - so;
68293                 moved = true;
68294             }
68295             // then make sure top/left isn't negative
68296             if (x < s.left) {
68297                 x = s.left;
68298                 moved = true;
68299             }
68300             if (y < s.top) {
68301                 y = s.top;
68302                 moved = true;
68303             }
68304             if (moved) {
68305                 Ext.Layer.superclass.setXY.call(this, [x, y]);
68306                 this.sync();
68307             }
68308         }
68309         return this;
68310     },
68311
68312     getConstrainOffset: function() {
68313         return this.shadowOffset;
68314     },
68315
68316     // overridden Element method
68317     setVisible: function(visible, animate, duration, callback, easing) {
68318         var me = this,
68319             cb;
68320
68321         // post operation processing
68322         cb = function() {
68323             if (visible) {
68324                 me.sync(true);
68325             }
68326             if (callback) {
68327                 callback();
68328             }
68329         };
68330
68331         // Hide shadow and shim if hiding
68332         if (!visible) {
68333             me.hideUnders(true);
68334         }
68335         me.callParent([visible, animate, duration, callback, easing]);
68336         if (!animate) {
68337             cb();
68338         }
68339         return me;
68340     },
68341
68342     // private
68343     beforeFx: function() {
68344         this.beforeAction();
68345         return this.callParent(arguments);
68346     },
68347
68348     // private
68349     afterFx: function() {
68350         this.callParent(arguments);
68351         this.sync(this.isVisible());
68352     },
68353
68354     // private
68355     beforeAction: function() {
68356         if (!this.updating && this.shadow) {
68357             this.shadow.hide();
68358         }
68359     },
68360
68361     // overridden Element method
68362     setLeft: function(left) {
68363         this.callParent(arguments);
68364         return this.sync();
68365     },
68366
68367     setTop: function(top) {
68368         this.callParent(arguments);
68369         return this.sync();
68370     },
68371
68372     setLeftTop: function(left, top) {
68373         this.callParent(arguments);
68374         return this.sync();
68375     },
68376
68377     setXY: function(xy, animate, duration, callback, easing) {
68378         var me = this;
68379         
68380         // Callback will restore shadow state and call the passed callback
68381         callback = me.createCB(callback);
68382
68383         me.fixDisplay();
68384         me.beforeAction();
68385         me.callParent([xy, animate, duration, callback, easing]);
68386         if (!animate) {
68387             callback();
68388         }
68389         return me;
68390     },
68391
68392     // private
68393     createCB: function(callback) {
68394         var me = this,
68395             showShadow = me.shadow && me.shadow.isVisible();
68396
68397         return function() {
68398             me.constrainXY();
68399             me.sync(showShadow);
68400             if (callback) {
68401                 callback();
68402             }
68403         };
68404     },
68405
68406     // overridden Element method
68407     setX: function(x, animate, duration, callback, easing) {
68408         this.setXY([x, this.getY()], animate, duration, callback, easing);
68409         return this;
68410     },
68411
68412     // overridden Element method
68413     setY: function(y, animate, duration, callback, easing) {
68414         this.setXY([this.getX(), y], animate, duration, callback, easing);
68415         return this;
68416     },
68417
68418     // overridden Element method
68419     setSize: function(w, h, animate, duration, callback, easing) {
68420         var me = this;
68421         
68422         // Callback will restore shadow state and call the passed callback
68423         callback = me.createCB(callback);
68424
68425         me.beforeAction();
68426         me.callParent([w, h, animate, duration, callback, easing]);
68427         if (!animate) {
68428             callback();
68429         }
68430         return me;
68431     },
68432
68433     // overridden Element method
68434     setWidth: function(w, animate, duration, callback, easing) {
68435         var me = this;
68436         
68437         // Callback will restore shadow state and call the passed callback
68438         callback = me.createCB(callback);
68439
68440         me.beforeAction();
68441         me.callParent([w, animate, duration, callback, easing]);
68442         if (!animate) {
68443             callback();
68444         }
68445         return me;
68446     },
68447
68448     // overridden Element method
68449     setHeight: function(h, animate, duration, callback, easing) {
68450         var me = this;
68451         
68452         // Callback will restore shadow state and call the passed callback
68453         callback = me.createCB(callback);
68454
68455         me.beforeAction();
68456         me.callParent([h, animate, duration, callback, easing]);
68457         if (!animate) {
68458             callback();
68459         }
68460         return me;
68461     },
68462
68463     // overridden Element method
68464     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
68465         var me = this;
68466         
68467         // Callback will restore shadow state and call the passed callback
68468         callback = me.createCB(callback);
68469
68470         me.beforeAction();
68471         if (!animate) {
68472             Ext.Layer.superclass.setXY.call(me, [x, y]);
68473             Ext.Layer.superclass.setSize.call(me, width, height);
68474             callback();
68475         } else {
68476             me.callParent([x, y, width, height, animate, duration, callback, easing]);
68477         }
68478         return me;
68479     },
68480
68481     /**
68482      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
68483      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
68484      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
68485      * element will receive the highest  z-index.
68486      * @param {Number} zindex The new z-index to set
68487      * @return {Ext.Layer} The Layer
68488      */
68489     setZIndex: function(zindex) {
68490         var me = this;
68491         
68492         me.zindex = zindex;
68493         if (me.getShim()) {
68494             me.shim.setStyle('z-index', zindex++);
68495         }
68496         if (me.shadow) {
68497             me.shadow.setZIndex(zindex++);
68498         }
68499         return me.setStyle('z-index', zindex);
68500     },
68501     
68502     setOpacity: function(opacity){
68503         if (this.shadow) {
68504             this.shadow.setOpacity(opacity);
68505         }
68506         return this.callParent(arguments);
68507     }
68508 });
68509
68510 /**
68511  * @class Ext.layout.component.ProgressBar
68512  * @extends Ext.layout.component.Component
68513  * @private
68514  */
68515
68516 Ext.define('Ext.layout.component.ProgressBar', {
68517
68518     /* Begin Definitions */
68519
68520     alias: ['layout.progressbar'],
68521
68522     extend: 'Ext.layout.component.Component',
68523
68524     /* End Definitions */
68525
68526     type: 'progressbar',
68527
68528     onLayout: function(width, height) {
68529         var me = this,
68530             owner = me.owner,
68531             textEl = owner.textEl;
68532         
68533         me.setElementSize(owner.el, width, height);
68534         textEl.setWidth(owner.el.getWidth(true));
68535         
68536         me.callParent([width, height]);
68537         
68538         owner.updateProgress(owner.value);
68539     }
68540 });
68541 /**
68542  * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
68543  *
68544  * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
68545  * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
68546  * that has predictable points of interest at which you can update the control.
68547  *
68548  * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
68549  * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
68550  * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
68551  * for indicating intermediate progress.
68552  *
68553  *     @example
68554  *     var p = Ext.create('Ext.ProgressBar', {
68555  *        renderTo: Ext.getBody(),
68556  *        width: 300
68557  *     });
68558  *
68559  *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
68560  *     p.wait({
68561  *         interval: 500, //bar will move fast!
68562  *         duration: 50000,
68563  *         increment: 15,
68564  *         text: 'Updating...',
68565  *         scope: this,
68566  *         fn: function(){
68567  *             p.updateText('Done!');
68568  *         }
68569  *     });
68570  */
68571 Ext.define('Ext.ProgressBar', {
68572     extend: 'Ext.Component',
68573     alias: 'widget.progressbar',
68574
68575     requires: [
68576         'Ext.Template',
68577         'Ext.CompositeElement',
68578         'Ext.TaskManager',
68579         'Ext.layout.component.ProgressBar'
68580     ],
68581
68582     uses: ['Ext.fx.Anim'],
68583
68584    /**
68585     * @cfg {Number} [value=0]
68586     * A floating point value between 0 and 1 (e.g., .5)
68587     */
68588
68589    /**
68590     * @cfg {String} [text='']
68591     * The progress bar text (defaults to '')
68592     */
68593
68594    /**
68595     * @cfg {String/HTMLElement/Ext.Element} textEl
68596     * The element to render the progress text to (defaults to the progress bar's internal text element)
68597     */
68598
68599    /**
68600     * @cfg {String} id
68601     * The progress bar element's id (defaults to an auto-generated id)
68602     */
68603
68604    /**
68605     * @cfg {String} [baseCls='x-progress']
68606     * The base CSS class to apply to the progress bar's wrapper element.
68607     */
68608     baseCls: Ext.baseCSSPrefix + 'progress',
68609
68610     config: {
68611         /**
68612         * @cfg {Boolean} animate
68613         * True to animate the progress bar during transitions
68614         */
68615         animate: false,
68616
68617         /**
68618          * @cfg {String} text
68619          * The text shown in the progress bar
68620          */
68621         text: ''
68622     },
68623
68624     // private
68625     waitTimer: null,
68626
68627     renderTpl: [
68628         '<div class="{baseCls}-text {baseCls}-text-back">',
68629             '<div>&#160;</div>',
68630         '</div>',
68631         '<div id="{id}-bar" class="{baseCls}-bar">',
68632             '<div class="{baseCls}-text">',
68633                 '<div>&#160;</div>',
68634             '</div>',
68635         '</div>'
68636     ],
68637
68638     componentLayout: 'progressbar',
68639
68640     // private
68641     initComponent: function() {
68642         this.callParent();
68643
68644         this.addChildEls('bar');
68645
68646         this.addEvents(
68647             /**
68648              * @event update
68649              * Fires after each update interval
68650              * @param {Ext.ProgressBar} this
68651              * @param {Number} value The current progress value
68652              * @param {String} text The current progress text
68653              */
68654             "update"
68655         );
68656     },
68657
68658     afterRender : function() {
68659         var me = this;
68660
68661         // This produces a composite w/2 el's (which is why we cannot use childEls or
68662         // renderSelectors):
68663         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
68664
68665         me.callParent(arguments);
68666
68667         if (me.value) {
68668             me.updateProgress(me.value, me.text);
68669         }
68670         else {
68671             me.updateText(me.text);
68672         }
68673     },
68674
68675     /**
68676      * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
68677      * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
68678      * it will never automatically reset -- you are responsible for determining when the progress is complete and
68679      * calling {@link #reset} to clear and/or hide the control.
68680      * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
68681      * @param {String} [text=''] The string to display in the progress text element
68682      * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
68683      * specified, the default for the class is used
68684      * @return {Ext.ProgressBar} this
68685      */
68686     updateProgress: function(value, text, animate) {
68687         var me = this,
68688             newWidth;
68689             
68690         me.value = value || 0;
68691         if (text) {
68692             me.updateText(text);
68693         }
68694         if (me.rendered && !me.isDestroyed) {
68695             if (me.isVisible(true)) {
68696                 newWidth = Math.floor(me.value * me.el.getWidth(true));
68697                 if (Ext.isForcedBorderBox) {
68698                     newWidth += me.bar.getBorderWidth("lr");
68699                 }
68700                 if (animate === true || (animate !== false && me.animate)) {
68701                     me.bar.stopAnimation();
68702                     me.bar.animate(Ext.apply({
68703                         to: {
68704                             width: newWidth + 'px'
68705                         }
68706                     }, me.animate));
68707                 } else {
68708                     me.bar.setWidth(newWidth);
68709                 }
68710             } else {
68711                 // force a layout when we're visible again
68712                 me.doComponentLayout();
68713             }
68714         }
68715         me.fireEvent('update', me, me.value, text);
68716         return me;
68717     },
68718
68719     /**
68720      * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
68721      * display the updated text.
68722      * @param {String} [text=''] The string to display in the progress text element
68723      * @return {Ext.ProgressBar} this
68724      */
68725     updateText: function(text) {
68726         var me = this;
68727         
68728         me.text = text;
68729         if (me.rendered) {
68730             me.textEl.update(me.text);
68731         }
68732         return me;
68733     },
68734
68735     applyText : function(text) {
68736         this.updateText(text);
68737     },
68738
68739     /**
68740      * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
68741      * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
68742      * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
68743      * {@link #reset}.
68744      *
68745      * Example usage:
68746      *
68747      *     var p = new Ext.ProgressBar({
68748      *        renderTo: 'my-el'
68749      *     });
68750      *
68751      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
68752      *     var p = Ext.create('Ext.ProgressBar', {
68753      *        renderTo: Ext.getBody(),
68754      *        width: 300
68755      *     });
68756      *
68757      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
68758      *     p.wait({
68759      *        interval: 500, //bar will move fast!
68760      *        duration: 50000,
68761      *        increment: 15,
68762      *        text: 'Updating...',
68763      *        scope: this,
68764      *        fn: function(){
68765      *           p.updateText('Done!');
68766      *        }
68767      *     });
68768      *
68769      *     //Or update indefinitely until some async action completes, then reset manually
68770      *     p.wait();
68771      *     myAction.on('complete', function(){
68772      *         p.reset();
68773      *         p.updateText('Done!');
68774      *     });
68775      *
68776      * @param {Object} config (optional) Configuration options
68777      * @param {Number} config.duration The length of time in milliseconds that the progress bar should
68778      * run before resetting itself (defaults to undefined, in which case it will run indefinitely
68779      * until reset is called)
68780      * @param {Number} config.interval The length of time in milliseconds between each progress update
68781      * (defaults to 1000 ms)
68782      * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
68783      * value is not specified, the default for the class is used.
68784      * @param {Number} config.increment The number of progress update segments to display within the
68785      * progress bar (defaults to 10).  If the bar reaches the end and is still updating, it will
68786      * automatically wrap back to the beginning.
68787      * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
68788      * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
68789      * updating.  The function will be called with no arguments.  This function will be ignored if
68790      * duration is not specified since in that case the progress bar can only be stopped programmatically,
68791      * so any required function should be called by the same code after it resets the progress bar.
68792      * @param {Object} config.scope The scope that is passed to the callback function (only applies when
68793      * duration and fn are both passed).
68794      * @return {Ext.ProgressBar} this
68795      */
68796     wait: function(o) {
68797         var me = this;
68798             
68799         if (!me.waitTimer) {
68800             scope = me;
68801             o = o || {};
68802             me.updateText(o.text);
68803             me.waitTimer = Ext.TaskManager.start({
68804                 run: function(i){
68805                     var inc = o.increment || 10;
68806                     i -= 1;
68807                     me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
68808                 },
68809                 interval: o.interval || 1000,
68810                 duration: o.duration,
68811                 onStop: function(){
68812                     if (o.fn) {
68813                         o.fn.apply(o.scope || me);
68814                     }
68815                     me.reset();
68816                 },
68817                 scope: scope
68818             });
68819         }
68820         return me;
68821     },
68822
68823     /**
68824      * Returns true if the progress bar is currently in a {@link #wait} operation
68825      * @return {Boolean} True if waiting, else false
68826      */
68827     isWaiting: function(){
68828         return this.waitTimer !== null;
68829     },
68830
68831     /**
68832      * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
68833      * (using the {@link #hideMode} property internally).
68834      * @param {Boolean} [hide=false] True to hide the progress bar.
68835      * @return {Ext.ProgressBar} this
68836      */
68837     reset: function(hide){
68838         var me = this;
68839         
68840         me.updateProgress(0);
68841         me.clearTimer();
68842         if (hide === true) {
68843             me.hide();
68844         }
68845         return me;
68846     },
68847
68848     // private
68849     clearTimer: function(){
68850         var me = this;
68851         
68852         if (me.waitTimer) {
68853             me.waitTimer.onStop = null; //prevent recursion
68854             Ext.TaskManager.stop(me.waitTimer);
68855             me.waitTimer = null;
68856         }
68857     },
68858
68859     onDestroy: function(){
68860         var me = this;
68861         
68862         me.clearTimer();
68863         if (me.rendered) {
68864             if (me.textEl.isComposite) {
68865                 me.textEl.clear();
68866             }
68867             Ext.destroyMembers(me, 'textEl', 'progressBar');
68868         }
68869         me.callParent();
68870     }
68871 });
68872
68873 /**
68874  * Private utility class that manages the internal Shadow cache
68875  * @private
68876  */
68877 Ext.define('Ext.ShadowPool', {
68878     singleton: true,
68879     requires: ['Ext.DomHelper'],
68880
68881     markup: function() {
68882         if (Ext.supports.CSS3BoxShadow) {
68883             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
68884         } else if (Ext.isIE) {
68885             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
68886         } else {
68887             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
68888                 '<div class="xst" role="presentation">' +
68889                     '<div class="xstl" role="presentation"></div>' +
68890                     '<div class="xstc" role="presentation"></div>' +
68891                     '<div class="xstr" role="presentation"></div>' +
68892                 '</div>' +
68893                 '<div class="xsc" role="presentation">' +
68894                     '<div class="xsml" role="presentation"></div>' +
68895                     '<div class="xsmc" role="presentation"></div>' +
68896                     '<div class="xsmr" role="presentation"></div>' +
68897                 '</div>' +
68898                 '<div class="xsb" role="presentation">' +
68899                     '<div class="xsbl" role="presentation"></div>' +
68900                     '<div class="xsbc" role="presentation"></div>' +
68901                     '<div class="xsbr" role="presentation"></div>' +
68902                 '</div>' +
68903             '</div>';
68904         }
68905     }(),
68906
68907     shadows: [],
68908
68909     pull: function() {
68910         var sh = this.shadows.shift();
68911         if (!sh) {
68912             sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
68913             sh.autoBoxAdjust = false;
68914         }
68915         return sh;
68916     },
68917
68918     push: function(sh) {
68919         this.shadows.push(sh);
68920     },
68921     
68922     reset: function() {
68923         Ext.Array.each(this.shadows, function(shadow) {
68924             shadow.remove();
68925         });
68926         this.shadows = [];
68927     }
68928 });
68929 /**
68930  * @class Ext.Shadow
68931  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
68932  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
68933  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
68934  */
68935 Ext.define('Ext.Shadow', {
68936     requires: ['Ext.ShadowPool'],
68937
68938     /**
68939      * Creates new Shadow.
68940      * @param {Object} config (optional) Config object.
68941      */
68942     constructor: function(config) {
68943         var me = this,
68944             adjusts = {
68945                 h: 0
68946             },
68947             offset,
68948             rad;
68949         
68950         Ext.apply(me, config);
68951         if (!Ext.isString(me.mode)) {
68952             me.mode = me.defaultMode;
68953         }
68954         offset = me.offset;
68955         rad = Math.floor(offset / 2);
68956         me.opacity = 50;
68957         switch (me.mode.toLowerCase()) {
68958             // all this hideous nonsense calculates the various offsets for shadows
68959             case "drop":
68960                 if (Ext.supports.CSS3BoxShadow) {
68961                     adjusts.w = adjusts.h = -offset;
68962                     adjusts.l = adjusts.t = offset;
68963                 } else {
68964                     adjusts.w = 0;
68965                     adjusts.l = adjusts.t = offset;
68966                     adjusts.t -= 1;
68967                     if (Ext.isIE) {
68968                         adjusts.l -= offset + rad;
68969                         adjusts.t -= offset + rad;
68970                         adjusts.w -= rad;
68971                         adjusts.h -= rad;
68972                         adjusts.t += 1;
68973                     }
68974                 }
68975                 break;
68976             case "sides":
68977                 if (Ext.supports.CSS3BoxShadow) {
68978                     adjusts.h -= offset;
68979                     adjusts.t = offset;
68980                     adjusts.l = adjusts.w = 0;
68981                 } else {
68982                     adjusts.w = (offset * 2);
68983                     adjusts.l = -offset;
68984                     adjusts.t = offset - 1;
68985                     if (Ext.isIE) {
68986                         adjusts.l -= (offset - rad);
68987                         adjusts.t -= offset + rad;
68988                         adjusts.l += 1;
68989                         adjusts.w -= (offset - rad) * 2;
68990                         adjusts.w -= rad + 1;
68991                         adjusts.h -= 1;
68992                     }
68993                 }
68994                 break;
68995             case "frame":
68996                 if (Ext.supports.CSS3BoxShadow) {
68997                     adjusts.l = adjusts.w = adjusts.t = 0;
68998                 } else {
68999                     adjusts.w = adjusts.h = (offset * 2);
69000                     adjusts.l = adjusts.t = -offset;
69001                     adjusts.t += 1;
69002                     adjusts.h -= 2;
69003                     if (Ext.isIE) {
69004                         adjusts.l -= (offset - rad);
69005                         adjusts.t -= (offset - rad);
69006                         adjusts.l += 1;
69007                         adjusts.w -= (offset + rad + 1);
69008                         adjusts.h -= (offset + rad);
69009                         adjusts.h += 1;
69010                     }
69011                     break;
69012                 }
69013         }
69014         me.adjusts = adjusts;
69015     },
69016
69017     /**
69018      * @cfg {String} mode
69019      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
69020      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
69021      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
69022      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
69023      * </ul></div>
69024      */
69025     /**
69026      * @cfg {Number} offset
69027      * The number of pixels to offset the shadow from the element
69028      */
69029     offset: 4,
69030
69031     // private
69032     defaultMode: "drop",
69033
69034     /**
69035      * Displays the shadow under the target element
69036      * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
69037      */
69038     show: function(target) {
69039         var me = this,
69040             index;
69041         
69042         target = Ext.get(target);
69043         if (!me.el) {
69044             me.el = Ext.ShadowPool.pull();
69045             if (me.el.dom.nextSibling != target.dom) {
69046                 me.el.insertBefore(target);
69047             }
69048         }
69049         index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
69050         me.el.setStyle("z-index", me.zIndex || index);
69051         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
69052             me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
69053         }
69054         me.realign(
69055             target.getLeft(true),
69056             target.getTop(true),
69057             target.dom.offsetWidth,
69058             target.dom.offsetHeight
69059         );
69060         me.el.dom.style.display = "block";
69061     },
69062
69063     /**
69064      * Returns true if the shadow is visible, else false
69065      */
69066     isVisible: function() {
69067         return this.el ? true: false;
69068     },
69069
69070     /**
69071      * Direct alignment when values are already available. Show must be called at least once before
69072      * calling this method to ensure it is initialized.
69073      * @param {Number} left The target element left position
69074      * @param {Number} top The target element top position
69075      * @param {Number} width The target element width
69076      * @param {Number} height The target element height
69077      */
69078     realign: function(l, t, targetWidth, targetHeight) {
69079         if (!this.el) {
69080             return;
69081         }
69082         var adjusts = this.adjusts,
69083             d = this.el.dom,
69084             targetStyle = d.style,
69085             shadowWidth,
69086             shadowHeight,
69087             cn,
69088             sww, 
69089             sws, 
69090             shs;
69091
69092         targetStyle.left = (l + adjusts.l) + "px";
69093         targetStyle.top = (t + adjusts.t) + "px";
69094         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
69095         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
69096         sws = shadowWidth + "px";
69097         shs = shadowHeight + "px";
69098         if (targetStyle.width != sws || targetStyle.height != shs) {
69099             targetStyle.width = sws;
69100             targetStyle.height = shs;
69101             if (Ext.supports.CSS3BoxShadow) {
69102                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
69103             } else {
69104
69105                 // Adjust the 9 point framed element to poke out on the required sides
69106                 if (!Ext.isIE) {
69107                     cn = d.childNodes;
69108                     sww = Math.max(0, (shadowWidth - 12)) + "px";
69109                     cn[0].childNodes[1].style.width = sww;
69110                     cn[1].childNodes[1].style.width = sww;
69111                     cn[2].childNodes[1].style.width = sww;
69112                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
69113                 }
69114             }
69115         }
69116     },
69117
69118     /**
69119      * Hides this shadow
69120      */
69121     hide: function() {
69122         var me = this;
69123         
69124         if (me.el) {
69125             me.el.dom.style.display = "none";
69126             Ext.ShadowPool.push(me.el);
69127             delete me.el;
69128         }
69129     },
69130
69131     /**
69132      * Adjust the z-index of this shadow
69133      * @param {Number} zindex The new z-index
69134      */
69135     setZIndex: function(z) {
69136         this.zIndex = z;
69137         if (this.el) {
69138             this.el.setStyle("z-index", z);
69139         }
69140     },
69141     
69142     /**
69143      * Sets the opacity of the shadow
69144      * @param {Number} opacity The opacity
69145      */
69146     setOpacity: function(opacity){
69147         if (this.el) {
69148             if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
69149                 opacity = Math.floor(opacity * 100 / 2) / 100;
69150             }
69151             this.opacity = opacity;
69152             this.el.setOpacity(opacity);
69153         }
69154     }
69155 });
69156 /**
69157  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
69158  * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
69159  * primary button action, but any custom handler can provide the arrowclick implementation.  Example usage:
69160  *
69161  *     @example
69162  *     // display a dropdown menu:
69163  *     Ext.create('Ext.button.Split', {
69164  *         renderTo: Ext.getBody(),
69165  *         text: 'Options',
69166  *         // handle a click on the button itself
69167  *         handler: function() {
69168  *             alert("The button was clicked");
69169  *         },
69170  *         menu: new Ext.menu.Menu({
69171  *             items: [
69172  *                 // these will render as dropdown menu items when the arrow is clicked:
69173  *                 {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
69174  *                 {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
69175  *             ]
69176  *         })
69177  *     });
69178  *
69179  * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
69180  * arrow is clicked:
69181  *
69182  *     Ext.create('Ext.button.Split', {
69183  *         renderTo: 'button-ct',
69184  *         text: 'Options',
69185  *         handler: optionsHandler,
69186  *         arrowHandler: myCustomHandler
69187  *     });
69188  *
69189  */
69190 Ext.define('Ext.button.Split', {
69191
69192     /* Begin Definitions */
69193     alias: 'widget.splitbutton',
69194
69195     extend: 'Ext.button.Button',
69196     alternateClassName: 'Ext.SplitButton',
69197     /* End Definitions */
69198     
69199     /**
69200      * @cfg {Function} arrowHandler
69201      * A function called when the arrow button is clicked (can be used instead of click event)
69202      */
69203     /**
69204      * @cfg {String} arrowTooltip
69205      * The title attribute of the arrow
69206      */
69207
69208     // private
69209     arrowCls      : 'split',
69210     split         : true,
69211
69212     // private
69213     initComponent : function(){
69214         this.callParent();
69215         /**
69216          * @event arrowclick
69217          * Fires when this button's arrow is clicked.
69218          * @param {Ext.button.Split} this
69219          * @param {Event} e The click event
69220          */
69221         this.addEvents("arrowclick");
69222     },
69223
69224     /**
69225      * Sets this button's arrow click handler.
69226      * @param {Function} handler The function to call when the arrow is clicked
69227      * @param {Object} scope (optional) Scope for the function passed above
69228      */
69229     setArrowHandler : function(handler, scope){
69230         this.arrowHandler = handler;
69231         this.scope = scope;
69232     },
69233
69234     // private
69235     onClick : function(e, t) {
69236         var me = this;
69237         
69238         e.preventDefault();
69239         if (!me.disabled) {
69240             if (me.overMenuTrigger) {
69241                 me.maybeShowMenu();
69242                 me.fireEvent("arrowclick", me, e);
69243                 if (me.arrowHandler) {
69244                     me.arrowHandler.call(me.scope || me, me, e);
69245                 }
69246             } else {
69247                 me.doToggle();
69248                 me.fireHandler();
69249             }
69250         }
69251     }
69252 });
69253 /**
69254  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
69255  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
69256  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
69257  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
69258  *
69259  *     @example
69260  *     Ext.create('Ext.button.Cycle', {
69261  *         showText: true,
69262  *         prependText: 'View as ',
69263  *         renderTo: Ext.getBody(),
69264  *         menu: {
69265  *             id: 'view-type-menu',
69266  *             items: [{
69267  *                 text: 'text only',
69268  *                 iconCls: 'view-text',
69269  *                 checked: true
69270  *             },{
69271  *                 text: 'HTML',
69272  *                 iconCls: 'view-html'
69273  *             }]
69274  *         },
69275  *         changeHandler: function(cycleBtn, activeItem) {
69276  *             Ext.Msg.alert('Change View', activeItem.text);
69277  *         }
69278  *     });
69279  */
69280 Ext.define('Ext.button.Cycle', {
69281
69282     /* Begin Definitions */
69283
69284     alias: 'widget.cycle',
69285
69286     extend: 'Ext.button.Split',
69287     alternateClassName: 'Ext.CycleButton',
69288
69289     /* End Definitions */
69290
69291     /**
69292      * @cfg {Object[]} items
69293      * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
69294      * `{text:'Foo', iconCls:'foo-icon'}`)
69295      * 
69296      * @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
69297      * {@link Ext.menu.CheckItem CheckItems}.
69298      */
69299     /**
69300      * @cfg {Boolean} [showText=false]
69301      * True to display the active item's text as the button text. The Button will show its
69302      * configured {@link #text} if this config is omitted.
69303      */
69304     /**
69305      * @cfg {String} [prependText='']
69306      * A static string to prepend before the active item's text when displayed as the button's text (only applies when
69307      * showText = true).
69308      */
69309     /**
69310      * @cfg {Function} changeHandler
69311      * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
69312      * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
69313      * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
69314      * item)
69315      */
69316     /**
69317      * @cfg {String} forceIcon
69318      * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
69319      * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
69320      * button's icon to match the selected item's icon on change.
69321      */
69322     /**
69323      * @property {Ext.menu.Menu} menu
69324      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
69325      * available choices.
69326      */
69327
69328     // private
69329     getButtonText: function(item) {
69330         var me = this,
69331             text = '';
69332
69333         if (item && me.showText === true) {
69334             if (me.prependText) {
69335                 text += me.prependText;
69336             }
69337             text += item.text;
69338             return text;
69339         }
69340         return me.text;
69341     },
69342
69343     /**
69344      * Sets the button's active menu item.
69345      * @param {Ext.menu.CheckItem} item The item to activate
69346      * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
69347      */
69348     setActiveItem: function(item, suppressEvent) {
69349         var me = this;
69350
69351         if (!Ext.isObject(item)) {
69352             item = me.menu.getComponent(item);
69353         }
69354         if (item) {
69355             if (!me.rendered) {
69356                 me.text = me.getButtonText(item);
69357                 me.iconCls = item.iconCls;
69358             } else {
69359                 me.setText(me.getButtonText(item));
69360                 me.setIconCls(item.iconCls);
69361             }
69362             me.activeItem = item;
69363             if (!item.checked) {
69364                 item.setChecked(true, false);
69365             }
69366             if (me.forceIcon) {
69367                 me.setIconCls(me.forceIcon);
69368             }
69369             if (!suppressEvent) {
69370                 me.fireEvent('change', me, item);
69371             }
69372         }
69373     },
69374
69375     /**
69376      * Gets the currently active menu item.
69377      * @return {Ext.menu.CheckItem} The active item
69378      */
69379     getActiveItem: function() {
69380         return this.activeItem;
69381     },
69382
69383     // private
69384     initComponent: function() {
69385         var me = this,
69386             checked = 0,
69387             items;
69388
69389         me.addEvents(
69390             /**
69391              * @event change
69392              * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
69393              * set on this CycleButton, it will be called instead on active item change and this change event will not
69394              * be fired.
69395              * @param {Ext.button.Cycle} this
69396              * @param {Ext.menu.CheckItem} item The menu item that was selected
69397              */
69398             "change"
69399         );
69400
69401         if (me.changeHandler) {
69402             me.on('change', me.changeHandler, me.scope || me);
69403             delete me.changeHandler;
69404         }
69405
69406         // Allow them to specify a menu config which is a standard Button config.
69407         // Remove direct use of "items" in 5.0.
69408         items = (me.menu.items||[]).concat(me.items||[]);
69409         me.menu = Ext.applyIf({
69410             cls: Ext.baseCSSPrefix + 'cycle-menu',
69411             items: []
69412         }, me.menu);
69413
69414         // Convert all items to CheckItems
69415         Ext.each(items, function(item, i) {
69416             item = Ext.applyIf({
69417                 group: me.id,
69418                 itemIndex: i,
69419                 checkHandler: me.checkHandler,
69420                 scope: me,
69421                 checked: item.checked || false
69422             }, item);
69423             me.menu.items.push(item);
69424             if (item.checked) {
69425                 checked = i;
69426             }
69427         });
69428         me.itemCount = me.menu.items.length;
69429         me.callParent(arguments);
69430         me.on('click', me.toggleSelected, me);
69431         me.setActiveItem(checked, me);
69432
69433         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
69434         if (me.width && me.showText) {
69435             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
69436         }
69437     },
69438
69439     // private
69440     checkHandler: function(item, pressed) {
69441         if (pressed) {
69442             this.setActiveItem(item);
69443         }
69444     },
69445
69446     /**
69447      * This is normally called internally on button click, but can be called externally to advance the button's active
69448      * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
69449      * item will be set to the first item in the menu.
69450      */
69451     toggleSelected: function() {
69452         var me = this,
69453             m = me.menu,
69454             checkItem;
69455
69456         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
69457         checkItem.setChecked(true);
69458     }
69459 });
69460 /**
69461  * Provides a container for arranging a group of related Buttons in a tabular manner.
69462  *
69463  *     @example
69464  *     Ext.create('Ext.panel.Panel', {
69465  *         title: 'Panel with ButtonGroup',
69466  *         width: 300,
69467  *         height:200,
69468  *         renderTo: document.body,
69469  *         bodyPadding: 10,
69470  *         html: 'HTML Panel Content',
69471  *         tbar: [{
69472  *             xtype: 'buttongroup',
69473  *             columns: 3,
69474  *             title: 'Clipboard',
69475  *             items: [{
69476  *                 text: 'Paste',
69477  *                 scale: 'large',
69478  *                 rowspan: 3,
69479  *                 iconCls: 'add',
69480  *                 iconAlign: 'top',
69481  *                 cls: 'btn-as-arrow'
69482  *             },{
69483  *                 xtype:'splitbutton',
69484  *                 text: 'Menu Button',
69485  *                 scale: 'large',
69486  *                 rowspan: 3,
69487  *                 iconCls: 'add',
69488  *                 iconAlign: 'top',
69489  *                 arrowAlign:'bottom',
69490  *                 menu: [{ text: 'Menu Item 1' }]
69491  *             },{
69492  *                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
69493  *             },{
69494  *                 text: 'Copy', iconCls: 'add16'
69495  *             },{
69496  *                 text: 'Format', iconCls: 'add16'
69497  *             }]
69498  *         }]
69499  *     });
69500  *
69501  */
69502 Ext.define('Ext.container.ButtonGroup', {
69503     extend: 'Ext.panel.Panel',
69504     alias: 'widget.buttongroup',
69505     alternateClassName: 'Ext.ButtonGroup',
69506
69507     /**
69508      * @cfg {Number} columns The `columns` configuration property passed to the
69509      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
69510      */
69511
69512     /**
69513      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
69514      */
69515     baseCls: Ext.baseCSSPrefix + 'btn-group',
69516
69517     /**
69518      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
69519      */
69520     layout: {
69521         type: 'table'
69522     },
69523
69524     defaultType: 'button',
69525
69526     /**
69527      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
69528      */
69529     frame: true,
69530
69531     frameHeader: false,
69532
69533     internalDefaults: {removeMode: 'container', hideParent: true},
69534
69535     initComponent : function(){
69536         // Copy the component's columns config to the layout if specified
69537         var me = this,
69538             cols = me.columns;
69539
69540         me.noTitleCls = me.baseCls + '-notitle';
69541         if (cols) {
69542             me.layout = Ext.apply({}, {columns: cols}, me.layout);
69543         }
69544
69545         if (!me.title) {
69546             me.addCls(me.noTitleCls);
69547         }
69548         me.callParent(arguments);
69549     },
69550
69551     afterLayout: function() {
69552         var me = this;
69553
69554         me.callParent(arguments);
69555
69556         // Pugly hack for a pugly browser:
69557         // If not an explicitly set width, then size the width to match the inner table
69558         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
69559             var t = me.getTargetEl();
69560             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
69561         }
69562
69563         // IE7 needs a forced repaint to make the top framing div expand to full width
69564         if (Ext.isIE7) {
69565             me.el.repaint();
69566         }
69567     },
69568
69569     afterRender: function() {
69570         var me = this;
69571
69572         //we need to add an addition item in here so the ButtonGroup title is centered
69573         if (me.header) {
69574             // Header text cannot flex, but must be natural size if it's being centered
69575             delete me.header.items.items[0].flex;
69576
69577             // For Centering, surround the text with two flex:1 spacers.
69578             me.suspendLayout = true;
69579             me.header.insert(1, {
69580                 xtype: 'component',
69581                 ui   : me.ui,
69582                 flex : 1
69583             });
69584             me.header.insert(0, {
69585                 xtype: 'component',
69586                 ui   : me.ui,
69587                 flex : 1
69588             });
69589             me.suspendLayout = false;
69590         }
69591
69592         me.callParent(arguments);
69593     },
69594
69595     // private
69596     onBeforeAdd: function(component) {
69597         if (component.is('button')) {
69598             component.ui = component.ui + '-toolbar';
69599         }
69600         this.callParent(arguments);
69601     },
69602
69603     //private
69604     applyDefaults: function(c) {
69605         if (!Ext.isString(c)) {
69606             c = this.callParent(arguments);
69607             var d = this.internalDefaults;
69608             if (c.events) {
69609                 Ext.applyIf(c.initialConfig, d);
69610                 Ext.apply(c, d);
69611             } else {
69612                 Ext.applyIf(c, d);
69613             }
69614         }
69615         return c;
69616     }
69617
69618     /**
69619      * @cfg {Array} tools  @hide
69620      */
69621     /**
69622      * @cfg {Boolean} collapsible  @hide
69623      */
69624     /**
69625      * @cfg {Boolean} collapseMode  @hide
69626      */
69627     /**
69628      * @cfg {Boolean} animCollapse  @hide
69629      */
69630     /**
69631      * @cfg {Boolean} closable  @hide
69632      */
69633 });
69634
69635 /**
69636  * A specialized container representing the viewable application area (the browser viewport).
69637  *
69638  * The Viewport renders itself to the document body, and automatically sizes itself to the size of
69639  * the browser viewport and manages window resizing. There may only be one Viewport created
69640  * in a page.
69641  *
69642  * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
69643  * on its child Components if you configure it with a {@link #layout}.
69644  *
69645  * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
69646  * required layout is simpler, a different layout should be chosen.
69647  *
69648  * For example, to simply make a single child item occupy all available space, use
69649  * {@link Ext.layout.container.Fit fit layout}.
69650  *
69651  * To display one "active" item at full size from a choice of several child items, use
69652  * {@link Ext.layout.container.Card card layout}.
69653  *
69654  * Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
69655  * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
69656  * method of any of its child Panels may themselves have a layout.
69657  *
69658  * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
69659  * for scrolling if needed using the {@link #autoScroll} config.
69660  *
69661  * An example showing a classic application border layout:
69662  *
69663  *     @example
69664  *     Ext.create('Ext.container.Viewport', {
69665  *         layout: 'border',
69666  *         items: [{
69667  *             region: 'north',
69668  *             html: '<h1 class="x-panel-header">Page Title</h1>',
69669  *             autoHeight: true,
69670  *             border: false,
69671  *             margins: '0 0 5 0'
69672  *         }, {
69673  *             region: 'west',
69674  *             collapsible: true,
69675  *             title: 'Navigation',
69676  *             width: 150
69677  *             // could use a TreePanel or AccordionLayout for navigational items
69678  *         }, {
69679  *             region: 'south',
69680  *             title: 'South Panel',
69681  *             collapsible: true,
69682  *             html: 'Information goes here',
69683  *             split: true,
69684  *             height: 100,
69685  *             minHeight: 100
69686  *         }, {
69687  *             region: 'east',
69688  *             title: 'East Panel',
69689  *             collapsible: true,
69690  *             split: true,
69691  *             width: 150
69692  *         }, {
69693  *             region: 'center',
69694  *             xtype: 'tabpanel', // TabPanel itself has no title
69695  *             activeTab: 0,      // First tab active by default
69696  *             items: {
69697  *                 title: 'Default Tab',
69698  *                 html: 'The first tab\'s content. Others may be added dynamically'
69699  *             }
69700  *         }]
69701  *     });
69702  */
69703 Ext.define('Ext.container.Viewport', {
69704     extend: 'Ext.container.Container',
69705     alias: 'widget.viewport',
69706     requires: ['Ext.EventManager'],
69707     alternateClassName: 'Ext.Viewport',
69708
69709     // Privatize config options which, if used, would interfere with the
69710     // correct operation of the Viewport as the sole manager of the
69711     // layout of the document body.
69712
69713     /**
69714      * @cfg {String/HTMLElement/Ext.Element} applyTo
69715      * Not applicable.
69716      */
69717
69718     /**
69719      * @cfg {Boolean} allowDomMove
69720      * Not applicable.
69721      */
69722
69723     /**
69724      * @cfg {Boolean} hideParent
69725      * Not applicable.
69726      */
69727
69728     /**
69729      * @cfg {String/HTMLElement/Ext.Element} renderTo
69730      * Not applicable. Always renders to document body.
69731      */
69732
69733     /**
69734      * @cfg {Boolean} hideParent
69735      * Not applicable.
69736      */
69737
69738     /**
69739      * @cfg {Number} height
69740      * Not applicable. Sets itself to viewport width.
69741      */
69742
69743     /**
69744      * @cfg {Number} width
69745      * Not applicable. Sets itself to viewport height.
69746      */
69747
69748     /**
69749      * @cfg {Boolean} autoHeight
69750      * Not applicable.
69751      */
69752
69753     /**
69754      * @cfg {Boolean} autoWidth
69755      * Not applicable.
69756      */
69757
69758     /**
69759      * @cfg {Boolean} deferHeight
69760      * Not applicable.
69761      */
69762
69763     /**
69764      * @cfg {Boolean} monitorResize
69765      * Not applicable.
69766      */
69767
69768     isViewport: true,
69769
69770     ariaRole: 'application',
69771
69772     initComponent : function() {
69773         var me = this,
69774             html = Ext.fly(document.body.parentNode),
69775             el;
69776         me.callParent(arguments);
69777         html.addCls(Ext.baseCSSPrefix + 'viewport');
69778         if (me.autoScroll) {
69779             html.setStyle('overflow', 'auto');
69780         }
69781         me.el = el = Ext.getBody();
69782         el.setHeight = Ext.emptyFn;
69783         el.setWidth = Ext.emptyFn;
69784         el.setSize = Ext.emptyFn;
69785         el.dom.scroll = 'no';
69786         me.allowDomMove = false;
69787         Ext.EventManager.onWindowResize(me.fireResize, me);
69788         me.renderTo = me.el;
69789         me.width = Ext.Element.getViewportWidth();
69790         me.height = Ext.Element.getViewportHeight();
69791     },
69792
69793     fireResize : function(w, h){
69794         // setSize is the single entry point to layouts
69795         this.setSize(w, h);
69796     }
69797 });
69798
69799 /*
69800  * This is a derivative of the similarly named class in the YUI Library.
69801  * The original license:
69802  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
69803  * Code licensed under the BSD License:
69804  * http://developer.yahoo.net/yui/license.txt
69805  */
69806
69807
69808 /**
69809  * @class Ext.dd.DDTarget
69810  * @extends Ext.dd.DragDrop
69811  * A DragDrop implementation that does not move, but can be a drop
69812  * target.  You would get the same result by simply omitting implementation
69813  * for the event callbacks, but this way we reduce the processing cost of the
69814  * event listener and the callbacks.
69815  */
69816 Ext.define('Ext.dd.DDTarget', {
69817     extend: 'Ext.dd.DragDrop',
69818
69819     /**
69820      * Creates new DDTarget.
69821      * @param {String} id the id of the element that is a drop target
69822      * @param {String} sGroup the group of related DragDrop objects
69823      * @param {Object} config an object containing configurable attributes.
69824      * Valid properties for DDTarget in addition to those in DragDrop: none.
69825      */
69826     constructor: function(id, sGroup, config) {
69827         if (id) {
69828             this.initTarget(id, sGroup, config);
69829         }
69830     },
69831
69832     /**
69833      * @hide
69834      * Overridden and disabled. A DDTarget does not support being dragged.
69835      * @method
69836      */
69837     getDragEl: Ext.emptyFn,
69838     /**
69839      * @hide
69840      * Overridden and disabled. A DDTarget does not support being dragged.
69841      * @method
69842      */
69843     isValidHandleChild: Ext.emptyFn,
69844     /**
69845      * @hide
69846      * Overridden and disabled. A DDTarget does not support being dragged.
69847      * @method
69848      */
69849     startDrag: Ext.emptyFn,
69850     /**
69851      * @hide
69852      * Overridden and disabled. A DDTarget does not support being dragged.
69853      * @method
69854      */
69855     endDrag: Ext.emptyFn,
69856     /**
69857      * @hide
69858      * Overridden and disabled. A DDTarget does not support being dragged.
69859      * @method
69860      */
69861     onDrag: Ext.emptyFn,
69862     /**
69863      * @hide
69864      * Overridden and disabled. A DDTarget does not support being dragged.
69865      * @method
69866      */
69867     onDragDrop: Ext.emptyFn,
69868     /**
69869      * @hide
69870      * Overridden and disabled. A DDTarget does not support being dragged.
69871      * @method
69872      */
69873     onDragEnter: Ext.emptyFn,
69874     /**
69875      * @hide
69876      * Overridden and disabled. A DDTarget does not support being dragged.
69877      * @method
69878      */
69879     onDragOut: Ext.emptyFn,
69880     /**
69881      * @hide
69882      * Overridden and disabled. A DDTarget does not support being dragged.
69883      * @method
69884      */
69885     onDragOver: Ext.emptyFn,
69886     /**
69887      * @hide
69888      * Overridden and disabled. A DDTarget does not support being dragged.
69889      * @method
69890      */
69891     onInvalidDrop: Ext.emptyFn,
69892     /**
69893      * @hide
69894      * Overridden and disabled. A DDTarget does not support being dragged.
69895      * @method
69896      */
69897     onMouseDown: Ext.emptyFn,
69898     /**
69899      * @hide
69900      * Overridden and disabled. A DDTarget does not support being dragged.
69901      * @method
69902      */
69903     onMouseUp: Ext.emptyFn,
69904     /**
69905      * @hide
69906      * Overridden and disabled. A DDTarget does not support being dragged.
69907      * @method
69908      */
69909     setXConstraint: Ext.emptyFn,
69910     /**
69911      * @hide
69912      * Overridden and disabled. A DDTarget does not support being dragged.
69913      * @method
69914      */
69915     setYConstraint: Ext.emptyFn,
69916     /**
69917      * @hide
69918      * Overridden and disabled. A DDTarget does not support being dragged.
69919      * @method
69920      */
69921     resetConstraints: Ext.emptyFn,
69922     /**
69923      * @hide
69924      * Overridden and disabled. A DDTarget does not support being dragged.
69925      * @method
69926      */
69927     clearConstraints: Ext.emptyFn,
69928     /**
69929      * @hide
69930      * Overridden and disabled. A DDTarget does not support being dragged.
69931      * @method
69932      */
69933     clearTicks: Ext.emptyFn,
69934     /**
69935      * @hide
69936      * Overridden and disabled. A DDTarget does not support being dragged.
69937      * @method
69938      */
69939     setInitPosition: Ext.emptyFn,
69940     /**
69941      * @hide
69942      * Overridden and disabled. A DDTarget does not support being dragged.
69943      * @method
69944      */
69945     setDragElId: Ext.emptyFn,
69946     /**
69947      * @hide
69948      * Overridden and disabled. A DDTarget does not support being dragged.
69949      * @method
69950      */
69951     setHandleElId: Ext.emptyFn,
69952     /**
69953      * @hide
69954      * Overridden and disabled. A DDTarget does not support being dragged.
69955      * @method
69956      */
69957     setOuterHandleElId: Ext.emptyFn,
69958     /**
69959      * @hide
69960      * Overridden and disabled. A DDTarget does not support being dragged.
69961      * @method
69962      */
69963     addInvalidHandleClass: Ext.emptyFn,
69964     /**
69965      * @hide
69966      * Overridden and disabled. A DDTarget does not support being dragged.
69967      * @method
69968      */
69969     addInvalidHandleId: Ext.emptyFn,
69970     /**
69971      * @hide
69972      * Overridden and disabled. A DDTarget does not support being dragged.
69973      * @method
69974      */
69975     addInvalidHandleType: Ext.emptyFn,
69976     /**
69977      * @hide
69978      * Overridden and disabled. A DDTarget does not support being dragged.
69979      * @method
69980      */
69981     removeInvalidHandleClass: Ext.emptyFn,
69982     /**
69983      * @hide
69984      * Overridden and disabled. A DDTarget does not support being dragged.
69985      * @method
69986      */
69987     removeInvalidHandleId: Ext.emptyFn,
69988     /**
69989      * @hide
69990      * Overridden and disabled. A DDTarget does not support being dragged.
69991      * @method
69992      */
69993     removeInvalidHandleType: Ext.emptyFn,
69994
69995     toString: function() {
69996         return ("DDTarget " + this.id);
69997     }
69998 });
69999 /**
70000  * @class Ext.dd.DragTracker
70001  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
70002  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
70003  * an element that can be dragged around to change the Slider's value.
70004  * DragTracker provides a series of template methods that should be overridden to provide functionality
70005  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
70006  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
70007  */
70008 Ext.define('Ext.dd.DragTracker', {
70009
70010     uses: ['Ext.util.Region'],
70011
70012     mixins: {
70013         observable: 'Ext.util.Observable'
70014     },
70015
70016     /**
70017      * @property {Boolean} active
70018      * Read-only property indicated whether the user is currently dragging this
70019      * tracker.
70020      */
70021     active: false,
70022
70023     /**
70024      * @property {HTMLElement} dragTarget
70025      * <p><b>Only valid during drag operations. Read-only.</b></p>
70026      * <p>The element being dragged.</p>
70027      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
70028      */
70029
70030     /**
70031      * @cfg {Boolean} trackOver
70032      * <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>
70033      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
70034      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
70035      */
70036     trackOver: false,
70037
70038     /**
70039      * @cfg {String} overCls
70040      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
70041      * when a delegate element) is mouseovered.</p>
70042      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
70043      */
70044
70045     /**
70046      * @cfg {Ext.util.Region/Ext.Element} constrainTo
70047      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
70048      * the result of the {@link #getOffset} call.</p>
70049      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
70050      */
70051
70052     /**
70053      * @cfg {Number} tolerance
70054      * Number of pixels the drag target must be moved before dragging is
70055      * considered to have started. Defaults to <code>5</code>.
70056      */
70057     tolerance: 5,
70058
70059     /**
70060      * @cfg {Boolean/Number} autoStart
70061      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
70062      * Specify a Number for the number of milliseconds to defer trigger start.
70063      */
70064     autoStart: false,
70065
70066     /**
70067      * @cfg {String} delegate
70068      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
70069      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
70070      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
70071      */
70072
70073     /**
70074      * @cfg {Boolean} preventDefault
70075      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
70076      */
70077
70078     /**
70079      * @cfg {Boolean} stopEvent
70080      * 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>.
70081      */
70082
70083     constructor : function(config){
70084         Ext.apply(this, config);
70085         this.addEvents(
70086             /**
70087              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
70088              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
70089              * used, when the mouse enters a delegate element).</p>
70090              * @param {Object} this
70091              * @param {Object} e event object
70092              * @param {HTMLElement} target The element mouseovered.
70093              */
70094             'mouseover',
70095
70096             /**
70097              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
70098              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
70099              * used, when the mouse exits a delegate element).</p>
70100              * @param {Object} this
70101              * @param {Object} e event object
70102              */
70103             'mouseout',
70104
70105             /**
70106              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
70107              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
70108              * the {@link #autoStart} timer fires.</p>
70109              * <p>Return false to veto the drag operation.</p>
70110              * @param {Object} this
70111              * @param {Object} e event object
70112              */
70113             'mousedown',
70114
70115             /**
70116              * @event mouseup
70117              * @param {Object} this
70118              * @param {Object} e event object
70119              */
70120             'mouseup',
70121
70122             /**
70123              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
70124              * @param {Object} this
70125              * @param {Object} e event object
70126              */
70127             'mousemove',
70128
70129             /**
70130              * @event beforestart
70131              * @param {Object} this
70132              * @param {Object} e event object
70133              */
70134             'beforedragstart',
70135
70136             /**
70137              * @event dragstart
70138              * @param {Object} this
70139              * @param {Object} e event object
70140              */
70141             'dragstart',
70142
70143             /**
70144              * @event dragend
70145              * @param {Object} this
70146              * @param {Object} e event object
70147              */
70148             'dragend',
70149
70150             /**
70151              * @event drag
70152              * @param {Object} this
70153              * @param {Object} e event object
70154              */
70155             'drag'
70156         );
70157
70158         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
70159
70160         if (this.el) {
70161             this.initEl(this.el);
70162         }
70163
70164         // Dont pass the config so that it is not applied to 'this' again
70165         this.mixins.observable.constructor.call(this);
70166         if (this.disabled) {
70167             this.disable();
70168         }
70169
70170     },
70171
70172     /**
70173      * Initializes the DragTracker on a given element.
70174      * @param {Ext.Element/HTMLElement} el The element
70175      */
70176     initEl: function(el) {
70177         this.el = Ext.get(el);
70178
70179         // The delegate option may also be an element on which to listen
70180         this.handle = Ext.get(this.delegate);
70181
70182         // If delegate specified an actual element to listen on, we do not use the delegate listener option
70183         this.delegate = this.handle ? undefined : this.delegate;
70184
70185         if (!this.handle) {
70186             this.handle = this.el;
70187         }
70188
70189         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
70190         // We process mousedown to begin tracking.
70191         this.mon(this.handle, {
70192             mousedown: this.onMouseDown,
70193             delegate: this.delegate,
70194             scope: this
70195         });
70196
70197         // If configured to do so, track mouse entry and exit into the target (or delegate).
70198         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
70199         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
70200         if (this.trackOver || this.overCls) {
70201             this.mon(this.handle, {
70202                 mouseover: this.onMouseOver,
70203                 mouseout: this.onMouseOut,
70204                 delegate: this.delegate,
70205                 scope: this
70206             });
70207         }
70208     },
70209
70210     disable: function() {
70211         this.disabled = true;
70212     },
70213
70214     enable: function() {
70215         this.disabled = false;
70216     },
70217
70218     destroy : function() {
70219         this.clearListeners();
70220         delete this.el;
70221     },
70222
70223     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
70224     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
70225     onMouseOver: function(e, target) {
70226         var me = this;
70227         if (!me.disabled) {
70228             if (Ext.EventManager.contains(e) || me.delegate) {
70229                 me.mouseIsOut = false;
70230                 if (me.overCls) {
70231                     me.el.addCls(me.overCls);
70232                 }
70233                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
70234             }
70235         }
70236     },
70237
70238     // When the pointer exits a tracking element, fire a mouseout.
70239     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
70240     onMouseOut: function(e) {
70241         if (this.mouseIsDown) {
70242             this.mouseIsOut = true;
70243         } else {
70244             if (this.overCls) {
70245                 this.el.removeCls(this.overCls);
70246             }
70247             this.fireEvent('mouseout', this, e);
70248         }
70249     },
70250
70251     onMouseDown: function(e, target){
70252         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
70253         if (this.disabled ||e.dragTracked) {
70254             return;
70255         }
70256
70257         // This information should be available in mousedown listener and onBeforeStart implementations
70258         this.dragTarget = this.delegate ? target : this.handle.dom;
70259         this.startXY = this.lastXY = e.getXY();
70260         this.startRegion = Ext.fly(this.dragTarget).getRegion();
70261
70262         if (this.fireEvent('mousedown', this, e) === false ||
70263             this.fireEvent('beforedragstart', this, e) === false ||
70264             this.onBeforeStart(e) === false) {
70265             return;
70266         }
70267
70268         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
70269         // The onMouseOut method will only ever be called after mouseup.
70270         this.mouseIsDown = true;
70271
70272         // Flag for downstream DragTracker instances that the mouse is being tracked.
70273         e.dragTracked = true;
70274
70275         if (this.preventDefault !== false) {
70276             e.preventDefault();
70277         }
70278         Ext.getDoc().on({
70279             scope: this,
70280             mouseup: this.onMouseUp,
70281             mousemove: this.onMouseMove,
70282             selectstart: this.stopSelect
70283         });
70284         if (this.autoStart) {
70285             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
70286         }
70287     },
70288
70289     onMouseMove: function(e, target){
70290         // BrowserBug: IE hack to see if button was released outside of window.
70291         // Needed in IE6-9 in quirks and strictmode
70292         if (this.active && Ext.isIE && !e.browserEvent.button) {
70293             e.preventDefault();
70294             this.onMouseUp(e);
70295             return;
70296         }
70297
70298         e.preventDefault();
70299         var xy = e.getXY(),
70300             s = this.startXY;
70301
70302         this.lastXY = xy;
70303         if (!this.active) {
70304             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
70305                 this.triggerStart(e);
70306             } else {
70307                 return;
70308             }
70309         }
70310
70311         // Returning false from a mousemove listener deactivates
70312         if (this.fireEvent('mousemove', this, e) === false) {
70313             this.onMouseUp(e);
70314         } else {
70315             this.onDrag(e);
70316             this.fireEvent('drag', this, e);
70317         }
70318     },
70319
70320     onMouseUp: function(e) {
70321         // Clear the flag which ensures onMouseOut fires only after the mouse button
70322         // is lifted if the mouseout happens *during* a drag.
70323         this.mouseIsDown = false;
70324
70325         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
70326         if (this.mouseIsOut) {
70327             this.mouseIsOut = false;
70328             this.onMouseOut(e);
70329         }
70330         e.preventDefault();
70331         this.fireEvent('mouseup', this, e);
70332         this.endDrag(e);
70333     },
70334
70335     /**
70336      * @private
70337      * Stop the drag operation, and remove active mouse listeners.
70338      */
70339     endDrag: function(e) {
70340         var doc = Ext.getDoc(),
70341         wasActive = this.active;
70342
70343         doc.un('mousemove', this.onMouseMove, this);
70344         doc.un('mouseup', this.onMouseUp, this);
70345         doc.un('selectstart', this.stopSelect, this);
70346         this.clearStart();
70347         this.active = false;
70348         if (wasActive) {
70349             this.onEnd(e);
70350             this.fireEvent('dragend', this, e);
70351         }
70352         // Private property calculated when first required and only cached during a drag
70353         delete this._constrainRegion;
70354
70355         // Remove flag from event singleton.  Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
70356         delete Ext.EventObject.dragTracked;
70357     },
70358
70359     triggerStart: function(e) {
70360         this.clearStart();
70361         this.active = true;
70362         this.onStart(e);
70363         this.fireEvent('dragstart', this, e);
70364     },
70365
70366     clearStart : function() {
70367         if (this.timer) {
70368             clearTimeout(this.timer);
70369             delete this.timer;
70370         }
70371     },
70372
70373     stopSelect : function(e) {
70374         e.stopEvent();
70375         return false;
70376     },
70377
70378     /**
70379      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
70380      * holds the mouse button down. Return false to disallow the drag
70381      * @param {Ext.EventObject} e The event object
70382      * @template
70383      */
70384     onBeforeStart : function(e) {
70385
70386     },
70387
70388     /**
70389      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
70390      * (e.g. the user has moved the tracked element beyond the specified tolerance)
70391      * @param {Ext.EventObject} e The event object
70392      * @template
70393      */
70394     onStart : function(xy) {
70395
70396     },
70397
70398     /**
70399      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
70400      * @param {Ext.EventObject} e The event object
70401      * @template
70402      */
70403     onDrag : function(e) {
70404
70405     },
70406
70407     /**
70408      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
70409      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
70410      * @param {Ext.EventObject} e The event object
70411      * @template
70412      */
70413     onEnd : function(e) {
70414
70415     },
70416
70417     /**
70418      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
70419      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
70420      * {@link #delegate} selector.</p>
70421      * @return {Ext.Element} The element currently being tracked.
70422      */
70423     getDragTarget : function(){
70424         return this.dragTarget;
70425     },
70426
70427     /**
70428      * @private
70429      * @returns {Ext.Element} The DragTracker's encapsulating element.
70430      */
70431     getDragCt : function(){
70432         return this.el;
70433     },
70434
70435     /**
70436      * @private
70437      * Return the Region into which the drag operation is constrained.
70438      * Either the XY pointer itself can be constrained, or the dragTarget element
70439      * The private property _constrainRegion is cached until onMouseUp
70440      */
70441     getConstrainRegion: function() {
70442         if (this.constrainTo) {
70443             if (this.constrainTo instanceof Ext.util.Region) {
70444                 return this.constrainTo;
70445             }
70446             if (!this._constrainRegion) {
70447                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
70448             }
70449         } else {
70450             if (!this._constrainRegion) {
70451                 this._constrainRegion = this.getDragCt().getViewRegion();
70452             }
70453         }
70454         return this._constrainRegion;
70455     },
70456
70457     getXY : function(constrain){
70458         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
70459     },
70460
70461     /**
70462      * Returns the X, Y offset of the current mouse position from the mousedown point.
70463      *
70464      * This method may optionally constrain the real offset values, and returns a point coerced in one
70465      * of two modes:
70466      *
70467      *  - `point`
70468      *    The current mouse position is coerced into the constrainRegion and the resulting position is returned.
70469      *  - `dragTarget`
70470      *    The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
70471      *    based upon the current mouse position, and then coerced into the constrainRegion. The returned
70472      *    mouse position is then adjusted by the same delta as was used to coerce the region.\
70473      *
70474      * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
70475      * as `point` or `dragTarget`. See above.
70476      * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
70477      */
70478     getOffset : function(constrain){
70479         var xy = this.getXY(constrain),
70480             s = this.startXY;
70481
70482         return [xy[0]-s[0], xy[1]-s[1]];
70483     },
70484
70485     constrainModes: {
70486         // Constrain the passed point to within the constrain region
70487         point: function(me, xy) {
70488             var dr = me.dragRegion,
70489                 constrainTo = me.getConstrainRegion();
70490
70491             // No constraint
70492             if (!constrainTo) {
70493                 return xy;
70494             }
70495
70496             dr.x = dr.left = dr[0] = dr.right = xy[0];
70497             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
70498             dr.constrainTo(constrainTo);
70499
70500             return [dr.left, dr.top];
70501         },
70502
70503         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
70504         dragTarget: function(me, xy) {
70505             var s = me.startXY,
70506                 dr = me.startRegion.copy(),
70507                 constrainTo = me.getConstrainRegion(),
70508                 adjust;
70509
70510             // No constraint
70511             if (!constrainTo) {
70512                 return xy;
70513             }
70514
70515             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
70516             // If it overflows, we constrain the passed XY to bring the potential
70517             // region back within the boundary.
70518             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
70519
70520             // Constrain the X coordinate by however much the dragTarget overflows
70521             if (dr.right > constrainTo.right) {
70522                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
70523                 dr.left += adjust;
70524             }
70525             if (dr.left < constrainTo.left) {
70526                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
70527             }
70528
70529             // Constrain the Y coordinate by however much the dragTarget overflows
70530             if (dr.bottom > constrainTo.bottom) {
70531                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
70532                 dr.top += adjust;
70533             }
70534             if (dr.top < constrainTo.top) {
70535                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
70536             }
70537             return xy;
70538         }
70539     }
70540 });
70541 /**
70542  * @class Ext.dd.DragZone
70543  * @extends Ext.dd.DragSource
70544  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
70545  * <p>This class does not move the drag target nodes, but a proxy element which may contain
70546  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
70547  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
70548  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
70549  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
70550  * is the most efficient way to "activate" those nodes.</p>
70551  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
70552  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
70553  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
70554  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
70555  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
70556  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
70557  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
70558 myDataView.on('render', function(v) {
70559     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
70560
70561 //      On receipt of a mousedown event, see if it is within a DataView node.
70562 //      Return a drag data object if so.
70563         getDragData: function(e) {
70564
70565 //          Use the DataView's own itemSelector (a mandatory property) to
70566 //          test if the mousedown is within one of the DataView's nodes.
70567             var sourceEl = e.getTarget(v.itemSelector, 10);
70568
70569 //          If the mousedown is within a DataView node, clone the node to produce
70570 //          a ddel element for use by the drag proxy. Also add application data
70571 //          to the returned data object.
70572             if (sourceEl) {
70573                 d = sourceEl.cloneNode(true);
70574                 d.id = Ext.id();
70575                 return {
70576                     ddel: d,
70577                     sourceEl: sourceEl,
70578                     repairXY: Ext.fly(sourceEl).getXY(),
70579                     sourceStore: v.store,
70580                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
70581                 }
70582             }
70583         },
70584
70585 //      Provide coordinates for the proxy to slide back to on failed drag.
70586 //      This is the original XY coordinates of the draggable element captured
70587 //      in the getDragData method.
70588         getRepairXY: function() {
70589             return this.dragData.repairXY;
70590         }
70591     });
70592 });</code></pre>
70593  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
70594  * cooperates with this DragZone.
70595  */
70596 Ext.define('Ext.dd.DragZone', {
70597
70598     extend: 'Ext.dd.DragSource',
70599
70600     /**
70601      * Creates new DragZone.
70602      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
70603      * @param {Object} config
70604      */
70605     constructor : function(el, config){
70606         this.callParent([el, config]);
70607         if (this.containerScroll) {
70608             Ext.dd.ScrollManager.register(this.el);
70609         }
70610     },
70611
70612     /**
70613      * This property contains the data representing the dragged object. This data is set up by the implementation
70614      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
70615      * any other data according to the application's needs.
70616      * @type Object
70617      * @property dragData
70618      */
70619
70620     /**
70621      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
70622      * for auto scrolling during drag operations.
70623      */
70624
70625     /**
70626      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
70627      * for a valid target to drag based on the mouse down. Override this method
70628      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
70629      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
70630      * @param {Event} e The mouse down event
70631      * @return {Object} The dragData
70632      */
70633     getDragData : function(e){
70634         return Ext.dd.Registry.getHandleFromEvent(e);
70635     },
70636
70637     /**
70638      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
70639      * this.dragData.ddel
70640      * @param {Number} x The x position of the click on the dragged object
70641      * @param {Number} y The y position of the click on the dragged object
70642      * @return {Boolean} true to continue the drag, false to cancel
70643      */
70644     onInitDrag : function(x, y){
70645         this.proxy.update(this.dragData.ddel.cloneNode(true));
70646         this.onStartDrag(x, y);
70647         return true;
70648     },
70649
70650     /**
70651      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
70652      */
70653     afterRepair : function(){
70654         var me = this;
70655         if (Ext.enableFx) {
70656             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
70657         }
70658         me.dragging = false;
70659     },
70660
70661     /**
70662      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
70663      * the XY of this.dragData.ddel
70664      * @param {Event} e The mouse up event
70665      * @return {Number[]} The xy location (e.g. [100, 200])
70666      */
70667     getRepairXY : function(e){
70668         return Ext.Element.fly(this.dragData.ddel).getXY();
70669     },
70670
70671     destroy : function(){
70672         this.callParent();
70673         if (this.containerScroll) {
70674             Ext.dd.ScrollManager.unregister(this.el);
70675         }
70676     }
70677 });
70678
70679 /**
70680  * @class Ext.dd.ScrollManager
70681  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
70682  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
70683  * but you can also override most of the configs per scroll container by adding a
70684  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
70685  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
70686  * <pre><code>
70687 var el = Ext.get('scroll-ct');
70688 el.ddScrollConfig = {
70689     vthresh: 50,
70690     hthresh: -1,
70691     frequency: 100,
70692     increment: 200
70693 };
70694 Ext.dd.ScrollManager.register(el);
70695 </code></pre>
70696  * Note: This class is designed to be used in "Point Mode
70697  * @singleton
70698  */
70699 Ext.define('Ext.dd.ScrollManager', {
70700     singleton: true,
70701     requires: [
70702         'Ext.dd.DragDropManager'
70703     ],
70704
70705     constructor: function() {
70706         var ddm = Ext.dd.DragDropManager;
70707         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
70708         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
70709         this.doScroll = Ext.Function.bind(this.doScroll, this);
70710         this.ddmInstance = ddm;
70711         this.els = {};
70712         this.dragEl = null;
70713         this.proc = {};
70714     },
70715
70716     onStop: function(e){
70717         var sm = Ext.dd.ScrollManager;
70718         sm.dragEl = null;
70719         sm.clearProc();
70720     },
70721
70722     triggerRefresh: function() {
70723         if (this.ddmInstance.dragCurrent) {
70724             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
70725         }
70726     },
70727
70728     doScroll: function() {
70729         if (this.ddmInstance.dragCurrent) {
70730             var proc   = this.proc,
70731                 procEl = proc.el,
70732                 ddScrollConfig = proc.el.ddScrollConfig,
70733                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
70734
70735             if (!this.animate) {
70736                 if (procEl.scroll(proc.dir, inc)) {
70737                     this.triggerRefresh();
70738                 }
70739             } else {
70740                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
70741             }
70742         }
70743     },
70744
70745     clearProc: function() {
70746         var proc = this.proc;
70747         if (proc.id) {
70748             clearInterval(proc.id);
70749         }
70750         proc.id = 0;
70751         proc.el = null;
70752         proc.dir = "";
70753     },
70754
70755     startProc: function(el, dir) {
70756         this.clearProc();
70757         this.proc.el = el;
70758         this.proc.dir = dir;
70759         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
70760             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
70761                   ? el.ddScrollConfig.frequency
70762                   : this.frequency;
70763
70764         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
70765             this.proc.id = setInterval(this.doScroll, freq);
70766         }
70767     },
70768
70769     onFire: function(e, isDrop) {
70770         if (isDrop || !this.ddmInstance.dragCurrent) {
70771             return;
70772         }
70773         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
70774             this.dragEl = this.ddmInstance.dragCurrent;
70775             // refresh regions on drag start
70776             this.refreshCache();
70777         }
70778
70779         var xy = e.getXY(),
70780             pt = e.getPoint(),
70781             proc = this.proc,
70782             els = this.els;
70783
70784         for (var id in els) {
70785             var el = els[id], r = el._region;
70786             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
70787             if (r && r.contains(pt) && el.isScrollable()) {
70788                 if (r.bottom - pt.y <= c.vthresh) {
70789                     if(proc.el != el){
70790                         this.startProc(el, "down");
70791                     }
70792                     return;
70793                 }else if (r.right - pt.x <= c.hthresh) {
70794                     if (proc.el != el) {
70795                         this.startProc(el, "left");
70796                     }
70797                     return;
70798                 } else if(pt.y - r.top <= c.vthresh) {
70799                     if (proc.el != el) {
70800                         this.startProc(el, "up");
70801                     }
70802                     return;
70803                 } else if(pt.x - r.left <= c.hthresh) {
70804                     if (proc.el != el) {
70805                         this.startProc(el, "right");
70806                     }
70807                     return;
70808                 }
70809             }
70810         }
70811         this.clearProc();
70812     },
70813
70814     /**
70815      * Registers new overflow element(s) to auto scroll
70816      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
70817      * The id of or the element to be scrolled or an array of either
70818      */
70819     register : function(el){
70820         if (Ext.isArray(el)) {
70821             for(var i = 0, len = el.length; i < len; i++) {
70822                     this.register(el[i]);
70823             }
70824         } else {
70825             el = Ext.get(el);
70826             this.els[el.id] = el;
70827         }
70828     },
70829
70830     /**
70831      * Unregisters overflow element(s) so they are no longer scrolled
70832      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
70833      * The id of or the element to be removed or an array of either
70834      */
70835     unregister : function(el){
70836         if(Ext.isArray(el)){
70837             for (var i = 0, len = el.length; i < len; i++) {
70838                 this.unregister(el[i]);
70839             }
70840         }else{
70841             el = Ext.get(el);
70842             delete this.els[el.id];
70843         }
70844     },
70845
70846     /**
70847      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
70848      * trigger scrolling
70849      * @type Number
70850      */
70851     vthresh : 25,
70852     /**
70853      * The number of pixels from the right or left edge of a container the pointer needs to be to
70854      * trigger scrolling
70855      * @type Number
70856      */
70857     hthresh : 25,
70858
70859     /**
70860      * The number of pixels to scroll in each scroll increment
70861      * @type Number
70862      */
70863     increment : 100,
70864
70865     /**
70866      * The frequency of scrolls in milliseconds
70867      * @type Number
70868      */
70869     frequency : 500,
70870
70871     /**
70872      * True to animate the scroll
70873      * @type Boolean
70874      */
70875     animate: true,
70876
70877     /**
70878      * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
70879      * @type Number
70880      */
70881     animDuration: 0.4,
70882
70883     /**
70884      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs.
70885      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
70886      * @type String
70887      */
70888     ddGroup: undefined,
70889
70890     /**
70891      * Manually trigger a cache refresh.
70892      */
70893     refreshCache : function(){
70894         var els = this.els,
70895             id;
70896         for (id in els) {
70897             if(typeof els[id] == 'object'){ // for people extending the object prototype
70898                 els[id]._region = els[id].getRegion();
70899             }
70900         }
70901     }
70902 });
70903
70904 /**
70905  * @class Ext.dd.DropTarget
70906  * @extends Ext.dd.DDTarget
70907  * A simple class that provides the basic implementation needed to make any element a drop target that can have
70908  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
70909  */
70910 Ext.define('Ext.dd.DropTarget', {
70911     extend: 'Ext.dd.DDTarget',
70912     requires: ['Ext.dd.ScrollManager'],
70913
70914     /**
70915      * Creates new DropTarget.
70916      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
70917      * @param {Object} config
70918      */
70919     constructor : function(el, config){
70920         this.el = Ext.get(el);
70921
70922         Ext.apply(this, config);
70923
70924         if(this.containerScroll){
70925             Ext.dd.ScrollManager.register(this.el);
70926         }
70927
70928         this.callParent([this.el.dom, this.ddGroup || this.group,
70929               {isTarget: true}]);
70930     },
70931
70932     /**
70933      * @cfg {String} ddGroup
70934      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
70935      * interact with other drag drop objects in the same group.
70936      */
70937     /**
70938      * @cfg {String} [overClass=""]
70939      * The CSS class applied to the drop target element while the drag source is over it.
70940      */
70941     /**
70942      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
70943      * The CSS class returned to the drag source when drop is allowed.
70944      */
70945     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
70946     /**
70947      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
70948      * The CSS class returned to the drag source when drop is not allowed.
70949      */
70950     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
70951
70952     // private
70953     isTarget : true,
70954
70955     // private
70956     isNotifyTarget : true,
70957
70958     /**
70959      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
70960      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
70961      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
70962      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
70963      * @param {Event} e The event
70964      * @param {Object} data An object containing arbitrary data supplied by the drag source
70965      * @return {String} status The CSS class that communicates the drop status back to the source so that the
70966      * underlying {@link Ext.dd.StatusProxy} can be updated
70967      */
70968     notifyEnter : function(dd, e, data){
70969         if(this.overClass){
70970             this.el.addCls(this.overClass);
70971         }
70972         return this.dropAllowed;
70973     },
70974
70975     /**
70976      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
70977      * This method will be called on every mouse movement while the drag source is over the drop target.
70978      * This default implementation simply returns the dropAllowed config value.
70979      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
70980      * @param {Event} e The event
70981      * @param {Object} data An object containing arbitrary data supplied by the drag source
70982      * @return {String} status The CSS class that communicates the drop status back to the source so that the
70983      * underlying {@link Ext.dd.StatusProxy} can be updated
70984      */
70985     notifyOver : function(dd, e, data){
70986         return this.dropAllowed;
70987     },
70988
70989     /**
70990      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
70991      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
70992      * overClass (if any) from the drop element.
70993      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
70994      * @param {Event} e The event
70995      * @param {Object} data An object containing arbitrary data supplied by the drag source
70996      */
70997     notifyOut : function(dd, e, data){
70998         if(this.overClass){
70999             this.el.removeCls(this.overClass);
71000         }
71001     },
71002
71003     /**
71004      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
71005      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
71006      * implementation that does something to process the drop event and returns true so that the drag source's
71007      * repair action does not run.
71008      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
71009      * @param {Event} e The event
71010      * @param {Object} data An object containing arbitrary data supplied by the drag source
71011      * @return {Boolean} False if the drop was invalid.
71012      */
71013     notifyDrop : function(dd, e, data){
71014         return false;
71015     },
71016
71017     destroy : function(){
71018         this.callParent();
71019         if(this.containerScroll){
71020             Ext.dd.ScrollManager.unregister(this.el);
71021         }
71022     }
71023 });
71024
71025 /**
71026  * @class Ext.dd.Registry
71027  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
71028  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
71029  * @singleton
71030  */
71031 Ext.define('Ext.dd.Registry', {
71032     singleton: true,
71033     constructor: function() {
71034         this.elements = {}; 
71035         this.handles = {}; 
71036         this.autoIdSeed = 0;
71037     },
71038     
71039     getId: function(el, autogen){
71040         if(typeof el == "string"){
71041             return el;
71042         }
71043         var id = el.id;
71044         if(!id && autogen !== false){
71045             id = "extdd-" + (++this.autoIdSeed);
71046             el.id = id;
71047         }
71048         return id;
71049     },
71050     
71051     /**
71052      * Resgister a drag drop element
71053      * @param {String/HTMLElement} element The id or DOM node to register
71054      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
71055      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
71056      * knows how to interpret, plus there are some specific properties known to the Registry that should be
71057      * populated in the data object (if applicable):
71058      * <pre>
71059 Value      Description<br />
71060 ---------  ------------------------------------------<br />
71061 handles    Array of DOM nodes that trigger dragging<br />
71062            for the element being registered<br />
71063 isHandle   True if the element passed in triggers<br />
71064            dragging itself, else false
71065 </pre>
71066      */
71067     register : function(el, data){
71068         data = data || {};
71069         if (typeof el == "string") {
71070             el = document.getElementById(el);
71071         }
71072         data.ddel = el;
71073         this.elements[this.getId(el)] = data;
71074         if (data.isHandle !== false) {
71075             this.handles[data.ddel.id] = data;
71076         }
71077         if (data.handles) {
71078             var hs = data.handles;
71079             for (var i = 0, len = hs.length; i < len; i++) {
71080                 this.handles[this.getId(hs[i])] = data;
71081             }
71082         }
71083     },
71084
71085     /**
71086      * Unregister a drag drop element
71087      * @param {String/HTMLElement} element The id or DOM node to unregister
71088      */
71089     unregister : function(el){
71090         var id = this.getId(el, false);
71091         var data = this.elements[id];
71092         if(data){
71093             delete this.elements[id];
71094             if(data.handles){
71095                 var hs = data.handles;
71096                 for (var i = 0, len = hs.length; i < len; i++) {
71097                     delete this.handles[this.getId(hs[i], false)];
71098                 }
71099             }
71100         }
71101     },
71102
71103     /**
71104      * Returns the handle registered for a DOM Node by id
71105      * @param {String/HTMLElement} id The DOM node or id to look up
71106      * @return {Object} handle The custom handle data
71107      */
71108     getHandle : function(id){
71109         if(typeof id != "string"){ // must be element?
71110             id = id.id;
71111         }
71112         return this.handles[id];
71113     },
71114
71115     /**
71116      * Returns the handle that is registered for the DOM node that is the target of the event
71117      * @param {Event} e The event
71118      * @return {Object} handle The custom handle data
71119      */
71120     getHandleFromEvent : function(e){
71121         var t = e.getTarget();
71122         return t ? this.handles[t.id] : null;
71123     },
71124
71125     /**
71126      * Returns a custom data object that is registered for a DOM node by id
71127      * @param {String/HTMLElement} id The DOM node or id to look up
71128      * @return {Object} data The custom data
71129      */
71130     getTarget : function(id){
71131         if(typeof id != "string"){ // must be element?
71132             id = id.id;
71133         }
71134         return this.elements[id];
71135     },
71136
71137     /**
71138      * Returns a custom data object that is registered for the DOM node that is the target of the event
71139      * @param {Event} e The event
71140      * @return {Object} data The custom data
71141      */
71142     getTargetFromEvent : function(e){
71143         var t = e.getTarget();
71144         return t ? this.elements[t.id] || this.handles[t.id] : null;
71145     }
71146 });
71147 /**
71148  * @class Ext.dd.DropZone
71149  * @extends Ext.dd.DropTarget
71150
71151 This class provides a container DD instance that allows dropping on multiple child target nodes.
71152
71153 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
71154 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
71155 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
71156 mouse event to see if it has taken place within an element, or class of elements. This is easily done
71157 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
71158 {@link Ext.DomQuery} selector.
71159
71160 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
71161 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
71162 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
71163 of these methods to provide application-specific behaviour for these events to update both
71164 application state, and UI state.
71165
71166 For example to make a GridPanel a cooperating target with the example illustrated in
71167 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
71168
71169     myGridPanel.on('render', function() {
71170         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
71171
71172             // If the mouse is over a grid row, return that node. This is
71173             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
71174             getTargetFromEvent: function(e) {
71175                 return e.getTarget(myGridPanel.getView().rowSelector);
71176             },
71177
71178             // On entry into a target node, highlight that node.
71179             onNodeEnter : function(target, dd, e, data){ 
71180                 Ext.fly(target).addCls('my-row-highlight-class');
71181             },
71182
71183             // On exit from a target node, unhighlight that node.
71184             onNodeOut : function(target, dd, e, data){ 
71185                 Ext.fly(target).removeCls('my-row-highlight-class');
71186             },
71187
71188             // While over a target node, return the default drop allowed class which
71189             // places a "tick" icon into the drag proxy.
71190             onNodeOver : function(target, dd, e, data){ 
71191                 return Ext.dd.DropZone.prototype.dropAllowed;
71192             },
71193
71194             // On node drop we can interrogate the target to find the underlying
71195             // application object that is the real target of the dragged data.
71196             // In this case, it is a Record in the GridPanel's Store.
71197             // We can use the data set up by the DragZone's getDragData method to read
71198             // any data we decided to attach in the DragZone's getDragData method.
71199             onNodeDrop : function(target, dd, e, data){
71200                 var rowIndex = myGridPanel.getView().findRowIndex(target);
71201                 var r = myGridPanel.getStore().getAt(rowIndex);
71202                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
71203                     ' on Record id ' + r.id);
71204                 return true;
71205             }
71206         });
71207     }
71208
71209 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
71210 cooperates with this DropZone.
71211
71212  * @markdown
71213  */
71214 Ext.define('Ext.dd.DropZone', {
71215     extend: 'Ext.dd.DropTarget',
71216     requires: ['Ext.dd.Registry'],
71217
71218     /**
71219      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
71220      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
71221      * provide your own custom lookup.
71222      * @param {Event} e The event
71223      * @return {Object} data The custom data
71224      */
71225     getTargetFromEvent : function(e){
71226         return Ext.dd.Registry.getTargetFromEvent(e);
71227     },
71228
71229     /**
71230      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
71231      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
71232      * This method has no default implementation and should be overridden to provide
71233      * node-specific processing if necessary.
71234      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
71235      * {@link #getTargetFromEvent} for this node)
71236      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71237      * @param {Event} e The event
71238      * @param {Object} data An object containing arbitrary data supplied by the drag source
71239      */
71240     onNodeEnter : function(n, dd, e, data){
71241         
71242     },
71243
71244     /**
71245      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
71246      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
71247      * The default implementation returns this.dropNotAllowed, so it should be
71248      * overridden to provide the proper feedback.
71249      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
71250      * {@link #getTargetFromEvent} for this node)
71251      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71252      * @param {Event} e The event
71253      * @param {Object} data An object containing arbitrary data supplied by the drag source
71254      * @return {String} status The CSS class that communicates the drop status back to the source so that the
71255      * underlying {@link Ext.dd.StatusProxy} can be updated
71256      */
71257     onNodeOver : function(n, dd, e, data){
71258         return this.dropAllowed;
71259     },
71260
71261     /**
71262      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
71263      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
71264      * node-specific processing if necessary.
71265      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
71266      * {@link #getTargetFromEvent} for this node)
71267      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71268      * @param {Event} e The event
71269      * @param {Object} data An object containing arbitrary data supplied by the drag source
71270      */
71271     onNodeOut : function(n, dd, e, data){
71272         
71273     },
71274
71275     /**
71276      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
71277      * the drop node.  The default implementation returns false, so it should be overridden to provide the
71278      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
71279      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
71280      * {@link #getTargetFromEvent} for this node)
71281      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71282      * @param {Event} e The event
71283      * @param {Object} data An object containing arbitrary data supplied by the drag source
71284      * @return {Boolean} True if the drop was valid, else false
71285      */
71286     onNodeDrop : function(n, dd, e, data){
71287         return false;
71288     },
71289
71290     /**
71291      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
71292      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
71293      * it should be overridden to provide the proper feedback if necessary.
71294      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71295      * @param {Event} e The event
71296      * @param {Object} data An object containing arbitrary data supplied by the drag source
71297      * @return {String} status The CSS class that communicates the drop status back to the source so that the
71298      * underlying {@link Ext.dd.StatusProxy} can be updated
71299      */
71300     onContainerOver : function(dd, e, data){
71301         return this.dropNotAllowed;
71302     },
71303
71304     /**
71305      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
71306      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
71307      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
71308      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
71309      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71310      * @param {Event} e The event
71311      * @param {Object} data An object containing arbitrary data supplied by the drag source
71312      * @return {Boolean} True if the drop was valid, else false
71313      */
71314     onContainerDrop : function(dd, e, data){
71315         return false;
71316     },
71317
71318     /**
71319      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
71320      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
71321      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
71322      * you should override this method and provide a custom implementation.
71323      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71324      * @param {Event} e The event
71325      * @param {Object} data An object containing arbitrary data supplied by the drag source
71326      * @return {String} status The CSS class that communicates the drop status back to the source so that the
71327      * underlying {@link Ext.dd.StatusProxy} can be updated
71328      */
71329     notifyEnter : function(dd, e, data){
71330         return this.dropNotAllowed;
71331     },
71332
71333     /**
71334      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
71335      * This method will be called on every mouse movement while the drag source is over the drop zone.
71336      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
71337      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
71338      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
71339      * registered node, it will call {@link #onContainerOver}.
71340      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71341      * @param {Event} e The event
71342      * @param {Object} data An object containing arbitrary data supplied by the drag source
71343      * @return {String} status The CSS class that communicates the drop status back to the source so that the
71344      * underlying {@link Ext.dd.StatusProxy} can be updated
71345      */
71346     notifyOver : function(dd, e, data){
71347         var n = this.getTargetFromEvent(e);
71348         if(!n) { // not over valid drop target
71349             if(this.lastOverNode){
71350                 this.onNodeOut(this.lastOverNode, dd, e, data);
71351                 this.lastOverNode = null;
71352             }
71353             return this.onContainerOver(dd, e, data);
71354         }
71355         if(this.lastOverNode != n){
71356             if(this.lastOverNode){
71357                 this.onNodeOut(this.lastOverNode, dd, e, data);
71358             }
71359             this.onNodeEnter(n, dd, e, data);
71360             this.lastOverNode = n;
71361         }
71362         return this.onNodeOver(n, dd, e, data);
71363     },
71364
71365     /**
71366      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
71367      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
71368      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
71369      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
71370      * @param {Event} e The event
71371      * @param {Object} data An object containing arbitrary data supplied by the drag zone
71372      */
71373     notifyOut : function(dd, e, data){
71374         if(this.lastOverNode){
71375             this.onNodeOut(this.lastOverNode, dd, e, data);
71376             this.lastOverNode = null;
71377         }
71378     },
71379
71380     /**
71381      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
71382      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
71383      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
71384      * otherwise it will call {@link #onContainerDrop}.
71385      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
71386      * @param {Event} e The event
71387      * @param {Object} data An object containing arbitrary data supplied by the drag source
71388      * @return {Boolean} False if the drop was invalid.
71389      */
71390     notifyDrop : function(dd, e, data){
71391         if(this.lastOverNode){
71392             this.onNodeOut(this.lastOverNode, dd, e, data);
71393             this.lastOverNode = null;
71394         }
71395         var n = this.getTargetFromEvent(e);
71396         return n ?
71397             this.onNodeDrop(n, dd, e, data) :
71398             this.onContainerDrop(dd, e, data);
71399     },
71400
71401     // private
71402     triggerCacheRefresh : function() {
71403         Ext.dd.DDM.refreshCache(this.groups);
71404     }
71405 });
71406 /**
71407  * @class Ext.flash.Component
71408  * @extends Ext.Component
71409  *
71410  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
71411  * in layout like any other Component.
71412  *
71413  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
71414  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
71415  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
71416  * and then simply import it into the head of your HTML document:
71417  *
71418  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
71419  *
71420  * ## Configuration
71421  *
71422  * This component allows several options for configuring how the target Flash movie is embedded. The most
71423  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
71424  * configurations include:
71425  *
71426  * - {@link #backgroundColor}
71427  * - {@link #wmode}
71428  * - {@link #flashVars}
71429  * - {@link #flashParams}
71430  * - {@link #flashAttributes}
71431  *
71432  * ## Example usage:
71433  *
71434  *     var win = Ext.widget('window', {
71435  *         title: "It's a tiger!",
71436  *         layout: 'fit',
71437  *         width: 300,
71438  *         height: 300,
71439  *         x: 20,
71440  *         y: 20,
71441  *         resizable: true,
71442  *         items: {
71443  *             xtype: 'flash',
71444  *             url: 'tiger.swf'
71445  *         }
71446  *     });
71447  *     win.show();
71448  *
71449  * ## Express Install
71450  *
71451  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
71452  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
71453  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
71454  *
71455  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
71456  *
71457  * @docauthor Jason Johnston <jason@sencha.com>
71458  */
71459 Ext.define('Ext.flash.Component', {
71460     extend: 'Ext.Component',
71461     alternateClassName: 'Ext.FlashComponent',
71462     alias: 'widget.flash',
71463
71464     /**
71465      * @cfg {String} flashVersion
71466      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
71467      */
71468     flashVersion : '9.0.115',
71469
71470     /**
71471      * @cfg {String} backgroundColor
71472      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
71473      */
71474     backgroundColor: '#ffffff',
71475
71476     /**
71477      * @cfg {String} wmode
71478      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
71479      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
71480      * movie transparent.
71481      */
71482     wmode: 'opaque',
71483
71484     /**
71485      * @cfg {Object} flashVars
71486      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
71487      */
71488
71489     /**
71490      * @cfg {Object} flashParams
71491      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
71492      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
71493      */
71494
71495     /**
71496      * @cfg {Object} flashAttributes
71497      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
71498      */
71499
71500     /**
71501      * @cfg {String} url
71502      * The URL of the SWF file to include. Required.
71503      */
71504
71505     /**
71506      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
71507      * so that the movie matches the width of the component.
71508      */
71509     swfWidth: '100%',
71510
71511     /**
71512      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
71513      * so that the movie matches the height of the component.
71514      */
71515     swfHeight: '100%',
71516
71517     /**
71518      * @cfg {Boolean} expressInstall
71519      * True to prompt the user to install flash if not installed. Note that this uses
71520      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
71521      */
71522     expressInstall: false,
71523
71524     /**
71525      * @property swf
71526      * @type {Ext.Element}
71527      * A reference to the object or embed element into which the SWF file is loaded. Only
71528      * populated after the component is rendered and the SWF has been successfully embedded.
71529      */
71530
71531     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
71532     renderTpl: ['<div id="{swfId}"></div>'],
71533
71534     initComponent: function() {
71535         // <debug>
71536         if (!('swfobject' in window)) {
71537             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/');
71538         }
71539         if (!this.url) {
71540             Ext.Error.raise('The "url" config is required for Ext.flash.Component');
71541         }
71542         // </debug>
71543
71544         this.callParent();
71545         this.addEvents(
71546             /**
71547              * @event success
71548              * Fired when the Flash movie has been successfully embedded
71549              * @param {Ext.flash.Component} this
71550              */
71551             'success',
71552
71553             /**
71554              * @event failure
71555              * Fired when the Flash movie embedding fails
71556              * @param {Ext.flash.Component} this
71557              */
71558             'failure'
71559         );
71560     },
71561
71562     onRender: function() {
71563         var me = this,
71564             params, vars, undef,
71565             swfId = me.getSwfId();
71566
71567         me.renderData.swfId = swfId;
71568
71569         me.callParent(arguments);
71570
71571         params = Ext.apply({
71572             allowScriptAccess: 'always',
71573             bgcolor: me.backgroundColor,
71574             wmode: me.wmode
71575         }, me.flashParams);
71576
71577         vars = Ext.apply({
71578             allowedDomain: document.location.hostname
71579         }, me.flashVars);
71580
71581         new swfobject.embedSWF(
71582             me.url,
71583             swfId,
71584             me.swfWidth,
71585             me.swfHeight,
71586             me.flashVersion,
71587             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
71588             vars,
71589             params,
71590             me.flashAttributes,
71591             Ext.bind(me.swfCallback, me)
71592         );
71593     },
71594
71595     /**
71596      * @private
71597      * The callback method for handling an embedding success or failure by SWFObject
71598      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
71599      */
71600     swfCallback: function(e) {
71601         var me = this;
71602         if (e.success) {
71603             me.swf = Ext.get(e.ref);
71604             me.onSuccess();
71605             me.fireEvent('success', me);
71606         } else {
71607             me.onFailure();
71608             me.fireEvent('failure', me);
71609         }
71610     },
71611
71612     /**
71613      * Retrieve the id of the SWF object/embed element
71614      */
71615     getSwfId: function() {
71616         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
71617     },
71618
71619     onSuccess: function() {
71620         // swfobject forces visiblity:visible on the swf element, which prevents it 
71621         // from getting hidden when an ancestor is given visibility:hidden.
71622         this.swf.setStyle('visibility', 'inherit');
71623     },
71624
71625     onFailure: Ext.emptyFn,
71626
71627     beforeDestroy: function() {
71628         var me = this,
71629             swf = me.swf;
71630         if (swf) {
71631             swfobject.removeSWF(me.getSwfId());
71632             Ext.destroy(swf);
71633             delete me.swf;
71634         }
71635         me.callParent();
71636     },
71637
71638     statics: {
71639         /**
71640          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
71641          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
71642          * @static
71643          * @type String
71644          */
71645         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
71646     }
71647 });
71648
71649 /**
71650  * @class Ext.form.action.Action
71651  * @extends Ext.Base
71652  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
71653  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
71654  * the Form needs to perform an action such as submit or load. The Configuration options
71655  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
71656  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
71657  * <p>The instance of Action which performed the action is passed to the success
71658  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
71659  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
71660  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
71661  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
71662  */
71663 Ext.define('Ext.form.action.Action', {
71664     alternateClassName: 'Ext.form.Action',
71665
71666     /**
71667      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
71668      * is invoking this Action. Required.
71669      */
71670
71671     /**
71672      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
71673      * configured on the {@link #form}.
71674      */
71675
71676     /**
71677      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
71678      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
71679      * before the {@link #success} callback is called and before the Form's
71680      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
71681      */
71682
71683     /**
71684      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
71685      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
71686      */
71687
71688     /**
71689      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
71690      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
71691      * input fields.</p>
71692      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
71693      */
71694
71695     /**
71696      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
71697      * {@link Ext.data.proxy.Ajax#headers}.</p>
71698      */
71699
71700     /**
71701      * @cfg {Number} timeout The number of seconds to wait for a server response before
71702      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
71703      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
71704      * {@link #form}.
71705      */
71706
71707     /**
71708      * @cfg {Function} success The function to call when a valid success return packet is received.
71709      * The function is passed the following parameters:<ul class="mdetail-params">
71710      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
71711      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
71712      * property of this object may be examined to perform custom postprocessing.</div></li>
71713      * </ul>
71714      */
71715
71716     /**
71717      * @cfg {Function} failure The function to call when a failure packet was received, or when an
71718      * error ocurred in the Ajax communication.
71719      * The function is passed the following parameters:<ul class="mdetail-params">
71720      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
71721      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
71722      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
71723      * property of this object may be examined to perform custom postprocessing.</div></li>
71724      * </ul>
71725      */
71726
71727     /**
71728      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
71729      * callback functions (the <tt>this</tt> reference for the callback functions).
71730      */
71731
71732     /**
71733      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
71734      * during the time the action is being processed.
71735      */
71736
71737     /**
71738      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
71739      * during the time the action is being processed.
71740      */
71741
71742     /**
71743      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
71744      * when it is submitted. Defaults to <tt>true</tt>.
71745      */
71746     submitEmptyText : true,
71747     /**
71748      * @property type
71749      * The type of action this Action instance performs.
71750      * Currently only "submit" and "load" are supported.
71751      * @type {String}
71752      */
71753
71754     /**
71755      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
71756      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
71757      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
71758      * <pre><code>
71759 var fp = new Ext.form.Panel({
71760 ...
71761 buttons: [{
71762     text: 'Save',
71763     formBind: true,
71764     handler: function(){
71765         if(fp.getForm().isValid()){
71766             fp.getForm().submit({
71767                 url: 'form-submit.php',
71768                 waitMsg: 'Submitting your data...',
71769                 success: function(form, action){
71770                     // server responded with success = true
71771                     var result = action.{@link #result};
71772                 },
71773                 failure: function(form, action){
71774                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
71775                         Ext.Msg.alert('Error',
71776                             'Status:'+action.{@link #response}.status+': '+
71777                             action.{@link #response}.statusText);
71778                     }
71779                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
71780                         // server responded with success = false
71781                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
71782                     }
71783                 }
71784             });
71785         }
71786     }
71787 },{
71788     text: 'Reset',
71789     handler: function(){
71790         fp.getForm().reset();
71791     }
71792 }]
71793      * </code></pre>
71794      * @property failureType
71795      * @type {String}
71796      */
71797
71798     /**
71799      * The raw XMLHttpRequest object used to perform the action.
71800      * @property response
71801      * @type {Object}
71802      */
71803
71804     /**
71805      * The decoded response object containing a boolean <tt>success</tt> property and
71806      * other, action-specific properties.
71807      * @property result
71808      * @type {Object}
71809      */
71810
71811     /**
71812      * Creates new Action.
71813      * @param {Object} config (optional) Config object.
71814      */
71815     constructor: function(config) {
71816         if (config) {
71817             Ext.apply(this, config);
71818         }
71819
71820         // Normalize the params option to an Object
71821         var params = config.params;
71822         if (Ext.isString(params)) {
71823             this.params = Ext.Object.fromQueryString(params);
71824         }
71825     },
71826
71827     /**
71828      * Invokes this action using the current configuration.
71829      */
71830     run: Ext.emptyFn,
71831
71832     /**
71833      * @private
71834      * @method onSuccess
71835      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
71836      * @param {Object} response
71837      */
71838
71839     /**
71840      * @private
71841      * @method handleResponse
71842      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
71843      * @param {Object} response
71844      */
71845
71846     /**
71847      * @private
71848      * Handles a failure response.
71849      * @param {Object} response
71850      */
71851     onFailure : function(response){
71852         this.response = response;
71853         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
71854         this.form.afterAction(this, false);
71855     },
71856
71857     /**
71858      * @private
71859      * Validates that a response contains either responseText or responseXML and invokes
71860      * {@link #handleResponse} to build the result object.
71861      * @param {Object} response The raw response object.
71862      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
71863      *                         the response had empty responseText and responseXML.
71864      */
71865     processResponse : function(response){
71866         this.response = response;
71867         if (!response.responseText && !response.responseXML) {
71868             return true;
71869         }
71870         return (this.result = this.handleResponse(response));
71871     },
71872
71873     /**
71874      * @private
71875      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
71876      * @return {String} The URL.
71877      */
71878     getUrl: function() {
71879         return this.url || this.form.url;
71880     },
71881
71882     /**
71883      * @private
71884      * Determine the HTTP method to be used for the request.
71885      * @return {String} The HTTP method
71886      */
71887     getMethod: function() {
71888         return (this.method || this.form.method || 'POST').toUpperCase();
71889     },
71890
71891     /**
71892      * @private
71893      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
71894      * Items in params override items of the same name in baseParams.
71895      * @return {Object} the full set of parameters
71896      */
71897     getParams: function() {
71898         return Ext.apply({}, this.params, this.form.baseParams);
71899     },
71900
71901     /**
71902      * @private
71903      * Creates a callback object.
71904      */
71905     createCallback: function() {
71906         var me = this,
71907             undef,
71908             form = me.form;
71909         return {
71910             success: me.onSuccess,
71911             failure: me.onFailure,
71912             scope: me,
71913             timeout: (this.timeout * 1000) || (form.timeout * 1000),
71914             upload: form.fileUpload ? me.onSuccess : undef
71915         };
71916     },
71917
71918     statics: {
71919         /**
71920          * @property CLIENT_INVALID
71921          * Failure type returned when client side validation of the Form fails
71922          * thus aborting a submit action. Client side validation is performed unless
71923          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
71924          * @type {String}
71925          * @static
71926          */
71927         CLIENT_INVALID: 'client',
71928
71929         /**
71930          * @property SERVER_INVALID
71931          * <p>Failure type returned when server side processing fails and the {@link #result}'s
71932          * <tt>success</tt> property is set to <tt>false</tt>.</p>
71933          * <p>In the case of a form submission, field-specific error messages may be returned in the
71934          * {@link #result}'s <tt>errors</tt> property.</p>
71935          * @type {String}
71936          * @static
71937          */
71938         SERVER_INVALID: 'server',
71939
71940         /**
71941          * @property CONNECT_FAILURE
71942          * Failure type returned when a communication error happens when attempting
71943          * to send a request to the remote server. The {@link #response} may be examined to
71944          * provide further information.
71945          * @type {String}
71946          * @static
71947          */
71948         CONNECT_FAILURE: 'connect',
71949
71950         /**
71951          * @property LOAD_FAILURE
71952          * Failure type returned when the response's <tt>success</tt>
71953          * property is set to <tt>false</tt>, or no field values are returned in the response's
71954          * <tt>data</tt> property.
71955          * @type {String}
71956          * @static
71957          */
71958         LOAD_FAILURE: 'load'
71959
71960
71961     }
71962 });
71963
71964 /**
71965  * @class Ext.form.action.Submit
71966  * @extends Ext.form.action.Action
71967  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
71968  * and processes the returned response.</p>
71969  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
71970  * {@link Ext.form.Basic#submit submit}ting.</p>
71971  * <p><u><b>Response Packet Criteria</b></u></p>
71972  * <p>A response packet may contain:
71973  * <div class="mdetail-params"><ul>
71974  * <li><b><code>success</code></b> property : Boolean
71975  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
71976  * <li><b><code>errors</code></b> property : Object
71977  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
71978  * which is optional, contains error messages for invalid fields.</div></li>
71979  * </ul></div>
71980  * <p><u><b>JSON Packets</b></u></p>
71981  * <p>By default, response packets are assumed to be JSON, so a typical response
71982  * packet may look like this:</p><pre><code>
71983 {
71984     success: false,
71985     errors: {
71986         clientCode: "Client not found",
71987         portOfLoading: "This field must not be null"
71988     }
71989 }</code></pre>
71990  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
71991  * or event handler methods. The object decoded from this JSON is available in the
71992  * {@link Ext.form.action.Action#result result} property.</p>
71993  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
71994     errorReader: new Ext.data.reader.Xml({
71995             record : 'field',
71996             success: '@success'
71997         }, [
71998             'id', 'msg'
71999         ]
72000     )
72001 </code></pre>
72002  * <p>then the results may be sent back in XML format:</p><pre><code>
72003 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
72004 &lt;message success="false"&gt;
72005 &lt;errors&gt;
72006     &lt;field&gt;
72007         &lt;id&gt;clientCode&lt;/id&gt;
72008         &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;
72009     &lt;/field&gt;
72010     &lt;field&gt;
72011         &lt;id&gt;portOfLoading&lt;/id&gt;
72012         &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;
72013     &lt;/field&gt;
72014 &lt;/errors&gt;
72015 &lt;/message&gt;
72016 </code></pre>
72017  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
72018  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
72019  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
72020  */
72021 Ext.define('Ext.form.action.Submit', {
72022     extend:'Ext.form.action.Action',
72023     alternateClassName: 'Ext.form.Action.Submit',
72024     alias: 'formaction.submit',
72025
72026     type: 'submit',
72027
72028     /**
72029      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
72030      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
72031      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
72032      */
72033
72034     // inherit docs
72035     run : function(){
72036         var form = this.form;
72037         if (this.clientValidation === false || form.isValid()) {
72038             this.doSubmit();
72039         } else {
72040             // client validation failed
72041             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
72042             form.afterAction(this, false);
72043         }
72044     },
72045
72046     /**
72047      * @private
72048      * Perform the submit of the form data.
72049      */
72050     doSubmit: function() {
72051         var formEl,
72052             ajaxOptions = Ext.apply(this.createCallback(), {
72053                 url: this.getUrl(),
72054                 method: this.getMethod(),
72055                 headers: this.headers
72056             });
72057
72058         // For uploads we need to create an actual form that contains the file upload fields,
72059         // and pass that to the ajax call so it can do its iframe-based submit method.
72060         if (this.form.hasUpload()) {
72061             formEl = ajaxOptions.form = this.buildForm();
72062             ajaxOptions.isUpload = true;
72063         } else {
72064             ajaxOptions.params = this.getParams();
72065         }
72066
72067         Ext.Ajax.request(ajaxOptions);
72068
72069         if (formEl) {
72070             Ext.removeNode(formEl);
72071         }
72072     },
72073
72074     /**
72075      * @private
72076      * Build the full set of parameters from the field values plus any additional configured params.
72077      */
72078     getParams: function() {
72079         var nope = false,
72080             configParams = this.callParent(),
72081             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
72082         return Ext.apply({}, fieldParams, configParams);
72083     },
72084
72085     /**
72086      * @private
72087      * Build a form element containing fields corresponding to all the parameters to be
72088      * submitted (everything returned by {@link #getParams}.
72089      * NOTE: the form element is automatically added to the DOM, so any code that uses
72090      * it must remove it from the DOM after finishing with it.
72091      * @return HTMLFormElement
72092      */
72093     buildForm: function() {
72094         var fieldsSpec = [],
72095             formSpec,
72096             formEl,
72097             basicForm = this.form,
72098             params = this.getParams(),
72099             uploadFields = [];
72100
72101         basicForm.getFields().each(function(field) {
72102             if (field.isFileUpload()) {
72103                 uploadFields.push(field);
72104             }
72105         });
72106
72107         function addField(name, val) {
72108             fieldsSpec.push({
72109                 tag: 'input',
72110                 type: 'hidden',
72111                 name: name,
72112                 value: Ext.String.htmlEncode(val)
72113             });
72114         }
72115
72116         // Add the form field values
72117         Ext.iterate(params, function(key, val) {
72118             if (Ext.isArray(val)) {
72119                 Ext.each(val, function(v) {
72120                     addField(key, v);
72121                 });
72122             } else {
72123                 addField(key, val);
72124             }
72125         });
72126
72127         formSpec = {
72128             tag: 'form',
72129             action: this.getUrl(),
72130             method: this.getMethod(),
72131             target: this.target || '_self',
72132             style: 'display:none',
72133             cn: fieldsSpec
72134         };
72135
72136         // Set the proper encoding for file uploads
72137         if (uploadFields.length) {
72138             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
72139         }
72140
72141         // Create the form
72142         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
72143
72144         // Special handling for file upload fields: since browser security measures prevent setting
72145         // their values programatically, and prevent carrying their selected values over when cloning,
72146         // we have to move the actual field instances out of their components and into the form.
72147         Ext.Array.each(uploadFields, function(field) {
72148             if (field.rendered) { // can only have a selected file value after being rendered
72149                 formEl.appendChild(field.extractFileInput());
72150             }
72151         });
72152
72153         return formEl;
72154     },
72155
72156
72157
72158     /**
72159      * @private
72160      */
72161     onSuccess: function(response) {
72162         var form = this.form,
72163             success = true,
72164             result = this.processResponse(response);
72165         if (result !== true && !result.success) {
72166             if (result.errors) {
72167                 form.markInvalid(result.errors);
72168             }
72169             this.failureType = Ext.form.action.Action.SERVER_INVALID;
72170             success = false;
72171         }
72172         form.afterAction(this, success);
72173     },
72174
72175     /**
72176      * @private
72177      */
72178     handleResponse: function(response) {
72179         var form = this.form,
72180             errorReader = form.errorReader,
72181             rs, errors, i, len, records;
72182         if (errorReader) {
72183             rs = errorReader.read(response);
72184             records = rs.records;
72185             errors = [];
72186             if (records) {
72187                 for(i = 0, len = records.length; i < len; i++) {
72188                     errors[i] = records[i].data;
72189                 }
72190             }
72191             if (errors.length < 1) {
72192                 errors = null;
72193             }
72194             return {
72195                 success : rs.success,
72196                 errors : errors
72197             };
72198         }
72199         return Ext.decode(response.responseText);
72200     }
72201 });
72202
72203 /**
72204  * @class Ext.util.ComponentDragger
72205  * @extends Ext.dd.DragTracker
72206  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
72207  * <p>This is configured with a Component to be made draggable, and a config object for the
72208  * {@link Ext.dd.DragTracker} class.</p>
72209  * <p>A {@link #delegate} may be provided which may be either the element to use as the mousedown target
72210  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
72211  */
72212 Ext.define('Ext.util.ComponentDragger', {
72213
72214     /**
72215      * @cfg {Boolean} constrain
72216      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
72217      */
72218
72219     /**
72220      * @cfg {String/Ext.Element} delegate
72221      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
72222      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
72223      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
72224      */
72225
72226     /**
72227      * @cfg {Boolean} constrainDelegate
72228      * Specify as <code>true</code> to constrain the drag handles within the {@link #constrainTo} region.
72229      */
72230
72231     extend: 'Ext.dd.DragTracker',
72232
72233     autoStart: 500,
72234
72235     /**
72236      * Creates new ComponentDragger.
72237      * @param {Object} comp The Component to provide dragging for.
72238      * @param {Object} config (optional) Config object
72239      */
72240     constructor: function(comp, config) {
72241         this.comp = comp;
72242         this.initialConstrainTo = config.constrainTo;
72243         this.callParent([ config ]);
72244     },
72245
72246     onStart: function(e) {
72247         var me = this,
72248             comp = me.comp;
72249
72250         // Cache the start [X, Y] array
72251         this.startPosition = comp.getPosition();
72252
72253         // If client Component has a ghost method to show a lightweight version of itself
72254         // then use that as a drag proxy unless configured to liveDrag.
72255         if (comp.ghost && !comp.liveDrag) {
72256              me.proxy = comp.ghost();
72257              me.dragTarget = me.proxy.header.el;
72258         }
72259
72260         // Set the constrainTo Region before we start dragging.
72261         if (me.constrain || me.constrainDelegate) {
72262             me.constrainTo = me.calculateConstrainRegion();
72263         }
72264     },
72265
72266     calculateConstrainRegion: function() {
72267         var me = this,
72268             comp = me.comp,
72269             c = me.initialConstrainTo,
72270             delegateRegion,
72271             elRegion,
72272             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
72273
72274         // The configured constrainTo might be a Region or an element
72275         if (!(c instanceof Ext.util.Region)) {
72276             c =  Ext.fly(c).getViewRegion();
72277         }
72278
72279         // Reduce the constrain region to allow for shadow
72280         if (shadowSize) {
72281             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
72282         }
72283
72284         // If they only want to constrain the *delegate* to within the constrain region,
72285         // adjust the region to be larger based on the insets of the delegate from the outer
72286         // edges of the Component.
72287         if (!me.constrainDelegate) {
72288             delegateRegion = Ext.fly(me.dragTarget).getRegion();
72289             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
72290
72291             c.adjust(
72292                 delegateRegion.top - elRegion.top,
72293                 delegateRegion.right - elRegion.right,
72294                 delegateRegion.bottom - elRegion.bottom,
72295                 delegateRegion.left - elRegion.left
72296             );
72297         }
72298         return c;
72299     },
72300
72301     // Move either the ghost Component or the target Component to its new position on drag
72302     onDrag: function(e) {
72303         var me = this,
72304             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
72305             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
72306
72307         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
72308     },
72309
72310     onEnd: function(e) {
72311         if (this.proxy && !this.comp.liveDrag) {
72312             this.comp.unghost();
72313         }
72314     }
72315 });
72316 /**
72317  * A mixin which allows a component to be configured and decorated with a label and/or error message as is
72318  * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
72319  * to let them be managed by the Field layout.
72320  *
72321  * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
72322  * is more likely you will want to use one of the component classes that import this mixin, such as
72323  * Ext.form.field.Base or Ext.form.FieldContainer.
72324  *
72325  * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
72326  * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
72327  * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
72328  * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
72329  * two.)
72330  *
72331  * Component classes which use this mixin should use the Field layout
72332  * or a derivation thereof to properly size and position the label and message according to the component config.
72333  * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
72334  * set up correctly.
72335  *
72336  * @docauthor Jason Johnston <jason@sencha.com>
72337  */
72338 Ext.define("Ext.form.Labelable", {
72339     requires: ['Ext.XTemplate'],
72340
72341     /**
72342      * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
72343      * The rendering template for the field decorations. Component classes using this mixin should include
72344      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
72345      * {@link #getSubTplMarkup} method to generate the field body content.
72346      */
72347     labelableRenderTpl: [
72348         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
72349             '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
72350                 '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
72351                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
72352             '</label>',
72353         '</tpl>',
72354         '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
72355         '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
72356         '<div class="{clearCls}" role="presentation"><!-- --></div>',
72357         {
72358             compiled: true,
72359             disableFormats: true
72360         }
72361     ],
72362
72363     /**
72364      * @cfg {Ext.XTemplate} activeErrorsTpl
72365      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
72366      * into a single HTML string. By default this renders each message as an item in an unordered list.
72367      */
72368     activeErrorsTpl: [
72369         '<tpl if="errors && errors.length">',
72370             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
72371         '</tpl>'
72372     ],
72373
72374     /**
72375      * @property isFieldLabelable
72376      * @type Boolean
72377      * Flag denoting that this object is labelable as a field. Always true.
72378      */
72379     isFieldLabelable: true,
72380
72381     /**
72382      * @cfg {String} [formItemCls='x-form-item']
72383      * A CSS class to be applied to the outermost element to denote that it is participating in the form
72384      * field layout.
72385      */
72386     formItemCls: Ext.baseCSSPrefix + 'form-item',
72387
72388     /**
72389      * @cfg {String} [labelCls='x-form-item-label']
72390      * The CSS class to be applied to the label element.
72391      * This (single) CSS class is used to formulate the renderSelector and drives the field
72392      * layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
72393      * additional classes, use {@link #labelClsExtra}.
72394      */
72395     labelCls: Ext.baseCSSPrefix + 'form-item-label',
72396
72397     /**
72398      * @cfg {String} labelClsExtra
72399      * An optional string of one or more additional CSS classes to add to the label element.
72400      * Defaults to empty.
72401      */
72402
72403     /**
72404      * @cfg {String} [errorMsgCls='x-form-error-msg']
72405      * The CSS class to be applied to the error message element.
72406      */
72407     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
72408
72409     /**
72410      * @cfg {String} [baseBodyCls='x-form-item-body']
72411      * The CSS class to be applied to the body content element.
72412      */
72413     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
72414
72415     /**
72416      * @cfg {String} fieldBodyCls
72417      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
72418      */
72419     fieldBodyCls: '',
72420
72421     /**
72422      * @cfg {String} [clearCls='x-clear']
72423      * The CSS class to be applied to the special clearing div rendered directly after the field
72424      * contents wrapper to provide field clearing.
72425      */
72426     clearCls: Ext.baseCSSPrefix + 'clear',
72427
72428     /**
72429      * @cfg {String} [invalidCls='x-form-invalid']
72430      * The CSS class to use when marking the component invalid.
72431      */
72432     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
72433
72434     /**
72435      * @cfg {String} fieldLabel
72436      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
72437      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
72438      * configs.
72439      */
72440     fieldLabel: undefined,
72441
72442     /**
72443      * @cfg {String} labelAlign
72444      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
72445      * <ul>
72446      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
72447      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
72448      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
72449      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
72450      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
72451      * </ul>
72452      */
72453     labelAlign : 'left',
72454
72455     /**
72456      * @cfg {Number} labelWidth
72457      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
72458      * to "left" or "right".
72459      */
72460     labelWidth: 100,
72461
72462     /**
72463      * @cfg {Number} labelPad
72464      * The amount of space in pixels between the {@link #fieldLabel} and the input field.
72465      */
72466     labelPad : 5,
72467
72468     /**
72469      * @cfg {String} labelSeparator
72470      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
72471      */
72472     labelSeparator : ':',
72473
72474     /**
72475      * @cfg {String} labelStyle
72476      * A CSS style specification string to apply directly to this field's label.
72477      */
72478
72479     /**
72480      * @cfg {Boolean} hideLabel
72481      * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
72482      * Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
72483      */
72484     hideLabel: false,
72485
72486     /**
72487      * @cfg {Boolean} hideEmptyLabel
72488      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
72489      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
72490      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
72491      * to line up with other labeled fields in the same form.</p>
72492      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
72493      * the {@link #hideLabel} config to <tt>true</tt>.</p>
72494      */
72495     hideEmptyLabel: true,
72496
72497     /**
72498      * @cfg {Boolean} preventMark
72499      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
72500      */
72501     preventMark: false,
72502
72503     /**
72504      * @cfg {Boolean} autoFitErrors
72505      * Whether to adjust the component's body area to make room for 'side' or 'under'
72506      * {@link #msgTarget error messages}.
72507      */
72508     autoFitErrors: true,
72509
72510     /**
72511      * @cfg {String} msgTarget <p>The location where the error message text should display.
72512      * Must be one of the following values:</p>
72513      * <div class="mdetail-params"><ul>
72514      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
72515      * <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>
72516      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
72517      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
72518      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
72519      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
72520      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
72521      * </ul></div>
72522      */
72523     msgTarget: 'qtip',
72524
72525     /**
72526      * @cfg {String} activeError
72527      * If specified, then the component will be displayed with this value as its active error when
72528      * first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to
72529      * change it after component creation.
72530      */
72531
72532
72533     /**
72534      * Performs initialization of this mixin. Component classes using this mixin should call this method
72535      * during their own initialization.
72536      */
72537     initLabelable: function() {
72538         this.addCls(this.formItemCls);
72539
72540         this.addEvents(
72541             /**
72542              * @event errorchange
72543              * Fires when the active error message is changed via {@link #setActiveError}.
72544              * @param {Ext.form.Labelable} this
72545              * @param {String} error The active error message
72546              */
72547             'errorchange'
72548         );
72549     },
72550
72551     /**
72552      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
72553      * overridden to provide
72554      * @return {String} The configured field label, or empty string if not defined
72555      */
72556     getFieldLabel: function() {
72557         return this.fieldLabel || '';
72558     },
72559
72560     /**
72561      * @protected
72562      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
72563      * @return {Object} The template arguments
72564      */
72565     getLabelableRenderData: function() {
72566         var me = this,
72567             labelAlign = me.labelAlign,
72568             labelCls = me.labelCls,
72569             labelClsExtra = me.labelClsExtra,
72570             labelPad = me.labelPad,
72571             labelStyle;
72572
72573         // Calculate label styles up front rather than in the Field layout for speed; this
72574         // is safe because label alignment/width/pad are not expected to change.
72575         if (labelAlign === 'top') {
72576             labelStyle = 'margin-bottom:' + labelPad + 'px;';
72577         } else {
72578             labelStyle = 'margin-right:' + labelPad + 'px;';
72579             // Add the width for border-box browsers; will be set by the Field layout for content-box
72580             if (Ext.isBorderBox) {
72581                 labelStyle += 'width:' + me.labelWidth + 'px;';
72582             }
72583         }
72584
72585         return Ext.copyTo(
72586             {
72587                 inputId: me.getInputId(),
72588                 fieldLabel: me.getFieldLabel(),
72589                 labelCls: labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls,
72590                 labelStyle: labelStyle + (me.labelStyle || ''),
72591                 subTplMarkup: me.getSubTplMarkup()
72592             },
72593             me,
72594             'hideLabel,hideEmptyLabel,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
72595             true
72596         );
72597     },
72598
72599     onLabelableRender: function () {
72600         this.addChildEls(
72601             /**
72602              * @property labelEl
72603              * @type Ext.Element
72604              * The label Element for this component. Only available after the component has been rendered.
72605              */
72606             'labelEl',
72607
72608             /**
72609              * @property bodyEl
72610              * @type Ext.Element
72611              * The div Element wrapping the component's contents. Only available after the component has been rendered.
72612              */
72613             'bodyEl',
72614
72615             /**
72616              * @property errorEl
72617              * @type Ext.Element
72618              * The div Element that will contain the component's error message(s). Note that depending on the
72619              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
72620              * presentation, but will always be present in the DOM for use by assistive technologies.
72621              */
72622             'errorEl'
72623         );
72624     },
72625
72626     /**
72627      * @protected
72628      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
72629      * be implemented by classes including this mixin as needed.
72630      * @return {String} The markup to be inserted
72631      */
72632     getSubTplMarkup: function() {
72633         return '';
72634     },
72635
72636     /**
72637      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
72638      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
72639      * @return {String} The input id
72640      */
72641     getInputId: function() {
72642         return '';
72643     },
72644
72645     /**
72646      * Gets the active error message for this component, if any. This does not trigger
72647      * validation on its own, it merely returns any message that the component may already hold.
72648      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
72649      */
72650     getActiveError : function() {
72651         return this.activeError || '';
72652     },
72653
72654     /**
72655      * Tells whether the field currently has an active error message. This does not trigger
72656      * validation on its own, it merely looks for any message that the component may already hold.
72657      * @return {Boolean}
72658      */
72659     hasActiveError: function() {
72660         return !!this.getActiveError();
72661     },
72662
72663     /**
72664      * Sets the active error message to the given string. This replaces the entire error message
72665      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
72666      * messages and formats them according to the {@link #activeErrorsTpl}.
72667      *
72668      * Note that this only updates the error message element's text and attributes, you'll have
72669      * to call doComponentLayout to actually update the field's layout to match. If the field extends
72670      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
72671      *
72672      * @param {String} msg The error message
72673      */
72674     setActiveError: function(msg) {
72675         this.activeError = msg;
72676         this.activeErrors = [msg];
72677         this.renderActiveError();
72678     },
72679
72680     /**
72681      * Gets an Array of any active error messages currently applied to the field. This does not trigger
72682      * validation on its own, it merely returns any messages that the component may already hold.
72683      * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is returned.
72684      */
72685     getActiveErrors: function() {
72686         return this.activeErrors || [];
72687     },
72688
72689     /**
72690      * Set the active error message to an Array of error messages. The messages are formatted into
72691      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
72692      * which allows setting the entire error contents with a single string.
72693      *
72694      * Note that this only updates the error message element's text and attributes, you'll have
72695      * to call doComponentLayout to actually update the field's layout to match. If the field extends
72696      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
72697      *
72698      * @param {String[]} errors The error messages
72699      */
72700     setActiveErrors: function(errors) {
72701         this.activeErrors = errors;
72702         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
72703         this.renderActiveError();
72704     },
72705
72706     /**
72707      * Clears the active error message(s).
72708      *
72709      * Note that this only clears the error message element's text and attributes, you'll have
72710      * to call doComponentLayout to actually update the field's layout to match. If the field extends
72711      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
72712      */
72713     unsetActiveError: function() {
72714         delete this.activeError;
72715         delete this.activeErrors;
72716         this.renderActiveError();
72717     },
72718
72719     /**
72720      * @private
72721      * Updates the rendered DOM to match the current activeError. This only updates the content and
72722      * attributes, you'll have to call doComponentLayout to actually update the display.
72723      */
72724     renderActiveError: function() {
72725         var me = this,
72726             activeError = me.getActiveError(),
72727             hasError = !!activeError;
72728
72729         if (activeError !== me.lastActiveError) {
72730             me.fireEvent('errorchange', me, activeError);
72731             me.lastActiveError = activeError;
72732         }
72733
72734         if (me.rendered && !me.isDestroyed && !me.preventMark) {
72735             // Add/remove invalid class
72736             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
72737
72738             // Update the aria-invalid attribute
72739             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
72740
72741             // Update the errorEl with the error message text
72742             me.errorEl.dom.innerHTML = activeError;
72743         }
72744     },
72745
72746     /**
72747      * Applies a set of default configuration values to this Labelable instance. For each of the
72748      * properties in the given object, check if this component hasOwnProperty that config; if not
72749      * then it's inheriting a default value from its prototype and we should apply the default value.
72750      * @param {Object} defaults The defaults to apply to the object.
72751      */
72752     setFieldDefaults: function(defaults) {
72753         var me = this;
72754         Ext.iterate(defaults, function(key, val) {
72755             if (!me.hasOwnProperty(key)) {
72756                 me[key] = val;
72757             }
72758         });
72759     },
72760
72761     /**
72762      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
72763      * Note for implementors: if at all possible this method should be overridden with a custom implementation
72764      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
72765      */
72766     getBodyNaturalWidth: function() {
72767         return this.bodyEl.getWidth();
72768     }
72769
72770 });
72771
72772 /**
72773  * @docauthor Jason Johnston <jason@sencha.com>
72774  *
72775  * This mixin provides a common interface for the logical behavior and state of form fields, including:
72776  *
72777  * - Getter and setter methods for field values
72778  * - Events and methods for tracking value and validity changes
72779  * - Methods for triggering validation
72780  *
72781  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
72782  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
72783  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
72784  * plus methods that bind the Field value getters and setters to the input field's value.
72785  *
72786  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
72787  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
72788  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
72789  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
72790  * is called during the component's initialization.
72791  */
72792 Ext.define('Ext.form.field.Field', {
72793     /**
72794      * @property {Boolean} isFormField
72795      * Flag denoting that this component is a Field. Always true.
72796      */
72797     isFormField : true,
72798
72799     /**
72800      * @cfg {Object} value
72801      * A value to initialize this field with.
72802      */
72803
72804     /**
72805      * @cfg {String} name
72806      * The name of the field. By default this is used as the parameter name when including the
72807      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
72808      * being included in the form submit, set {@link #submitValue} to false.
72809      */
72810
72811     /**
72812      * @cfg {Boolean} disabled
72813      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
72814      */
72815     disabled : false,
72816
72817     /**
72818      * @cfg {Boolean} submitValue
72819      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
72820      * not disabled.
72821      */
72822     submitValue: true,
72823
72824     /**
72825      * @cfg {Boolean} validateOnChange
72826      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
72827      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
72828      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
72829      * typing.
72830      *
72831      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
72832      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
72833      * manually.
72834      *
72835      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
72836      * detected.
72837      */
72838     validateOnChange: true,
72839
72840     /**
72841      * @private
72842      */
72843     suspendCheckChange: 0,
72844
72845     /**
72846      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
72847      * their own initialization process.
72848      */
72849     initField: function() {
72850         this.addEvents(
72851             /**
72852              * @event change
72853              * Fires when a user-initiated change is detected in the value of the field.
72854              * @param {Ext.form.field.Field} this
72855              * @param {Object} newValue The new value
72856              * @param {Object} oldValue The original value
72857              */
72858             'change',
72859             /**
72860              * @event validitychange
72861              * Fires when a change in the field's validity is detected.
72862              * @param {Ext.form.field.Field} this
72863              * @param {Boolean} isValid Whether or not the field is now valid
72864              */
72865             'validitychange',
72866             /**
72867              * @event dirtychange
72868              * Fires when a change in the field's {@link #isDirty} state is detected.
72869              * @param {Ext.form.field.Field} this
72870              * @param {Boolean} isDirty Whether or not the field is now dirty
72871              */
72872             'dirtychange'
72873         );
72874
72875         this.initValue();
72876     },
72877
72878     /**
72879      * Initializes the field's value based on the initial config.
72880      */
72881     initValue: function() {
72882         var me = this;
72883
72884         /**
72885          * @property {Object} originalValue
72886          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
72887          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
72888          */
72889         me.originalValue = me.lastValue = me.value;
72890
72891         // Set the initial value - prevent validation on initial set
72892         me.suspendCheckChange++;
72893         me.setValue(me.value);
72894         me.suspendCheckChange--;
72895     },
72896
72897     /**
72898      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
72899      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
72900      * @return {String} name The field {@link Ext.form.field.Field#name name}
72901      */
72902     getName: function() {
72903         return this.name;
72904     },
72905
72906     /**
72907      * Returns the current data value of the field. The type of value returned is particular to the type of the
72908      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
72909      * @return {Object} value The field value
72910      */
72911     getValue: function() {
72912         return this.value;
72913     },
72914
72915     /**
72916      * Sets a data value into the field and runs the change detection and validation.
72917      * @param {Object} value The value to set
72918      * @return {Ext.form.field.Field} this
72919      */
72920     setValue: function(value) {
72921         var me = this;
72922         me.value = value;
72923         me.checkChange();
72924         return me;
72925     },
72926
72927     /**
72928      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
72929      * to provide custom comparison logic appropriate for the particular field's data type.
72930      * @param {Object} value1 The first value to compare
72931      * @param {Object} value2 The second value to compare
72932      * @return {Boolean} True if the values are equal, false if inequal.
72933      */
72934     isEqual: function(value1, value2) {
72935         return String(value1) === String(value2);
72936     },
72937     
72938     /**
72939      * Returns whether two values are logically equal.
72940      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
72941      * @private
72942      * @param {Object} value1 The first value to compare
72943      * @param {Object} value2 The second value to compare
72944      * @return {Boolean} True if the values are equal, false if inequal.
72945      */
72946     isEqualAsString: function(value1, value2){
72947         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
72948     },
72949
72950     /**
72951      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
72952      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
72953      * its current stringified value. More advanced field implementations may return more than one name-value pair.
72954      *
72955      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
72956      * validated}.
72957      *
72958      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
72959      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
72960      * submitted.
72961      */
72962     getSubmitData: function() {
72963         var me = this,
72964             data = null;
72965         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
72966             data = {};
72967             data[me.getName()] = '' + me.getValue();
72968         }
72969         return data;
72970     },
72971
72972     /**
72973      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
72974      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
72975      * being this field's {@link #getName name} and the value being its current data value. More advanced field
72976      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
72977      * field names in the Model.
72978      *
72979      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
72980      * validated}.
72981      *
72982      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
72983      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
72984      * submitted.
72985      */
72986     getModelData: function() {
72987         var me = this,
72988             data = null;
72989         if (!me.disabled && !me.isFileUpload()) {
72990             data = {};
72991             data[me.getName()] = me.getValue();
72992         }
72993         return data;
72994     },
72995
72996     /**
72997      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
72998      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
72999      */
73000     reset : function(){
73001         var me = this;
73002
73003         me.setValue(me.originalValue);
73004         me.clearInvalid();
73005         // delete here so we reset back to the original state
73006         delete me.wasValid;
73007     },
73008
73009     /**
73010      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
73011      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
73012      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
73013      */
73014     resetOriginalValue: function() {
73015         this.originalValue = this.getValue();
73016         this.checkDirty();
73017     },
73018
73019     /**
73020      * Checks whether the value of the field has changed since the last time it was checked.
73021      * If the value has changed, it:
73022      *
73023      * 1. Fires the {@link #change change event},
73024      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
73025      *    {@link #validitychange validitychange event} if the validity has changed, and
73026      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
73027      *    if it has changed.
73028      */
73029     checkChange: function() {
73030         if (!this.suspendCheckChange) {
73031             var me = this,
73032                 newVal = me.getValue(),
73033                 oldVal = me.lastValue;
73034             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
73035                 me.lastValue = newVal;
73036                 me.fireEvent('change', me, newVal, oldVal);
73037                 me.onChange(newVal, oldVal);
73038             }
73039         }
73040     },
73041
73042     /**
73043      * @private
73044      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
73045      * config is enabled, and invokes the dirty check.
73046      */
73047     onChange: function(newVal, oldVal) {
73048         if (this.validateOnChange) {
73049             this.validate();
73050         }
73051         this.checkDirty();
73052     },
73053
73054     /**
73055      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
73056      * Will always return false if the field is disabled.
73057      *
73058      * Note that if the owning {@link Ext.form.Basic form} was configured with
73059      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
73060      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
73061      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
73062      * false otherwise.
73063      */
73064     isDirty : function() {
73065         var me = this;
73066         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
73067     },
73068
73069     /**
73070      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
73071      * fires the {@link #dirtychange} event.
73072      */
73073     checkDirty: function() {
73074         var me = this,
73075             isDirty = me.isDirty();
73076         if (isDirty !== me.wasDirty) {
73077             me.fireEvent('dirtychange', me, isDirty);
73078             me.onDirtyChange(isDirty);
73079             me.wasDirty = isDirty;
73080         }
73081     },
73082
73083     /**
73084      * @private Called when the field's dirty state changes.
73085      * @param {Boolean} isDirty
73086      */
73087     onDirtyChange: Ext.emptyFn,
73088
73089     /**
73090      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
73091      * internally during validation and would not usually need to be used manually.
73092      *
73093      * Each subclass should override or augment the return value to provide their own errors.
73094      *
73095      * @param {Object} value The value to get errors for (defaults to the current field value)
73096      * @return {String[]} All error messages for this field; an empty Array if none.
73097      */
73098     getErrors: function(value) {
73099         return [];
73100     },
73101
73102     /**
73103      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
73104      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
73105      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
73106      *
73107      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
73108      * message display.
73109      *
73110      * @return {Boolean} True if the value is valid, else false
73111      */
73112     isValid : function() {
73113         var me = this;
73114         return me.disabled || Ext.isEmpty(me.getErrors());
73115     },
73116
73117     /**
73118      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
73119      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
73120      * **Note**: {@link #disabled} fields are always treated as valid.
73121      *
73122      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
73123      * To validate without side-effects, use {@link #isValid}.
73124      *
73125      * @return {Boolean} True if the value is valid, else false
73126      */
73127     validate : function() {
73128         var me = this,
73129             isValid = me.isValid();
73130         if (isValid !== me.wasValid) {
73131             me.wasValid = isValid;
73132             me.fireEvent('validitychange', me, isValid);
73133         }
73134         return isValid;
73135     },
73136
73137     /**
73138      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
73139      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
73140      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
73141      * change.
73142      * @param {Object} fn A function containing the transaction code
73143      */
73144     batchChanges: function(fn) {
73145         try {
73146             this.suspendCheckChange++;
73147             fn();
73148         } catch(e){
73149             throw e;
73150         } finally {
73151             this.suspendCheckChange--;
73152         }
73153         this.checkChange();
73154     },
73155
73156     /**
73157      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
73158      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
73159      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
73160      * input element.
73161      * @return {Boolean}
73162      */
73163     isFileUpload: function() {
73164         return false;
73165     },
73166
73167     /**
73168      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
73169      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
73170      * returned, so this method should also create a replacement.
73171      * @return {HTMLElement}
73172      */
73173     extractFileInput: function() {
73174         return null;
73175     },
73176
73177     /**
73178      * @method markInvalid
73179      * Associate one or more error messages with this field. Components using this mixin should implement this method to
73180      * update the component's rendering to display the messages.
73181      *
73182      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
73183      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
73184      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
73185      *
73186      * @param {String/String[]} errors The error message(s) for the field.
73187      */
73188     markInvalid: Ext.emptyFn,
73189
73190     /**
73191      * @method clearInvalid
73192      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
73193      * update the components rendering to clear any existing messages.
73194      *
73195      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
73196      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
73197      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
73198      */
73199     clearInvalid: Ext.emptyFn
73200
73201 });
73202
73203 /**
73204  * @class Ext.layout.component.field.Field
73205  * @extends Ext.layout.component.Component
73206  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
73207  * the form control, label, and error message treatment.
73208  * @private
73209  */
73210 Ext.define('Ext.layout.component.field.Field', {
73211
73212     /* Begin Definitions */
73213
73214     alias: ['layout.field'],
73215
73216     extend: 'Ext.layout.component.Component',
73217
73218     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
73219
73220     /* End Definitions */
73221
73222     type: 'field',
73223
73224     beforeLayout: function(width, height) {
73225         var me = this;
73226         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
73227     },
73228
73229     onLayout: function(width, height) {
73230         var me = this,
73231             owner = me.owner,
73232             labelStrategy = me.getLabelStrategy(),
73233             errorStrategy = me.getErrorStrategy(),
73234             isDefined = Ext.isDefined,
73235             isNumber = Ext.isNumber,
73236             lastSize, autoWidth, autoHeight, info, undef;
73237
73238         lastSize = me.lastComponentSize || {};
73239         if (!isDefined(width)) {
73240             width = lastSize.width;
73241             if (width < 0) { //first pass lastComponentSize.width is -Infinity
73242                 width = undef;
73243             }
73244         }
73245         if (!isDefined(height)) {
73246             height = lastSize.height;
73247             if (height < 0) { //first pass lastComponentSize.height is -Infinity
73248                 height = undef;
73249             }
73250         }
73251         autoWidth = !isNumber(width);
73252         autoHeight = !isNumber(height);
73253
73254         info = {
73255             autoWidth: autoWidth,
73256             autoHeight: autoHeight,
73257             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
73258             height: height,
73259             setOuterWidth: false, //whether the outer el width should be set to the calculated width
73260
73261             // insets for the bodyEl from each side of the component layout area
73262             insets: {
73263                 top: 0,
73264                 right: 0,
73265                 bottom: 0,
73266                 left: 0
73267             }
73268         };
73269
73270         // NOTE the order of calculating insets and setting styles here is very important; we must first
73271         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
73272         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
73273
73274         // perform preparation on the label and error (setting css classes, qtips, etc.)
73275         labelStrategy.prepare(owner, info);
73276         errorStrategy.prepare(owner, info);
73277
73278         // calculate the horizontal insets for the label and error
73279         labelStrategy.adjustHorizInsets(owner, info);
73280         errorStrategy.adjustHorizInsets(owner, info);
73281
73282         // set horizontal styles for label and error based on the current insets
73283         labelStrategy.layoutHoriz(owner, info);
73284         errorStrategy.layoutHoriz(owner, info);
73285
73286         // calculate the vertical insets for the label and error
73287         labelStrategy.adjustVertInsets(owner, info);
73288         errorStrategy.adjustVertInsets(owner, info);
73289
73290         // set vertical styles for label and error based on the current insets
73291         labelStrategy.layoutVert(owner, info);
73292         errorStrategy.layoutVert(owner, info);
73293
73294         // perform sizing of the elements based on the final dimensions and insets
73295         if (autoWidth && autoHeight) {
73296             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
73297             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
73298         } else {
73299             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
73300         }
73301         me.sizeBody(info);
73302
73303         me.activeError = owner.getActiveError();
73304     },
73305     
73306     onFocus: function(){
73307         this.getErrorStrategy().onFocus(this.owner);    
73308     },
73309
73310
73311     /**
73312      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
73313      */
73314     sizeBody: function(info) {
73315         var me = this,
73316             owner = me.owner,
73317             insets = info.insets,
73318             totalWidth = info.width,
73319             totalHeight = info.height,
73320             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
73321             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
73322
73323         // size the bodyEl
73324         me.setElementSize(owner.bodyEl, width, height);
73325
73326         // size the bodyEl's inner contents if necessary
73327         me.sizeBodyContents(width, height);
73328     },
73329
73330     /**
73331      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
73332      * default, subclasses can override to handle their specific contents.
73333      * @param {Number} width The bodyEl width
73334      * @param {Number} height The bodyEl height
73335      */
73336     sizeBodyContents: Ext.emptyFn,
73337
73338
73339     /**
73340      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
73341      * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
73342      */
73343     getLabelStrategy: function() {
73344         var me = this,
73345             strategies = me.labelStrategies,
73346             labelAlign = me.owner.labelAlign;
73347         return strategies[labelAlign] || strategies.base;
73348     },
73349
73350     /**
73351      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
73352      * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
73353      */
73354     getErrorStrategy: function() {
73355         var me = this,
73356             owner = me.owner,
73357             strategies = me.errorStrategies,
73358             msgTarget = owner.msgTarget;
73359         return !owner.preventMark && Ext.isString(msgTarget) ?
73360                 (strategies[msgTarget] || strategies.elementId) :
73361                 strategies.none;
73362     },
73363
73364
73365
73366     /**
73367      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
73368      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
73369      */
73370     labelStrategies: (function() {
73371         var applyIf = Ext.applyIf,
73372             emptyFn = Ext.emptyFn,
73373             base = {
73374                 prepare: function(owner, info) {
73375                     var cls = owner.labelCls + '-' + owner.labelAlign,
73376                         labelEl = owner.labelEl;
73377                     if (labelEl && !labelEl.hasCls(cls)) {
73378                         labelEl.addCls(cls);
73379                     }
73380                 },
73381                 adjustHorizInsets: emptyFn,
73382                 adjustVertInsets: emptyFn,
73383                 layoutHoriz: emptyFn,
73384                 layoutVert: emptyFn
73385             },
73386             left = applyIf({
73387                 prepare: function(owner, info) {
73388                     base.prepare(owner, info);
73389                     // If auto width, add the label width to the body's natural width.
73390                     if (info.autoWidth) {
73391                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
73392                     }
73393                     // Must set outer width to prevent field from wrapping below floated label
73394                     info.setOuterWidth = true;
73395                 },
73396                 adjustHorizInsets: function(owner, info) {
73397                     if (owner.labelEl) {
73398                         info.insets.left += owner.labelWidth + owner.labelPad;
73399                     }
73400                 },
73401                 layoutHoriz: function(owner, info) {
73402                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
73403                     // setting the width style because it needs to account for the final calculated
73404                     // padding/border styles for the label. So we set the width programmatically here to
73405                     // normalize content-box sizing, while letting border-box browsers use the original
73406                     // width style.
73407                     var labelEl = owner.labelEl;
73408                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
73409                         labelEl.setWidth(owner.labelWidth);
73410                         owner.isLabelSized = true;
73411                     }
73412                 }
73413             }, base);
73414
73415
73416         return {
73417             base: base,
73418
73419             /**
73420              * Label displayed above the bodyEl
73421              */
73422             top: applyIf({
73423                 adjustVertInsets: function(owner, info) {
73424                     var labelEl = owner.labelEl;
73425                     if (labelEl) {
73426                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
73427                                            labelEl.getFrameWidth('tb') + owner.labelPad;
73428                     }
73429                 }
73430             }, base),
73431
73432             /**
73433              * Label displayed to the left of the bodyEl
73434              */
73435             left: left,
73436
73437             /**
73438              * Same as left, only difference is text-align in CSS
73439              */
73440             right: left
73441         };
73442     })(),
73443
73444
73445
73446     /**
73447      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
73448      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
73449      */
73450     errorStrategies: (function() {
73451         function setDisplayed(el, displayed) {
73452             var wasDisplayed = el.getStyle('display') !== 'none';
73453             if (displayed !== wasDisplayed) {
73454                 el.setDisplayed(displayed);
73455             }
73456         }
73457
73458         function setStyle(el, name, value) {
73459             if (el.getStyle(name) !== value) {
73460                 el.setStyle(name, value);
73461             }
73462         }
73463         
73464         function showTip(owner) {
73465             var tip = Ext.layout.component.field.Field.tip,
73466                 target;
73467                 
73468             if (tip && tip.isVisible()) {
73469                 target = tip.activeTarget;
73470                 if (target && target.el === owner.getActionEl().dom) {
73471                     tip.toFront(true);
73472                 }
73473             }
73474         }
73475
73476         var applyIf = Ext.applyIf,
73477             emptyFn = Ext.emptyFn,
73478             base = {
73479                 prepare: function(owner) {
73480                     setDisplayed(owner.errorEl, false);
73481                 },
73482                 adjustHorizInsets: emptyFn,
73483                 adjustVertInsets: emptyFn,
73484                 layoutHoriz: emptyFn,
73485                 layoutVert: emptyFn,
73486                 onFocus: emptyFn
73487             };
73488
73489         return {
73490             none: base,
73491
73492             /**
73493              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
73494              */
73495             side: applyIf({
73496                 prepare: function(owner) {
73497                     var errorEl = owner.errorEl;
73498                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
73499                     Ext.layout.component.field.Field.initTip();
73500                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
73501                     setDisplayed(errorEl, owner.hasActiveError());
73502                 },
73503                 adjustHorizInsets: function(owner, info) {
73504                     if (owner.autoFitErrors && owner.hasActiveError()) {
73505                         info.insets.right += owner.errorEl.getWidth();
73506                     }
73507                 },
73508                 layoutHoriz: function(owner, info) {
73509                     if (owner.hasActiveError()) {
73510                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
73511                     }
73512                 },
73513                 layoutVert: function(owner, info) {
73514                     if (owner.hasActiveError()) {
73515                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
73516                     }
73517                 },
73518                 onFocus: showTip
73519             }, base),
73520
73521             /**
73522              * Error message displayed underneath the bodyEl
73523              */
73524             under: applyIf({
73525                 prepare: function(owner) {
73526                     var errorEl = owner.errorEl,
73527                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
73528                     if (!errorEl.hasCls(cls)) {
73529                         errorEl.addCls(cls);
73530                     }
73531                     setDisplayed(errorEl, owner.hasActiveError());
73532                 },
73533                 adjustVertInsets: function(owner, info) {
73534                     if (owner.autoFitErrors) {
73535                         info.insets.bottom += owner.errorEl.getHeight();
73536                     }
73537                 },
73538                 layoutHoriz: function(owner, info) {
73539                     var errorEl = owner.errorEl,
73540                         insets = info.insets;
73541
73542                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
73543                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
73544                 }
73545             }, base),
73546
73547             /**
73548              * Error displayed as QuickTip on hover of the field container
73549              */
73550             qtip: applyIf({
73551                 prepare: function(owner) {
73552                     setDisplayed(owner.errorEl, false);
73553                     Ext.layout.component.field.Field.initTip();
73554                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
73555                 },
73556                 onFocus: showTip
73557             }, base),
73558
73559             /**
73560              * Error displayed as title tip on hover of the field container
73561              */
73562             title: applyIf({
73563                 prepare: function(owner) {
73564                     setDisplayed(owner.errorEl, false);
73565                     owner.el.dom.title = owner.getActiveError() || '';
73566                 }
73567             }, base),
73568
73569             /**
73570              * Error message displayed as content of an element with a given id elsewhere in the app
73571              */
73572             elementId: applyIf({
73573                 prepare: function(owner) {
73574                     setDisplayed(owner.errorEl, false);
73575                     var targetEl = Ext.fly(owner.msgTarget);
73576                     if (targetEl) {
73577                         targetEl.dom.innerHTML = owner.getActiveError() || '';
73578                         targetEl.setDisplayed(owner.hasActiveError());
73579                     }
73580                 }
73581             }, base)
73582         };
73583     })(),
73584
73585     statics: {
73586         /**
73587          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
73588          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
73589          */
73590         initTip: function() {
73591             var tip = this.tip;
73592             if (!tip) {
73593                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
73594                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
73595                     renderTo: Ext.getBody()
73596                 });
73597                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
73598             }
73599         },
73600
73601         /**
73602          * Destroy the error tip instance.
73603          */
73604         destroyTip: function() {
73605             var tip = this.tip;
73606             if (tip) {
73607                 tip.destroy();
73608                 delete this.tip;
73609             }
73610         }
73611     }
73612
73613 });
73614
73615 /**
73616  * @singleton
73617  * @alternateClassName Ext.form.VTypes
73618  *
73619  * This is a singleton object which contains a set of commonly used field validation functions
73620  * and provides a mechanism for creating reusable custom field validations.
73621  * The following field validation functions are provided out of the box:
73622  *
73623  * - {@link #alpha}
73624  * - {@link #alphanum}
73625  * - {@link #email}
73626  * - {@link #url}
73627  *
73628  * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
73629  *
73630  *     Ext.create('Ext.form.field.Text', {
73631  *         fieldLabel: 'Email Address',
73632  *         name: 'email',
73633  *         vtype: 'email' // applies email validation rules to this field
73634  *     });
73635  *
73636  * To create custom VTypes:
73637  *
73638  *     // custom Vtype for vtype:'time'
73639  *     var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
73640  *     Ext.apply(Ext.form.field.VTypes, {
73641  *         //  vtype validation function
73642  *         time: function(val, field) {
73643  *             return timeTest.test(val);
73644  *         },
73645  *         // vtype Text property: The error text to display when the validation function returns false
73646  *         timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
73647  *         // vtype Mask property: The keystroke filter mask
73648  *         timeMask: /[\d\s:amp]/i
73649  *     });
73650  *
73651  * In the above example the `time` function is the validator that will run when field validation occurs,
73652  * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
73653  * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
73654  *
73655  * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
73656  * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
73657  *
73658  *     Ext.create('Ext.form.field.Text', {
73659  *         fieldLabel: 'Departure Time',
73660  *         name: 'departureTime',
73661  *         vtype: 'time' // applies custom time validation rules to this field
73662  *     });
73663  *
73664  * Another example of a custom validator:
73665  *
73666  *     // custom Vtype for vtype:'IPAddress'
73667  *     Ext.apply(Ext.form.field.VTypes, {
73668  *         IPAddress:  function(v) {
73669  *             return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
73670  *         },
73671  *         IPAddressText: 'Must be a numeric IP address',
73672  *         IPAddressMask: /[\d\.]/i
73673  *     });
73674  *
73675  * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
73676  * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
73677  */
73678 Ext.define('Ext.form.field.VTypes', (function(){
73679     // closure these in so they are only created once.
73680     var alpha = /^[a-zA-Z_]+$/,
73681         alphanum = /^[a-zA-Z0-9_]+$/,
73682         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
73683         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
73684
73685     // All these messages and functions are configurable
73686     return {
73687         singleton: true,
73688         alternateClassName: 'Ext.form.VTypes',
73689
73690         /**
73691          * The function used to validate email addresses. Note that this is a very basic validation - complete
73692          * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
73693          * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
73694          * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
73695          * to validate the following emails:
73696          *
73697          * - `barney@example.de`
73698          * - `barney.rubble@example.com`
73699          * - `barney-rubble@example.coop`
73700          * - `barney+rubble@example.com`
73701          *
73702          * [1]: http://en.wikipedia.org/wiki/E-mail_address
73703          *
73704          * @param {String} value The email address
73705          * @return {Boolean} true if the RegExp test passed, and false if not.
73706          */
73707         'email' : function(v){
73708             return email.test(v);
73709         },
73710         /**
73711          * @property {String} emailText
73712          * The error text to display when the email validation function returns false.
73713          * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
73714          */
73715         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
73716         /**
73717          * @property {RegExp} emailMask
73718          * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
73719          * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
73720          */
73721         'emailMask' : /[a-z0-9_\.\-@\+]/i,
73722
73723         /**
73724          * The function used to validate URLs
73725          * @param {String} value The URL
73726          * @return {Boolean} true if the RegExp test passed, and false if not.
73727          */
73728         'url' : function(v){
73729             return url.test(v);
73730         },
73731         /**
73732          * @property {String} urlText
73733          * The error text to display when the url validation function returns false.
73734          * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
73735          */
73736         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
73737
73738         /**
73739          * The function used to validate alpha values
73740          * @param {String} value The value
73741          * @return {Boolean} true if the RegExp test passed, and false if not.
73742          */
73743         'alpha' : function(v){
73744             return alpha.test(v);
73745         },
73746         /**
73747          * @property {String} alphaText
73748          * The error text to display when the alpha validation function returns false.
73749          * Defaults to: 'This field should only contain letters and _'
73750          */
73751         'alphaText' : 'This field should only contain letters and _',
73752         /**
73753          * @property {RegExp} alphaMask
73754          * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
73755          */
73756         'alphaMask' : /[a-z_]/i,
73757
73758         /**
73759          * The function used to validate alphanumeric values
73760          * @param {String} value The value
73761          * @return {Boolean} true if the RegExp test passed, and false if not.
73762          */
73763         'alphanum' : function(v){
73764             return alphanum.test(v);
73765         },
73766         /**
73767          * @property {String} alphanumText
73768          * The error text to display when the alphanumeric validation function returns false.
73769          * Defaults to: 'This field should only contain letters, numbers and _'
73770          */
73771         'alphanumText' : 'This field should only contain letters, numbers and _',
73772         /**
73773          * @property {RegExp} alphanumMask
73774          * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
73775          */
73776         'alphanumMask' : /[a-z0-9_]/i
73777     };
73778 })());
73779
73780 /**
73781  * @private
73782  * @class Ext.layout.component.field.Text
73783  * @extends Ext.layout.component.field.Field
73784  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
73785  */
73786 Ext.define('Ext.layout.component.field.Text', {
73787     extend: 'Ext.layout.component.field.Field',
73788     alias: 'layout.textfield',
73789     requires: ['Ext.util.TextMetrics'],
73790
73791     type: 'textfield',
73792
73793
73794     /**
73795      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
73796      * changed since the last layout.
73797      */
73798     beforeLayout: function(width, height) {
73799         var me = this,
73800             owner = me.owner,
73801             lastValue = this.lastValue,
73802             value = owner.getRawValue();
73803         this.lastValue = value;
73804         return me.callParent(arguments) || (owner.grow && value !== lastValue);
73805     },
73806
73807
73808     /**
73809      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
73810      * {@link Ext.form.field.Text#grow} configurations.
73811      * @param {Number} width The bodyEl width
73812      * @param {Number} height The bodyEl height
73813      */
73814     sizeBodyContents: function(width, height) {
73815         var size = this.adjustForGrow(width, height);
73816         this.setElementSize(this.owner.inputEl, size[0], size[1]);
73817     },
73818
73819
73820     /**
73821      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
73822      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
73823      * @param {Number} width The bodyEl width
73824      * @param {Number} height The bodyEl height
73825      * @return {Number[]} [inputElWidth, inputElHeight]
73826      */
73827     adjustForGrow: function(width, height) {
73828         var me = this,
73829             owner = me.owner,
73830             inputEl, value, calcWidth,
73831             result = [width, height];
73832
73833         if (owner.grow) {
73834             inputEl = owner.inputEl;
73835
73836             // Find the width that contains the whole text value
73837             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
73838             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
73839
73840             // Constrain
73841             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
73842                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
73843         }
73844
73845         return result;
73846     }
73847
73848 });
73849
73850 /**
73851  * @private
73852  * @class Ext.layout.component.field.TextArea
73853  * @extends Ext.layout.component.field.Field
73854  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
73855  */
73856 Ext.define('Ext.layout.component.field.TextArea', {
73857     extend: 'Ext.layout.component.field.Text',
73858     alias: 'layout.textareafield',
73859
73860     type: 'textareafield',
73861
73862
73863     /**
73864      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
73865      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
73866      * textfield layout's implementation to handle height rather than width.
73867      * @param {Number} width The bodyEl width
73868      * @param {Number} height The bodyEl height
73869      * @return {Number[]} [inputElWidth, inputElHeight]
73870      */
73871     adjustForGrow: function(width, height) {
73872         var me = this,
73873             owner = me.owner,
73874             inputEl, value, max,
73875             curWidth, curHeight, calcHeight,
73876             result = [width, height];
73877
73878         if (owner.grow) {
73879             inputEl = owner.inputEl;
73880             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
73881             curHeight = inputEl.getHeight();
73882
73883             // Get and normalize the field value for measurement
73884             value = inputEl.dom.value || '&#160;';
73885             value += owner.growAppend;
73886
73887             // Translate newlines to <br> tags
73888             value = value.replace(/\n/g, '<br>');
73889
73890             // Find the height that contains the whole text value
73891             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
73892                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
73893
73894             // Constrain
73895             max = owner.growMax;
73896             if (Ext.isNumber(height)) {
73897                 max = Math.min(max, height);
73898             }
73899             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
73900         }
73901
73902         return result;
73903     }
73904
73905 });
73906 /**
73907  * @class Ext.layout.container.Anchor
73908  * @extends Ext.layout.container.Container
73909  * 
73910  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
73911  * If the container is resized, all anchored items are automatically rerendered according to their
73912  * `{@link #anchor}` rules.
73913  *
73914  * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor' 
73915  * config, and should generally not need to be created directly via the new keyword.
73916  * 
73917  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
73918  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
73919  * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
73920  *
73921  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
73922  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
73923  * logic if necessary.  
73924  *
73925  *     @example
73926  *     Ext.create('Ext.Panel', {
73927  *         width: 500,
73928  *         height: 400,
73929  *         title: "AnchorLayout Panel",
73930  *         layout: 'anchor',
73931  *         renderTo: Ext.getBody(),
73932  *         items: [
73933  *             {
73934  *                 xtype: 'panel',
73935  *                 title: '75% Width and 20% Height',
73936  *                 anchor: '75% 20%'
73937  *             },
73938  *             {
73939  *                 xtype: 'panel',
73940  *                 title: 'Offset -300 Width & -200 Height',
73941  *                 anchor: '-300 -200'          
73942  *             },
73943  *             {
73944  *                 xtype: 'panel',
73945  *                 title: 'Mixed Offset and Percent',
73946  *                 anchor: '-250 20%'
73947  *             }
73948  *         ]
73949  *     });
73950  */
73951 Ext.define('Ext.layout.container.Anchor', {
73952
73953     /* Begin Definitions */
73954
73955     alias: 'layout.anchor',
73956     extend: 'Ext.layout.container.Container',
73957     alternateClassName: 'Ext.layout.AnchorLayout',
73958
73959     /* End Definitions */
73960
73961     /**
73962      * @cfg {String} anchor
73963      *
73964      * This configuation option is to be applied to **child `items`** of a container managed by
73965      * this layout (ie. configured with `layout:'anchor'`).
73966      *
73967      * This value is what tells the layout how an item should be anchored to the container. `items`
73968      * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
73969      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
73970      * The following types of anchor values are supported:
73971      *
73972      * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
73973      *
73974      *   The first anchor is the percentage width that the item should take up within the container, and the
73975      *   second is the percentage height.  For example:
73976      *
73977      *       // two values specified
73978      *       anchor: '100% 50%' // render item complete width of the container and
73979      *                          // 1/2 height of the container
73980      *       // one value specified
73981      *       anchor: '100%'     // the width value; the height will default to auto
73982      *
73983      * - **Offsets** : Any positive or negative integer value.
73984      *
73985      *   This is a raw adjustment where the first anchor is the offset from the right edge of the container,
73986      *   and the second is the offset from the bottom edge. For example:
73987      *
73988      *       // two values specified
73989      *       anchor: '-50 -100' // render item the complete width of the container
73990      *                          // minus 50 pixels and
73991      *                          // the complete height minus 100 pixels.
73992      *       // one value specified
73993      *       anchor: '-50'      // anchor value is assumed to be the right offset value
73994      *                          // bottom offset will default to 0
73995      *
73996      * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
73997      *
73998      *   Either the container must have a fixed size or an anchorSize config value defined at render time in
73999      *   order for these to have any effect.
74000      *   
74001      * - **Mixed** :
74002      *
74003      *   Anchor values can also be mixed as needed.  For example, to render the width offset from the container
74004      *   right edge by 50 pixels and 75% of the container's height use:
74005      *   
74006      *       anchor:   '-50 75%'
74007      */
74008     type: 'anchor',
74009
74010     /**
74011      * @cfg {String} defaultAnchor
74012      * 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%'.
74013      */
74014     defaultAnchor: '100%',
74015
74016     parseAnchorRE: /^(r|right|b|bottom)$/i,
74017
74018     // private
74019     onLayout: function() {
74020         this.callParent(arguments);
74021
74022         var me = this,
74023             size = me.getLayoutTargetSize(),
74024             owner = me.owner,
74025             target = me.getTarget(),
74026             ownerWidth = size.width,
74027             ownerHeight = size.height,
74028             overflow = target.getStyle('overflow'),
74029             components = me.getVisibleItems(owner),
74030             len = components.length,
74031             boxes = [],
74032             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
74033             i, el, cleaner;
74034
74035         if (ownerWidth < 20 && ownerHeight < 20) {
74036             return;
74037         }
74038
74039         // Anchor layout uses natural HTML flow to arrange the child items.
74040         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
74041         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
74042         if (!me.clearEl) {
74043             me.clearEl = target.createChild({
74044                 cls: Ext.baseCSSPrefix + 'clear',
74045                 role: 'presentation'
74046             });
74047         }
74048
74049         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
74050         if (!Ext.supports.RightMargin) {
74051             cleaner = Ext.Element.getRightMarginFixCleaner(target);
74052             target.addCls(Ext.baseCSSPrefix + 'inline-children');
74053         }
74054
74055         for (i = 0; i < len; i++) {
74056             component = components[i];
74057             el = component.el;
74058
74059             anchorSpec = component.anchorSpec;
74060             if (anchorSpec) {
74061                 if (anchorSpec.right) {
74062                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
74063                 } else {
74064                     calcWidth = undefined;
74065                 }
74066                 if (anchorSpec.bottom) {
74067                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
74068                 } else {
74069                     calcHeight = undefined;
74070                 }
74071
74072                 boxes.push({
74073                     component: component,
74074                     anchor: true,
74075                     width: calcWidth || undefined,
74076                     height: calcHeight || undefined
74077                 });
74078             } else {
74079                 boxes.push({
74080                     component: component,
74081                     anchor: false
74082                 });
74083             }
74084         }
74085
74086         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
74087         if (!Ext.supports.RightMargin) {
74088             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
74089             cleaner();
74090         }
74091
74092         for (i = 0; i < len; i++) {
74093             box = boxes[i];
74094             me.setItemSize(box.component, box.width, box.height);
74095         }
74096
74097         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
74098             newTargetSize = me.getLayoutTargetSize();
74099             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
74100                 me.adjustmentPass = true;
74101                 me.onLayout();
74102             }
74103         }
74104
74105         delete me.adjustmentPass;
74106     },
74107
74108     // private
74109     parseAnchor: function(a, start, cstart) {
74110         if (a && a != 'none') {
74111             var ratio;
74112             // standard anchor
74113             if (this.parseAnchorRE.test(a)) {
74114                 var diff = cstart - start;
74115                 return function(v) {
74116                     return v - diff;
74117                 };
74118             }    
74119             // percentage
74120             else if (a.indexOf('%') != -1) {
74121                 ratio = parseFloat(a.replace('%', '')) * 0.01;
74122                 return function(v) {
74123                     return Math.floor(v * ratio);
74124                 };
74125             }    
74126             // simple offset adjustment
74127             else {
74128                 a = parseInt(a, 10);
74129                 if (!isNaN(a)) {
74130                     return function(v) {
74131                         return v + a;
74132                     };
74133                 }
74134             }
74135         }
74136         return null;
74137     },
74138
74139     // private
74140     adjustWidthAnchor: function(value, comp) {
74141         return value;
74142     },
74143
74144     // private
74145     adjustHeightAnchor: function(value, comp) {
74146         return value;
74147     },
74148
74149     configureItem: function(item) {
74150         var me = this,
74151             owner = me.owner,
74152             anchor= item.anchor,
74153             anchorsArray,
74154             anchorSpec,
74155             anchorWidth,
74156             anchorHeight;
74157
74158         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
74159             item.anchor = anchor = me.defaultAnchor;
74160         }
74161
74162         // find the container anchoring size
74163         if (owner.anchorSize) {
74164             if (typeof owner.anchorSize == 'number') {
74165                 anchorWidth = owner.anchorSize;
74166             }
74167             else {
74168                 anchorWidth = owner.anchorSize.width;
74169                 anchorHeight = owner.anchorSize.height;
74170             }
74171         }
74172         else {
74173             anchorWidth = owner.initialConfig.width;
74174             anchorHeight = owner.initialConfig.height;
74175         }
74176
74177         if (anchor) {
74178             // cache all anchor values
74179             anchorsArray = anchor.split(' ');
74180             item.anchorSpec = anchorSpec = {
74181                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
74182                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
74183             };
74184
74185             if (anchorSpec.right) {
74186                 item.layoutManagedWidth = 1;
74187             } else {
74188                 item.layoutManagedWidth = 2;
74189             }
74190
74191             if (anchorSpec.bottom) {
74192                 item.layoutManagedHeight = 1;
74193             } else {
74194                 item.layoutManagedHeight = 2;
74195             }
74196         } else {
74197             item.layoutManagedWidth = 2;
74198             item.layoutManagedHeight = 2;
74199         }
74200         this.callParent(arguments);
74201     }
74202
74203 });
74204 /**
74205  * @class Ext.form.action.Load
74206  * @extends Ext.form.action.Action
74207  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
74208  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
74209  * {@link Ext.form.Basic#load load}ing.</p>
74210  * <p><u><b>Response Packet Criteria</b></u></p>
74211  * <p>A response packet <b>must</b> contain:
74212  * <div class="mdetail-params"><ul>
74213  * <li><b><code>success</code></b> property : Boolean</li>
74214  * <li><b><code>data</code></b> property : Object</li>
74215  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
74216  * The individual value object for each Field is passed to the Field's
74217  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
74218  * </ul></div>
74219  * <p><u><b>JSON Packets</b></u></p>
74220  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
74221 var myFormPanel = new Ext.form.Panel({
74222     title: 'Client and routing info',
74223     items: [{
74224         fieldLabel: 'Client',
74225         name: 'clientName'
74226     }, {
74227         fieldLabel: 'Port of loading',
74228         name: 'portOfLoading'
74229     }, {
74230         fieldLabel: 'Port of discharge',
74231         name: 'portOfDischarge'
74232     }]
74233 });
74234 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
74235     url: '/getRoutingInfo.php',
74236     params: {
74237         consignmentRef: myConsignmentRef
74238     },
74239     failure: function(form, action) {
74240         Ext.Msg.alert("Load failed", action.result.errorMessage);
74241     }
74242 });
74243 </code></pre>
74244  * a <b>success response</b> packet may look like this:</p><pre><code>
74245 {
74246     success: true,
74247     data: {
74248         clientName: "Fred. Olsen Lines",
74249         portOfLoading: "FXT",
74250         portOfDischarge: "OSL"
74251     }
74252 }</code></pre>
74253  * while a <b>failure response</b> packet may look like this:</p><pre><code>
74254 {
74255     success: false,
74256     errorMessage: "Consignment reference not found"
74257 }</code></pre>
74258  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
74259  * callback or event handler methods. The object decoded from this JSON is available in the
74260  * {@link Ext.form.action.Action#result result} property.</p>
74261  */
74262 Ext.define('Ext.form.action.Load', {
74263     extend:'Ext.form.action.Action',
74264     requires: ['Ext.data.Connection'],
74265     alternateClassName: 'Ext.form.Action.Load',
74266     alias: 'formaction.load',
74267
74268     type: 'load',
74269
74270     /**
74271      * @private
74272      */
74273     run: function() {
74274         Ext.Ajax.request(Ext.apply(
74275             this.createCallback(),
74276             {
74277                 method: this.getMethod(),
74278                 url: this.getUrl(),
74279                 headers: this.headers,
74280                 params: this.getParams()
74281             }
74282         ));
74283     },
74284
74285     /**
74286      * @private
74287      */
74288     onSuccess: function(response){
74289         var result = this.processResponse(response),
74290             form = this.form;
74291         if (result === true || !result.success || !result.data) {
74292             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
74293             form.afterAction(this, false);
74294             return;
74295         }
74296         form.clearInvalid();
74297         form.setValues(result.data);
74298         form.afterAction(this, true);
74299     },
74300
74301     /**
74302      * @private
74303      */
74304     handleResponse: function(response) {
74305         var reader = this.form.reader,
74306             rs, data;
74307         if (reader) {
74308             rs = reader.read(response);
74309             data = rs.records && rs.records[0] ? rs.records[0].data : null;
74310             return {
74311                 success : rs.success,
74312                 data : data
74313             };
74314         }
74315         return Ext.decode(response.responseText);
74316     }
74317 });
74318
74319
74320 /**
74321  * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
74322  * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
74323  * their prior size, and can be {@link #minimize}d.
74324  *
74325  * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
74326  * grouping, activation, to front, to back and other application-specific behavior.
74327  *
74328  * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
74329  * {@link Ext.Component#renderTo renderTo}.
74330  *
74331  * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
74332  * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
74333  * in the required manner.**
74334  *
74335  *     @example
74336  *     Ext.create('Ext.window.Window', {
74337  *         title: 'Hello',
74338  *         height: 200,
74339  *         width: 400,
74340  *         layout: 'fit',
74341  *         items: {  // Let's put an empty grid in just to illustrate fit layout
74342  *             xtype: 'grid',
74343  *             border: false,
74344  *             columns: [{header: 'World'}],                 // One header just for show. There's no data,
74345  *             store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
74346  *         }
74347  *     }).show();
74348  */
74349 Ext.define('Ext.window.Window', {
74350     extend: 'Ext.panel.Panel',
74351
74352     alternateClassName: 'Ext.Window',
74353
74354     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
74355
74356     alias: 'widget.window',
74357
74358     /**
74359      * @cfg {Number} x
74360      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
74361      * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
74362      */
74363
74364     /**
74365      * @cfg {Number} y
74366      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
74367      * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
74368      */
74369
74370     /**
74371      * @cfg {Boolean} [modal=false]
74372      * True to make the window modal and mask everything behind it when displayed, false to display it without
74373      * restricting access to other UI elements.
74374      */
74375
74376     /**
74377      * @cfg {String/Ext.Element} [animateTarget=null]
74378      * Id or element from which the window should animate while opening.
74379      */
74380
74381     /**
74382      * @cfg {String/Number/Ext.Component} defaultFocus
74383      * Specifies a Component to receive focus when this Window is focused.
74384      *
74385      * This may be one of:
74386      *
74387      *   - The index of a footer Button.
74388      *   - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
74389      *   - A Component.
74390      */
74391
74392     /**
74393      * @cfg {Function} onEsc
74394      * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
74395      * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
74396      * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
74397      */
74398
74399     /**
74400      * @cfg {Boolean} [collapsed=false]
74401      * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
74402      * is true (the default) it will override the `collapsed` config and the window will always be
74403      * expanded when shown.
74404      */
74405
74406     /**
74407      * @cfg {Boolean} [maximized=false]
74408      * True to initially display the window in a maximized state.
74409      */
74410
74411     /**
74412     * @cfg {String} [baseCls='x-window']
74413     * The base CSS class to apply to this panel's element.
74414     */
74415     baseCls: Ext.baseCSSPrefix + 'window',
74416
74417     /**
74418      * @cfg {Boolean/Object} resizable
74419      * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
74420      *
74421      * This may also be specified as a config object to Ext.resizer.Resizer
74422      */
74423     resizable: true,
74424
74425     /**
74426      * @cfg {Boolean} draggable
74427      * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
74428      * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
74429      * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
74430      */
74431     draggable: true,
74432
74433     /**
74434      * @cfg {Boolean} constrain
74435      * True to constrain the window within its containing element, false to allow it to fall outside of its containing
74436      * element. By default the window will be rendered to document.body. To render and constrain the window within
74437      * another element specify {@link #renderTo}. Optionally the header only can be constrained
74438      * using {@link #constrainHeader}.
74439      */
74440     constrain: false,
74441
74442     /**
74443      * @cfg {Boolean} constrainHeader
74444      * True to constrain the window header within its containing element (allowing the window body to fall outside of
74445      * its containing element) or false to allow the header to fall outside its containing element.
74446      * Optionally the entire window can be constrained using {@link #constrain}.
74447      */
74448     constrainHeader: false,
74449
74450     /**
74451      * @cfg {Boolean} plain
74452      * True to render the window body with a transparent background so that it will blend into the framing elements,
74453      * false to add a lighter background color to visually highlight the body element and separate it more distinctly
74454      * from the surrounding frame.
74455      */
74456     plain: false,
74457
74458     /**
74459      * @cfg {Boolean} minimizable
74460      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
74461      * and disallow minimizing the window. Note that this button provides no implementation -- the
74462      * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
74463      * minimize behavior implemented for this option to be useful.
74464      */
74465     minimizable: false,
74466
74467     /**
74468      * @cfg {Boolean} maximizable
74469      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
74470      * and disallow maximizing the window. Note that when a window is maximized, the tool button
74471      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
74472      * the window to its previous size.
74473      */
74474     maximizable: false,
74475
74476     // inherit docs
74477     minHeight: 100,
74478
74479     // inherit docs
74480     minWidth: 200,
74481
74482     /**
74483      * @cfg {Boolean} expandOnShow
74484      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
74485      * {@link #collapsed}) when displayed.
74486      */
74487     expandOnShow: true,
74488
74489     // inherited docs, same default
74490     collapsible: false,
74491
74492     /**
74493      * @cfg {Boolean} closable
74494      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
74495      * disallow closing the window.
74496      *
74497      * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
74498      * Window has focus, the {@link #close} method will be called. This will _{@link Ext.Component#destroy destroy}_ the
74499      * Window and its content meaning that it may not be reused.
74500      *
74501      * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
74502      */
74503     closable: true,
74504
74505     /**
74506      * @cfg {Boolean} hidden
74507      * Render this Window hidden. If `true`, the {@link #hide} method will be called internally.
74508      */
74509     hidden: true,
74510
74511     // Inherit docs from Component. Windows render to the body on first show.
74512     autoRender: true,
74513
74514     // Inherit docs from Component. Windows hide using visibility.
74515     hideMode: 'visibility',
74516
74517     /** @cfg {Boolean} floating @hide Windows are always floating*/
74518     floating: true,
74519
74520     ariaRole: 'alertdialog',
74521
74522     itemCls: 'x-window-item',
74523
74524     overlapHeader: true,
74525
74526     ignoreHeaderBorderManagement: true,
74527
74528     // private
74529     initComponent: function() {
74530         var me = this;
74531         me.callParent();
74532         me.addEvents(
74533             /**
74534              * @event activate
74535              * Fires after the window has been visually activated via {@link #setActive}.
74536              * @param {Ext.window.Window} this
74537              */
74538
74539             /**
74540              * @event deactivate
74541              * Fires after the window has been visually deactivated via {@link #setActive}.
74542              * @param {Ext.window.Window} this
74543              */
74544
74545             /**
74546              * @event resize
74547              * Fires after the window has been resized.
74548              * @param {Ext.window.Window} this
74549              * @param {Number} width The window's new width
74550              * @param {Number} height The window's new height
74551              */
74552             'resize',
74553
74554             /**
74555              * @event maximize
74556              * Fires after the window has been maximized.
74557              * @param {Ext.window.Window} this
74558              */
74559             'maximize',
74560
74561             /**
74562              * @event minimize
74563              * Fires after the window has been minimized.
74564              * @param {Ext.window.Window} this
74565              */
74566             'minimize',
74567
74568             /**
74569              * @event restore
74570              * Fires after the window has been restored to its original size after being maximized.
74571              * @param {Ext.window.Window} this
74572              */
74573             'restore'
74574         );
74575
74576         if (me.plain) {
74577             me.addClsWithUI('plain');
74578         }
74579
74580         if (me.modal) {
74581             me.ariaRole = 'dialog';
74582         }
74583     },
74584
74585     // State Management
74586     // private
74587
74588     initStateEvents: function(){
74589         var events = this.stateEvents;
74590         // push on stateEvents if they don't exist
74591         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
74592             if (Ext.Array.indexOf(events, event)) {
74593                 events.push(event);
74594             }
74595         });
74596         this.callParent();
74597     },
74598
74599     getState: function() {
74600         var me = this,
74601             state = me.callParent() || {},
74602             maximized = !!me.maximized;
74603
74604         state.maximized = maximized;
74605         Ext.apply(state, {
74606             size: maximized ? me.restoreSize : me.getSize(),
74607             pos: maximized ? me.restorePos : me.getPosition()
74608         });
74609         return state;
74610     },
74611
74612     applyState: function(state){
74613         var me = this;
74614
74615         if (state) {
74616             me.maximized = state.maximized;
74617             if (me.maximized) {
74618                 me.hasSavedRestore = true;
74619                 me.restoreSize = state.size;
74620                 me.restorePos = state.pos;
74621             } else {
74622                 Ext.apply(me, {
74623                     width: state.size.width,
74624                     height: state.size.height,
74625                     x: state.pos[0],
74626                     y: state.pos[1]
74627                 });
74628             }
74629         }
74630     },
74631
74632     // private
74633     onMouseDown: function (e) {
74634         var preventFocus;
74635             
74636         if (this.floating) {
74637             if (Ext.fly(e.getTarget()).focusable()) {
74638                 preventFocus = true;
74639             }
74640             this.toFront(preventFocus);
74641         }
74642     },
74643
74644     // private
74645     onRender: function(ct, position) {
74646         var me = this;
74647         me.callParent(arguments);
74648         me.focusEl = me.el;
74649
74650         // Double clicking a header will toggleMaximize
74651         if (me.maximizable) {
74652             me.header.on({
74653                 dblclick: {
74654                     fn: me.toggleMaximize,
74655                     element: 'el',
74656                     scope: me
74657                 }
74658             });
74659         }
74660     },
74661
74662     // private
74663     afterRender: function() {
74664         var me = this,
74665             hidden = me.hidden,
74666             keyMap;
74667
74668         me.hidden = false;
74669         // Component's afterRender sizes and positions the Component
74670         me.callParent();
74671         me.hidden = hidden;
74672
74673         // Create the proxy after the size has been applied in Component.afterRender
74674         me.proxy = me.getProxy();
74675
74676         // clickToRaise
74677         me.mon(me.el, 'mousedown', me.onMouseDown, me);
74678         
74679         // allow the element to be focusable
74680         me.el.set({
74681             tabIndex: -1
74682         });
74683
74684         // Initialize
74685         if (me.maximized) {
74686             me.maximized = false;
74687             me.maximize();
74688         }
74689
74690         if (me.closable) {
74691             keyMap = me.getKeyMap();
74692             keyMap.on(27, me.onEsc, me);
74693
74694             //if (hidden) { ? would be consistent w/before/afterShow...
74695                 keyMap.disable();
74696             //}
74697         }
74698
74699         if (!hidden) {
74700             me.syncMonitorWindowResize();
74701             me.doConstrain();
74702         }
74703     },
74704
74705     /**
74706      * @private
74707      * @override
74708      * Override Component.initDraggable.
74709      * Window uses the header element as the delegate.
74710      */
74711     initDraggable: function() {
74712         var me = this,
74713             ddConfig;
74714
74715         if (!me.header) {
74716             me.updateHeader(true);
74717         }
74718
74719         /*
74720          * Check the header here again. If for whatever reason it wasn't created in
74721          * updateHeader (preventHeader) then we'll just ignore the rest since the
74722          * header acts as the drag handle.
74723          */
74724         if (me.header) {
74725             ddConfig = Ext.applyIf({
74726                 el: me.el,
74727                 delegate: '#' + me.header.id
74728             }, me.draggable);
74729
74730             // Add extra configs if Window is specified to be constrained
74731             if (me.constrain || me.constrainHeader) {
74732                 ddConfig.constrain = me.constrain;
74733                 ddConfig.constrainDelegate = me.constrainHeader;
74734                 ddConfig.constrainTo = me.constrainTo || me.container;
74735             }
74736
74737             /**
74738              * @property {Ext.util.ComponentDragger} dd
74739              * If this Window is configured {@link #draggable}, this property will contain an instance of
74740              * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
74741              * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
74742              *
74743              * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
74744              * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
74745              * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
74746              */
74747             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
74748             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
74749         }
74750     },
74751
74752     // private
74753     onEsc: function(k, e) {
74754         e.stopEvent();
74755         this[this.closeAction]();
74756     },
74757
74758     // private
74759     beforeDestroy: function() {
74760         var me = this;
74761         if (me.rendered) {
74762             delete this.animateTarget;
74763             me.hide();
74764             Ext.destroy(
74765                 me.keyMap
74766             );
74767         }
74768         me.callParent();
74769     },
74770
74771     /**
74772      * @private
74773      * @override
74774      * Contribute class-specific tools to the header.
74775      * Called by Panel's initTools.
74776      */
74777     addTools: function() {
74778         var me = this;
74779
74780         // Call Panel's initTools
74781         me.callParent();
74782
74783         if (me.minimizable) {
74784             me.addTool({
74785                 type: 'minimize',
74786                 handler: Ext.Function.bind(me.minimize, me, [])
74787             });
74788         }
74789         if (me.maximizable) {
74790             me.addTool({
74791                 type: 'maximize',
74792                 handler: Ext.Function.bind(me.maximize, me, [])
74793             });
74794             me.addTool({
74795                 type: 'restore',
74796                 handler: Ext.Function.bind(me.restore, me, []),
74797                 hidden: true
74798             });
74799         }
74800     },
74801
74802     /**
74803      * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
74804      * Container itself will receive focus.
74805      */
74806     getFocusEl: function() {
74807         var me = this,
74808             f = me.focusEl,
74809             defaultComp = me.defaultButton || me.defaultFocus,
74810             t = typeof db,
74811             el,
74812             ct;
74813
74814         if (Ext.isDefined(defaultComp)) {
74815             if (Ext.isNumber(defaultComp)) {
74816                 f = me.query('button')[defaultComp];
74817             } else if (Ext.isString(defaultComp)) {
74818                 f = me.down('#' + defaultComp);
74819             } else {
74820                 f = defaultComp;
74821             }
74822         }
74823         return f || me.focusEl;
74824     },
74825
74826     // private
74827     beforeShow: function() {
74828         this.callParent();
74829
74830         if (this.expandOnShow) {
74831             this.expand(false);
74832         }
74833     },
74834
74835     // private
74836     afterShow: function(animateTarget) {
74837         var me = this,
74838             animating = animateTarget || me.animateTarget;
74839
74840
74841         // No constraining code needs to go here.
74842         // Component.onShow constrains the Component. *If the constrain config is true*
74843
74844         // Perform superclass's afterShow tasks
74845         // Which might include animating a proxy from an animateTarget
74846         me.callParent(arguments);
74847
74848         if (me.maximized) {
74849             me.fitContainer();
74850         }
74851
74852         me.syncMonitorWindowResize();
74853         if (!animating) {
74854             me.doConstrain();
74855         }
74856
74857         if (me.keyMap) {
74858             me.keyMap.enable();
74859         }
74860     },
74861
74862     // private
74863     doClose: function() {
74864         var me = this;
74865
74866         // Being called as callback after going through the hide call below
74867         if (me.hidden) {
74868             me.fireEvent('close', me);
74869             if (me.closeAction == 'destroy') {
74870                 this.destroy();
74871             }
74872         } else {
74873             // close after hiding
74874             me.hide(me.animateTarget, me.doClose, me);
74875         }
74876     },
74877
74878     // private
74879     afterHide: function() {
74880         var me = this;
74881
74882         // No longer subscribe to resizing now that we're hidden
74883         me.syncMonitorWindowResize();
74884
74885         // Turn off keyboard handling once window is hidden
74886         if (me.keyMap) {
74887             me.keyMap.disable();
74888         }
74889
74890         // Perform superclass's afterHide tasks.
74891         me.callParent(arguments);
74892     },
74893
74894     // private
74895     onWindowResize: function() {
74896         if (this.maximized) {
74897             this.fitContainer();
74898         }
74899         this.doConstrain();
74900     },
74901
74902     /**
74903      * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
74904      * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
74905      * the minimize event can be handled or this method can be overridden.
74906      * @return {Ext.window.Window} this
74907      */
74908     minimize: function() {
74909         this.fireEvent('minimize', this);
74910         return this;
74911     },
74912
74913     afterCollapse: function() {
74914         var me = this;
74915
74916         if (me.maximizable) {
74917             me.tools.maximize.hide();
74918             me.tools.restore.hide();
74919         }
74920         if (me.resizer) {
74921             me.resizer.disable();
74922         }
74923         me.callParent(arguments);
74924     },
74925
74926     afterExpand: function() {
74927         var me = this;
74928
74929         if (me.maximized) {
74930             me.tools.restore.show();
74931         } else if (me.maximizable) {
74932             me.tools.maximize.show();
74933         }
74934         if (me.resizer) {
74935             me.resizer.enable();
74936         }
74937         me.callParent(arguments);
74938     },
74939
74940     /**
74941      * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
74942      * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
74943      * @return {Ext.window.Window} this
74944      */
74945     maximize: function() {
74946         var me = this;
74947
74948         if (!me.maximized) {
74949             me.expand(false);
74950             if (!me.hasSavedRestore) {
74951                 me.restoreSize = me.getSize();
74952                 me.restorePos = me.getPosition(true);
74953             }
74954             if (me.maximizable) {
74955                 me.tools.maximize.hide();
74956                 me.tools.restore.show();
74957             }
74958             me.maximized = true;
74959             me.el.disableShadow();
74960
74961             if (me.dd) {
74962                 me.dd.disable();
74963             }
74964             if (me.collapseTool) {
74965                 me.collapseTool.hide();
74966             }
74967             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
74968             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
74969
74970             me.syncMonitorWindowResize();
74971             me.setPosition(0, 0);
74972             me.fitContainer();
74973             me.fireEvent('maximize', me);
74974         }
74975         return me;
74976     },
74977
74978     /**
74979      * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
74980      * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
74981      * @return {Ext.window.Window} this
74982      */
74983     restore: function() {
74984         var me = this,
74985             tools = me.tools;
74986
74987         if (me.maximized) {
74988             delete me.hasSavedRestore;
74989             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
74990
74991             // Toggle tool visibility
74992             if (tools.restore) {
74993                 tools.restore.hide();
74994             }
74995             if (tools.maximize) {
74996                 tools.maximize.show();
74997             }
74998             if (me.collapseTool) {
74999                 me.collapseTool.show();
75000             }
75001
75002             // Restore the position/sizing
75003             me.setPosition(me.restorePos);
75004             me.setSize(me.restoreSize);
75005
75006             // Unset old position/sizing
75007             delete me.restorePos;
75008             delete me.restoreSize;
75009
75010             me.maximized = false;
75011
75012             me.el.enableShadow(true);
75013
75014             // Allow users to drag and drop again
75015             if (me.dd) {
75016                 me.dd.enable();
75017             }
75018
75019             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
75020
75021             me.syncMonitorWindowResize();
75022             me.doConstrain();
75023             me.fireEvent('restore', me);
75024         }
75025         return me;
75026     },
75027
75028     /**
75029      * Synchronizes the presence of our listener for window resize events. This method
75030      * should be called whenever this status might change.
75031      * @private
75032      */
75033     syncMonitorWindowResize: function () {
75034         var me = this,
75035             currentlyMonitoring = me._monitoringResize,
75036             // all the states where we should be listening to window resize:
75037             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
75038             // all the states where we veto this:
75039             veto = me.hidden || me.destroying || me.isDestroyed;
75040
75041         if (yes && !veto) {
75042             // we should be listening...
75043             if (!currentlyMonitoring) {
75044                 // but we aren't, so set it up
75045                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
75046                 me._monitoringResize = true;
75047             }
75048         } else if (currentlyMonitoring) {
75049             // we should not be listening, but we are, so tear it down
75050             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
75051             me._monitoringResize = false;
75052         }
75053     },
75054
75055     /**
75056      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
75057      * state of the window.
75058      * @return {Ext.window.Window} this
75059      */
75060     toggleMaximize: function() {
75061         return this[this.maximized ? 'restore': 'maximize']();
75062     }
75063
75064     /**
75065      * @cfg {Boolean} autoWidth @hide
75066      * Absolute positioned element and therefore cannot support autoWidth.
75067      * A width is a required configuration.
75068      **/
75069 });
75070
75071 /**
75072  * @docauthor Jason Johnston <jason@sencha.com>
75073  *
75074  * Base class for form fields that provides default event handling, rendering, and other common functionality
75075  * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
75076  * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
75077  *
75078  * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
75079  * rather than creating instances of this class directly. However if you are implementing a custom form field,
75080  * using this as the parent class is recommended.
75081  *
75082  * # Values and Conversions
75083  *
75084  * Because BaseField implements the Field mixin, it has a main value that can be initialized with the
75085  * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
75086  * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
75087  * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
75088  * input, this value data type can not always be directly used in the rendered field.
75089  *
75090  * Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
75091  * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
75092  * work with the raw value, though it is recommended to use getValue and setValue in most cases.
75093  *
75094  * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
75095  * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
75096  * should override these methods to handle the conversion.
75097  *
75098  * # Rendering
75099  *
75100  * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
75101  * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
75102  * field renderings.
75103  *
75104  * # Example usage:
75105  *
75106  *     @example
75107  *     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
75108  *     // searchUrl when the Enter key is pressed.222
75109  *     Ext.define('Ext.form.SearchField', {
75110  *         extend: 'Ext.form.field.Base',
75111  *         alias: 'widget.searchfield',
75112  *     
75113  *         inputType: 'search',
75114  *     
75115  *         // Config defining the search URL
75116  *         searchUrl: 'http://www.google.com/search?q={0}',
75117  *     
75118  *         // Add specialkey listener
75119  *         initComponent: function() {
75120  *             this.callParent();
75121  *             this.on('specialkey', this.checkEnterKey, this);
75122  *         },
75123  *     
75124  *         // Handle enter key presses, execute the search if the field has a value
75125  *         checkEnterKey: function(field, e) {
75126  *             var value = this.getValue();
75127  *             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
75128  *                 location.href = Ext.String.format(this.searchUrl, value);
75129  *             }
75130  *         }
75131  *     });
75132  *     
75133  *     Ext.create('Ext.form.Panel', {
75134  *         title: 'BaseField Example',
75135  *         bodyPadding: 5,
75136  *         width: 250,
75137  *     
75138  *         // Fields will be arranged vertically, stretched to full width
75139  *         layout: 'anchor',
75140  *         defaults: {
75141  *             anchor: '100%'
75142  *         },
75143  *         items: [{
75144  *             xtype: 'searchfield',
75145  *             fieldLabel: 'Search',
75146  *             name: 'query'
75147  *         }],
75148  *         renderTo: Ext.getBody()
75149  *     });
75150  */
75151 Ext.define('Ext.form.field.Base', {
75152     extend: 'Ext.Component',
75153     mixins: {
75154         labelable: 'Ext.form.Labelable',
75155         field: 'Ext.form.field.Field'
75156     },
75157     alias: 'widget.field',
75158     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
75159     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
75160
75161     /**
75162      * @cfg {Ext.XTemplate} fieldSubTpl
75163      * The content of the field body is defined by this config option.
75164      */
75165     fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
75166         '<input id="{id}" type="{type}" ',
75167         '<tpl if="name">name="{name}" </tpl>',
75168         '<tpl if="size">size="{size}" </tpl>',
75169         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
75170         'class="{fieldCls} {typeCls}" autocomplete="off" />',
75171         {
75172             compiled: true,
75173             disableFormats: true
75174         }
75175     ],
75176
75177     /**
75178      * @cfg {String} name
75179      * The name of the field. This is used as the parameter name when including the field value
75180      * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
75181      * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
75182      */
75183
75184     /**
75185      * @cfg {String} inputType
75186      * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
75187      * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
75188      * fall back to 'text'.
75189      *
75190      * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
75191      * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
75192      * a plain unstyled file input you can use a BaseField with inputType:'file'.
75193      */
75194     inputType: 'text',
75195
75196     /**
75197      * @cfg {Number} tabIndex
75198      * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
75199      * applyTo
75200      */
75201
75202     /**
75203      * @cfg {String} invalidText
75204      * The error text to use when marking a field invalid and no message is provided
75205      */
75206     invalidText : 'The value in this field is invalid',
75207
75208     /**
75209      * @cfg {String} [fieldCls='x-form-field']
75210      * The default CSS class for the field input
75211      */
75212     fieldCls : Ext.baseCSSPrefix + 'form-field',
75213
75214     /**
75215      * @cfg {String} fieldStyle
75216      * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
75217      * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
75218      * the style after initialization.
75219      */
75220
75221     /**
75222      * @cfg {String} [focusCls='x-form-focus']
75223      * The CSS class to use when the field receives focus
75224      */
75225     focusCls : Ext.baseCSSPrefix + 'form-focus',
75226
75227     /**
75228      * @cfg {String} dirtyCls
75229      * The CSS class to use when the field value {@link #isDirty is dirty}.
75230      */
75231     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
75232
75233     /**
75234      * @cfg {String[]} checkChangeEvents
75235      * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
75236      * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
75237      * fired, followed by validation if the {@link #validateOnChange} option is enabled.
75238      *
75239      * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
75240      * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
75241      * browsers; the only known exceptions at the time of writing are:
75242      *
75243      *   - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
75244      *   - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
75245      *     and textareas
75246      *   - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
75247      *
75248      * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
75249      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
75250      * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
75251      * such a task automatically.
75252      */
75253     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
75254                         ['change', 'propertychange'] :
75255                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
75256
75257     /**
75258      * @cfg {Number} checkChangeBuffer
75259      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
75260      * Defaults to 50 milliseconds.
75261      */
75262     checkChangeBuffer: 50,
75263
75264     componentLayout: 'field',
75265
75266     /**
75267      * @cfg {Boolean} readOnly
75268      * true to mark the field as readOnly in HTML.
75269      *
75270      * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
75271      * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
75272      * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
75273      */
75274     readOnly : false,
75275
75276     /**
75277      * @cfg {String} readOnlyCls
75278      * The CSS class applied to the component's main element when it is {@link #readOnly}.
75279      */
75280     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
75281
75282     /**
75283      * @cfg {String} inputId
75284      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
75285      * configure this manually, you must make sure it is unique in the document.
75286      */
75287
75288     /**
75289      * @cfg {Boolean} validateOnBlur
75290      * Whether the field should validate when it loses focus. This will cause fields to be validated
75291      * as the user steps through the fields in the form regardless of whether they are making changes to those fields
75292      * along the way. See also {@link #validateOnChange}.
75293      */
75294     validateOnBlur: true,
75295
75296     // private
75297     hasFocus : false,
75298
75299     baseCls: Ext.baseCSSPrefix + 'field',
75300
75301     maskOnDisable: false,
75302
75303     // private
75304     initComponent : function() {
75305         var me = this;
75306
75307         me.callParent();
75308
75309         me.subTplData = me.subTplData || {};
75310
75311         me.addEvents(
75312             /**
75313              * @event focus
75314              * Fires when this field receives input focus.
75315              * @param {Ext.form.field.Base} this
75316              */
75317             'focus',
75318             /**
75319              * @event blur
75320              * Fires when this field loses input focus.
75321              * @param {Ext.form.field.Base} this
75322              */
75323             'blur',
75324             /**
75325              * @event specialkey
75326              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
75327              * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
75328              * pressed. For example:
75329              *
75330              *     var form = new Ext.form.Panel({
75331              *         ...
75332              *         items: [{
75333              *                 fieldLabel: 'Field 1',
75334              *                 name: 'field1',
75335              *                 allowBlank: false
75336              *             },{
75337              *                 fieldLabel: 'Field 2',
75338              *                 name: 'field2',
75339              *                 listeners: {
75340              *                     specialkey: function(field, e){
75341              *                         // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
75342              *                         // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
75343              *                         if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
75344              *                             var form = field.up('form').getForm();
75345              *                             form.submit();
75346              *                         }
75347              *                     }
75348              *                 }
75349              *             }
75350              *         ],
75351              *         ...
75352              *     });
75353              *
75354              * @param {Ext.form.field.Base} this
75355              * @param {Ext.EventObject} e The event object
75356              */
75357             'specialkey'
75358         );
75359
75360         // Init mixins
75361         me.initLabelable();
75362         me.initField();
75363
75364         // Default name to inputId
75365         if (!me.name) {
75366             me.name = me.getInputId();
75367         }
75368     },
75369
75370     /**
75371      * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
75372      * automatically generated.
75373      */
75374     getInputId: function() {
75375         return this.inputId || (this.inputId = Ext.id());
75376     },
75377
75378     /**
75379      * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
75380      * @return {Object} The template data
75381      * @template
75382      */
75383     getSubTplData: function() {
75384         var me = this,
75385             type = me.inputType,
75386             inputId = me.getInputId();
75387
75388         return Ext.applyIf(me.subTplData, {
75389             id: inputId,
75390             cmpId: me.id,
75391             name: me.name || inputId,
75392             type: type,
75393             size: me.size || 20,
75394             cls: me.cls,
75395             fieldCls: me.fieldCls,
75396             tabIdx: me.tabIndex,
75397             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
75398         });
75399     },
75400
75401     afterRender: function() {
75402         this.callParent();
75403         
75404         if (this.inputEl) {
75405             this.inputEl.selectable();
75406         }
75407     },
75408
75409     /**
75410      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
75411      */
75412     getSubTplMarkup: function() {
75413         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
75414     },
75415
75416     initRenderTpl: function() {
75417         var me = this;
75418         if (!me.hasOwnProperty('renderTpl')) {
75419             me.renderTpl = me.getTpl('labelableRenderTpl');
75420         }
75421         return me.callParent();
75422     },
75423
75424     initRenderData: function() {
75425         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
75426     },
75427
75428     /**
75429      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
75430      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
75431      * Ext.Element#applyStyles}.
75432      */
75433     setFieldStyle: function(style) {
75434         var me = this,
75435             inputEl = me.inputEl;
75436         if (inputEl) {
75437             inputEl.applyStyles(style);
75438         }
75439         me.fieldStyle = style;
75440     },
75441
75442     // private
75443     onRender : function() {
75444         var me = this,
75445             fieldStyle = me.fieldStyle;
75446
75447         me.onLabelableRender();
75448
75449         /**
75450          * @property {Ext.Element} inputEl
75451          * The input Element for this Field. Only available after the field has been rendered.
75452          */
75453         me.addChildEls({ name: 'inputEl', id: me.getInputId() });
75454
75455         me.callParent(arguments);
75456
75457         // Make the stored rawValue get set as the input element's value
75458         me.setRawValue(me.rawValue);
75459
75460         if (me.readOnly) {
75461             me.setReadOnly(true);
75462         }
75463         if (me.disabled) {
75464             me.disable();
75465         }
75466         if (fieldStyle) {
75467             me.setFieldStyle(fieldStyle);
75468         }
75469
75470         me.renderActiveError();
75471     },
75472
75473     initAria: function() {
75474         var me = this;
75475         me.callParent();
75476
75477         // Associate the field to the error message element
75478         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
75479     },
75480
75481     getFocusEl: function() {
75482         return this.inputEl;
75483     },
75484
75485     isFileUpload: function() {
75486         return this.inputType === 'file';
75487     },
75488
75489     extractFileInput: function() {
75490         var me = this,
75491             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
75492             clone;
75493         if (fileInput) {
75494             clone = fileInput.cloneNode(true);
75495             fileInput.parentNode.replaceChild(clone, fileInput);
75496             me.inputEl = Ext.get(clone);
75497         }
75498         return fileInput;
75499     },
75500
75501     // private override to use getSubmitValue() as a convenience
75502     getSubmitData: function() {
75503         var me = this,
75504             data = null,
75505             val;
75506         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
75507             val = me.getSubmitValue();
75508             if (val !== null) {
75509                 data = {};
75510                 data[me.getName()] = val;
75511             }
75512         }
75513         return data;
75514     },
75515
75516     /**
75517      * Returns the value that would be included in a standard form submit for this field. This will be combined with the
75518      * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
75519      * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
75520      *
75521      * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
75522      * successfully {@link #validate validated}.
75523      *
75524      * @return {String} The value to be submitted, or null.
75525      */
75526     getSubmitValue: function() {
75527         return this.processRawValue(this.getRawValue());
75528     },
75529
75530     /**
75531      * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
75532      * normalized and converted value see {@link #getValue}.
75533      * @return {String} value The raw String value of the field
75534      */
75535     getRawValue: function() {
75536         var me = this,
75537             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
75538         me.rawValue = v;
75539         return v;
75540     },
75541
75542     /**
75543      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
75544      * validation. To set the value with these additional inspections see {@link #setValue}.
75545      * @param {Object} value The value to set
75546      * @return {Object} value The field value that is set
75547      */
75548     setRawValue: function(value) {
75549         var me = this;
75550         value = Ext.value(value, '');
75551         me.rawValue = value;
75552
75553         // Some Field subclasses may not render an inputEl
75554         if (me.inputEl) {
75555             me.inputEl.dom.value = value;
75556         }
75557         return value;
75558     },
75559
75560     /**
75561      * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
75562      * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
75563      * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
75564      * to a String for display in the field.
75565      *
75566      * See {@link #rawToValue} for the opposite conversion.
75567      *
75568      * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
75569      * to an empty string.
75570      *
75571      * @param {Object} value The mixed-type value to convert to the raw representation.
75572      * @return {Object} The converted raw value.
75573      */
75574     valueToRaw: function(value) {
75575         return '' + Ext.value(value, '');
75576     },
75577
75578     /**
75579      * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
75580      * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
75581      * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
75582      *
75583      * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
75584      * to the desired object type.
75585      *
75586      * See {@link #valueToRaw} for the opposite conversion.
75587      *
75588      * The base implementation does no conversion, returning the raw value untouched.
75589      *
75590      * @param {Object} rawValue
75591      * @return {Object} The converted value.
75592      */
75593     rawToValue: function(rawValue) {
75594         return rawValue;
75595     },
75596
75597     /**
75598      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
75599      * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
75600      * nothing; individual subclasses may override this as needed.
75601      *
75602      * @param {Object} value The unprocessed string value
75603      * @return {Object} The processed string value
75604      */
75605     processRawValue: function(value) {
75606         return value;
75607     },
75608
75609     /**
75610      * Returns the current data value of the field. The type of value returned is particular to the type of the
75611      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
75612      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
75613      * @return {Object} value The field value
75614      */
75615     getValue: function() {
75616         var me = this,
75617             val = me.rawToValue(me.processRawValue(me.getRawValue()));
75618         me.value = val;
75619         return val;
75620     },
75621
75622     /**
75623      * Sets a data value into the field and runs the change detection and validation. To set the value directly
75624      * without these inspections see {@link #setRawValue}.
75625      * @param {Object} value The value to set
75626      * @return {Ext.form.field.Field} this
75627      */
75628     setValue: function(value) {
75629         var me = this;
75630         me.setRawValue(me.valueToRaw(value));
75631         return me.mixins.field.setValue.call(me, value);
75632     },
75633
75634
75635     //private
75636     onDisable: function() {
75637         var me = this,
75638             inputEl = me.inputEl;
75639         me.callParent();
75640         if (inputEl) {
75641             inputEl.dom.disabled = true;
75642         }
75643     },
75644
75645     //private
75646     onEnable: function() {
75647         var me = this,
75648             inputEl = me.inputEl;
75649         me.callParent();
75650         if (inputEl) {
75651             inputEl.dom.disabled = false;
75652         }
75653     },
75654
75655     /**
75656      * Sets the read only state of this field.
75657      * @param {Boolean} readOnly Whether the field should be read only.
75658      */
75659     setReadOnly: function(readOnly) {
75660         var me = this,
75661             inputEl = me.inputEl;
75662         if (inputEl) {
75663             inputEl.dom.readOnly = readOnly;
75664             inputEl.dom.setAttribute('aria-readonly', readOnly);
75665         }
75666         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
75667         me.readOnly = readOnly;
75668     },
75669
75670     // private
75671     fireKey: function(e){
75672         if(e.isSpecialKey()){
75673             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
75674         }
75675     },
75676
75677     // private
75678     initEvents : function(){
75679         var me = this,
75680             inputEl = me.inputEl,
75681             onChangeTask,
75682             onChangeEvent;
75683         if (inputEl) {
75684             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
75685             me.mon(inputEl, 'focus', me.onFocus, me);
75686
75687             // standardise buffer across all browsers + OS-es for consistent event order.
75688             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
75689             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
75690
75691             // listen for immediate value changes
75692             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
75693             me.onChangeEvent = onChangeEvent = function() {
75694                 onChangeTask.delay(me.checkChangeBuffer);
75695             };
75696             Ext.each(me.checkChangeEvents, function(eventName) {
75697                 if (eventName === 'propertychange') {
75698                     me.usesPropertychange = true;
75699                 }
75700                 me.mon(inputEl, eventName, onChangeEvent);
75701             }, me);
75702         }
75703         me.callParent();
75704     },
75705
75706     doComponentLayout: function() {
75707         var me = this,
75708             inputEl = me.inputEl,
75709             usesPropertychange = me.usesPropertychange,
75710             ename = 'propertychange',
75711             onChangeEvent = me.onChangeEvent;
75712
75713         // In IE if propertychange is one of the checkChangeEvents, we need to remove
75714         // the listener prior to layout and re-add it after, to prevent it from firing
75715         // needlessly for attribute and style changes applied to the inputEl.
75716         if (usesPropertychange) {
75717             me.mun(inputEl, ename, onChangeEvent);
75718         }
75719         me.callParent(arguments);
75720         if (usesPropertychange) {
75721             me.mon(inputEl, ename, onChangeEvent);
75722         }
75723     },
75724
75725     // private
75726     preFocus: Ext.emptyFn,
75727
75728     // private
75729     onFocus: function() {
75730         var me = this,
75731             focusCls = me.focusCls,
75732             inputEl = me.inputEl;
75733         me.preFocus();
75734         if (focusCls && inputEl) {
75735             inputEl.addCls(focusCls);
75736         }
75737         if (!me.hasFocus) {
75738             me.hasFocus = true;
75739             me.componentLayout.onFocus();
75740             me.fireEvent('focus', me);
75741         }
75742     },
75743
75744     // private
75745     beforeBlur : Ext.emptyFn,
75746
75747     // private
75748     onBlur : function(){
75749         var me = this,
75750             focusCls = me.focusCls,
75751             inputEl = me.inputEl;
75752
75753         if (me.destroying) {
75754             return;
75755         }
75756
75757         me.beforeBlur();
75758         if (focusCls && inputEl) {
75759             inputEl.removeCls(focusCls);
75760         }
75761         if (me.validateOnBlur) {
75762             me.validate();
75763         }
75764         me.hasFocus = false;
75765         me.fireEvent('blur', me);
75766         me.postBlur();
75767     },
75768
75769     // private
75770     postBlur : Ext.emptyFn,
75771
75772
75773     /**
75774      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
75775      * @param {Boolean} isDirty
75776      */
75777     onDirtyChange: function(isDirty) {
75778         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
75779     },
75780
75781
75782     /**
75783      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
75784      * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
75785      * always treated as valid.
75786      *
75787      * @return {Boolean} True if the value is valid, else false
75788      */
75789     isValid : function() {
75790         var me = this;
75791         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
75792     },
75793
75794
75795     /**
75796      * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
75797      * {@link #markInvalid} and false is returned, otherwise true is returned.
75798      *
75799      * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
75800      * onwards {@link #getErrors} should be overridden instead.
75801      *
75802      * @param {Object} value The value to validate
75803      * @return {Boolean} True if all validations passed, false if one or more failed
75804      */
75805     validateValue: function(value) {
75806         var me = this,
75807             errors = me.getErrors(value),
75808             isValid = Ext.isEmpty(errors);
75809         if (!me.preventMark) {
75810             if (isValid) {
75811                 me.clearInvalid();
75812             } else {
75813                 me.markInvalid(errors);
75814             }
75815         }
75816
75817         return isValid;
75818     },
75819
75820     /**
75821      * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
75822      * display the messages and applying {@link #invalidCls} to the field's UI element.
75823      *
75824      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
75825      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
75826      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
75827      *
75828      * @param {String/String[]} errors The validation message(s) to display.
75829      */
75830     markInvalid : function(errors) {
75831         // Save the message and fire the 'invalid' event
75832         var me = this,
75833             oldMsg = me.getActiveError();
75834         me.setActiveErrors(Ext.Array.from(errors));
75835         if (oldMsg !== me.getActiveError()) {
75836             me.doComponentLayout();
75837         }
75838     },
75839
75840     /**
75841      * Clear any invalid styles/messages for this field.
75842      *
75843      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
75844      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
75845      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
75846      */
75847     clearInvalid : function() {
75848         // Clear the message and fire the 'valid' event
75849         var me = this,
75850             hadError = me.hasActiveError();
75851         me.unsetActiveError();
75852         if (hadError) {
75853             me.doComponentLayout();
75854         }
75855     },
75856
75857     /**
75858      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
75859      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
75860      */
75861     renderActiveError: function() {
75862         var me = this,
75863             hasError = me.hasActiveError();
75864         if (me.inputEl) {
75865             // Add/remove invalid class
75866             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
75867         }
75868         me.mixins.labelable.renderActiveError.call(me);
75869     },
75870
75871
75872     getActionEl: function() {
75873         return this.inputEl || this.el;
75874     }
75875
75876 });
75877
75878 /**
75879  * @docauthor Jason Johnston <jason@sencha.com>
75880  *
75881  * A basic text field.  Can be used as a direct replacement for traditional text inputs,
75882  * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
75883  * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
75884  *
75885  * # Validation
75886  *
75887  * The Text field has a useful set of validations built in:
75888  *
75889  * - {@link #allowBlank} for making the field required
75890  * - {@link #minLength} for requiring a minimum value length
75891  * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
75892  *   as the `maxlength` attribute on the input element)
75893  * - {@link #regex} to specify a custom regular expression for validation
75894  *
75895  * In addition, custom validations may be added:
75896  *
75897  * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
75898  *   custom validation logic
75899  * - {@link #validator} allows a custom arbitrary function to be called during validation
75900  *
75901  * The details around how and when each of these validation options get used are described in the
75902  * documentation for {@link #getErrors}.
75903  *
75904  * By default, the field value is checked for validity immediately while the user is typing in the
75905  * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
75906  * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
75907  * {@link Ext.form.Panel} class documentation.
75908  *
75909  * # Masking and Character Stripping
75910  *
75911  * Text fields can be configured with custom regular expressions to be applied to entered values before
75912  * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
75913  *
75914  * # Example usage
75915  *
75916  *     @example
75917  *     Ext.create('Ext.form.Panel', {
75918  *         title: 'Contact Info',
75919  *         width: 300,
75920  *         bodyPadding: 10,
75921  *         renderTo: Ext.getBody(),
75922  *         items: [{
75923  *             xtype: 'textfield',
75924  *             name: 'name',
75925  *             fieldLabel: 'Name',
75926  *             allowBlank: false  // requires a non-empty value
75927  *         }, {
75928  *             xtype: 'textfield',
75929  *             name: 'email',
75930  *             fieldLabel: 'Email Address',
75931  *             vtype: 'email'  // requires value to be a valid email address format
75932  *         }]
75933  *     });
75934  */
75935 Ext.define('Ext.form.field.Text', {
75936     extend:'Ext.form.field.Base',
75937     alias: 'widget.textfield',
75938     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
75939     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
75940
75941     /**
75942      * @cfg {String} vtypeText
75943      * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
75944      * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
75945      */
75946
75947     /**
75948      * @cfg {RegExp} stripCharsRe
75949      * A JavaScript RegExp object used to strip unwanted content from the value
75950      * before validation. If <tt>stripCharsRe</tt> is specified,
75951      * every character matching <tt>stripCharsRe</tt> will be removed before fed to validation.
75952      * This does not change the value of the field.
75953      */
75954
75955     /**
75956      * @cfg {Number} size
75957      * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
75958      * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
75959      */
75960     size: 20,
75961
75962     /**
75963      * @cfg {Boolean} [grow=false]
75964      * true if this field should automatically grow and shrink to its content
75965      */
75966
75967     /**
75968      * @cfg {Number} growMin
75969      * The minimum width to allow when `{@link #grow} = true`
75970      */
75971     growMin : 30,
75972
75973     /**
75974      * @cfg {Number} growMax
75975      * The maximum width to allow when `{@link #grow} = true`
75976      */
75977     growMax : 800,
75978
75979     /**
75980      * @cfg {String} growAppend
75981      * A string that will be appended to the field's current value for the purposes of calculating the target field
75982      * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
75983      * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
75984      * width is adjusted.
75985      */
75986     growAppend: 'W',
75987
75988     /**
75989      * @cfg {String} vtype
75990      * A validation type name as defined in {@link Ext.form.field.VTypes}
75991      */
75992
75993     /**
75994      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
75995      * typed) that do not match.
75996      * Note: It dose not filter characters already in the input.
75997      */
75998
75999     /**
76000      * @cfg {Boolean} [disableKeyFilter=false]
76001      * Specify true to disable input keystroke filtering
76002      */
76003
76004     /**
76005      * @cfg {Boolean} allowBlank
76006      * Specify false to validate that the value's length is > 0
76007      */
76008     allowBlank : true,
76009
76010     /**
76011      * @cfg {Number} minLength
76012      * Minimum input field length required
76013      */
76014     minLength : 0,
76015
76016     /**
76017      * @cfg {Number} maxLength
76018      * Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). This behavior is intended to
76019      * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
76020      * tracking. To restrict the maximum number of characters that can be entered into the field use the **{@link
76021      * Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
76022      */
76023     maxLength : Number.MAX_VALUE,
76024
76025     /**
76026      * @cfg {Boolean} enforceMaxLength
76027      * True to set the maxLength property on the underlying input field. Defaults to false
76028      */
76029
76030     /**
76031      * @cfg {String} minLengthText
76032      * Error text to display if the **{@link #minLength minimum length}** validation fails.
76033      */
76034     minLengthText : 'The minimum length for this field is {0}',
76035
76036     /**
76037      * @cfg {String} maxLengthText
76038      * Error text to display if the **{@link #maxLength maximum length}** validation fails
76039      */
76040     maxLengthText : 'The maximum length for this field is {0}',
76041
76042     /**
76043      * @cfg {Boolean} [selectOnFocus=false]
76044      * true to automatically select any existing field text when the field receives input focus
76045      */
76046
76047     /**
76048      * @cfg {String} blankText
76049      * The error text to display if the **{@link #allowBlank}** validation fails
76050      */
76051     blankText : 'This field is required',
76052
76053     /**
76054      * @cfg {Function} validator
76055      * A custom validation function to be called during field validation ({@link #getErrors}).
76056      * If specified, this function will be called first, allowing the developer to override the default validation
76057      * process.
76058      *
76059      * This function will be passed the following parameters:
76060      *
76061      * @cfg {Object} validator.value The current field value
76062      * @cfg {Boolean/String} validator.return
76063      *
76064      * - True if the value is valid
76065      * - An error message if the value is invalid
76066      */
76067
76068     /**
76069      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation.
76070      * If the test fails, the field will be marked invalid using
76071      * either <b><tt>{@link #regexText}</tt></b> or <b><tt>{@link #invalidText}</tt></b>.
76072      */
76073
76074     /**
76075      * @cfg {String} regexText
76076      * The error text to display if **{@link #regex}** is used and the test fails during validation
76077      */
76078     regexText : '',
76079
76080     /**
76081      * @cfg {String} emptyText
76082      * The default text to place into an empty field.
76083      *
76084      * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
76085      * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
76086      * false.
76087      *
76088      * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
76089      * avoided.
76090      */
76091
76092     /**
76093      * @cfg {String} [emptyCls='x-form-empty-field']
76094      * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
76095      * This class is automatically added and removed as needed depending on the current field value.
76096      */
76097     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
76098
76099     ariaRole: 'textbox',
76100
76101     /**
76102      * @cfg {Boolean} [enableKeyEvents=false]
76103      * true to enable the proxying of key events for the HTML input field
76104      */
76105
76106     componentLayout: 'textfield',
76107
76108     initComponent : function(){
76109         this.callParent();
76110         this.addEvents(
76111             /**
76112              * @event autosize
76113              * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
76114              * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
76115              * developer to apply additional logic at runtime to resize the field if needed.
76116              * @param {Ext.form.field.Text} this This text field
76117              * @param {Number} width The new field width
76118              */
76119             'autosize',
76120
76121             /**
76122              * @event keydown
76123              * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
76124              * @param {Ext.form.field.Text} this This text field
76125              * @param {Ext.EventObject} e
76126              */
76127             'keydown',
76128             /**
76129              * @event keyup
76130              * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
76131              * @param {Ext.form.field.Text} this This text field
76132              * @param {Ext.EventObject} e
76133              */
76134             'keyup',
76135             /**
76136              * @event keypress
76137              * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
76138              * @param {Ext.form.field.Text} this This text field
76139              * @param {Ext.EventObject} e
76140              */
76141             'keypress'
76142         );
76143     },
76144
76145     // private
76146     initEvents : function(){
76147         var me = this,
76148             el = me.inputEl;
76149
76150         me.callParent();
76151         if(me.selectOnFocus || me.emptyText){
76152             me.mon(el, 'mousedown', me.onMouseDown, me);
76153         }
76154         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
76155             me.mon(el, 'keypress', me.filterKeys, me);
76156         }
76157
76158         if (me.enableKeyEvents) {
76159             me.mon(el, {
76160                 scope: me,
76161                 keyup: me.onKeyUp,
76162                 keydown: me.onKeyDown,
76163                 keypress: me.onKeyPress
76164             });
76165         }
76166     },
76167
76168     /**
76169      * @private
76170      * Override. Treat undefined and null values as equal to an empty string value.
76171      */
76172     isEqual: function(value1, value2) {
76173         return this.isEqualAsString(value1, value2);
76174     },
76175
76176     /**
76177      * @private
76178      * If grow=true, invoke the autoSize method when the field's value is changed.
76179      */
76180     onChange: function() {
76181         this.callParent();
76182         this.autoSize();
76183     },
76184
76185     afterRender: function(){
76186         var me = this;
76187         if (me.enforceMaxLength) {
76188             me.inputEl.dom.maxLength = me.maxLength;
76189         }
76190         me.applyEmptyText();
76191         me.autoSize();
76192         me.callParent();
76193     },
76194
76195     onMouseDown: function(e){
76196         var me = this;
76197         if(!me.hasFocus){
76198             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
76199         }
76200     },
76201
76202     /**
76203      * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
76204      * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
76205      * to the raw value.
76206      * @param {String} value The unprocessed string value
76207      * @return {String} The processed string value
76208      */
76209     processRawValue: function(value) {
76210         var me = this,
76211             stripRe = me.stripCharsRe,
76212             newValue;
76213
76214         if (stripRe) {
76215             newValue = value.replace(stripRe, '');
76216             if (newValue !== value) {
76217                 me.setRawValue(newValue);
76218                 value = newValue;
76219             }
76220         }
76221         return value;
76222     },
76223
76224     //private
76225     onDisable: function(){
76226         this.callParent();
76227         if (Ext.isIE) {
76228             this.inputEl.dom.unselectable = 'on';
76229         }
76230     },
76231
76232     //private
76233     onEnable: function(){
76234         this.callParent();
76235         if (Ext.isIE) {
76236             this.inputEl.dom.unselectable = '';
76237         }
76238     },
76239
76240     onKeyDown: function(e) {
76241         this.fireEvent('keydown', this, e);
76242     },
76243
76244     onKeyUp: function(e) {
76245         this.fireEvent('keyup', this, e);
76246     },
76247
76248     onKeyPress: function(e) {
76249         this.fireEvent('keypress', this, e);
76250     },
76251
76252     /**
76253      * Resets the current field value to the originally-loaded value and clears any validation messages.
76254      * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
76255      */
76256     reset : function(){
76257         this.callParent();
76258         this.applyEmptyText();
76259     },
76260
76261     applyEmptyText : function(){
76262         var me = this,
76263             emptyText = me.emptyText,
76264             isEmpty;
76265
76266         if (me.rendered && emptyText) {
76267             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
76268
76269             if (Ext.supports.Placeholder) {
76270                 me.inputEl.dom.placeholder = emptyText;
76271             } else if (isEmpty) {
76272                 me.setRawValue(emptyText);
76273             }
76274
76275             //all browsers need this because of a styling issue with chrome + placeholders.
76276             //the text isnt vertically aligned when empty (and using the placeholder)
76277             if (isEmpty) {
76278                 me.inputEl.addCls(me.emptyCls);
76279             }
76280
76281             me.autoSize();
76282         }
76283     },
76284
76285     // private
76286     preFocus : function(){
76287         var me = this,
76288             inputEl = me.inputEl,
76289             emptyText = me.emptyText,
76290             isEmpty;
76291
76292         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
76293             me.setRawValue('');
76294             isEmpty = true;
76295             inputEl.removeCls(me.emptyCls);
76296         } else if (Ext.supports.Placeholder) {
76297             me.inputEl.removeCls(me.emptyCls);
76298         }
76299         if (me.selectOnFocus || isEmpty) {
76300             inputEl.dom.select();
76301         }
76302     },
76303
76304     onFocus: function() {
76305         var me = this;
76306         me.callParent(arguments);
76307         if (me.emptyText) {
76308             me.autoSize();
76309         }
76310     },
76311
76312     // private
76313     postBlur : function(){
76314         this.applyEmptyText();
76315     },
76316
76317     // private
76318     filterKeys : function(e){
76319         /*
76320          * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
76321          * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
76322          * so we can still process these special characters.
76323          */
76324         if (e.ctrlKey && !e.altKey) {
76325             return;
76326         }
76327         var key = e.getKey(),
76328             charCode = String.fromCharCode(e.getCharCode());
76329
76330         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
76331             return;
76332         }
76333
76334         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
76335             return;
76336         }
76337         if(!this.maskRe.test(charCode)){
76338             e.stopEvent();
76339         }
76340     },
76341
76342     /**
76343      * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
76344      * the current value of the input element if the field has been rendered, ignoring the value if it is the
76345      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
76346      * @return {String} The raw String value of the field
76347      */
76348     getRawValue: function() {
76349         var me = this,
76350             v = me.callParent();
76351         if (v === me.emptyText) {
76352             v = '';
76353         }
76354         return v;
76355     },
76356
76357     /**
76358      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
76359      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
76360      * @param {Object} value The value to set
76361      * @return {Ext.form.field.Text} this
76362      */
76363     setValue: function(value) {
76364         var me = this,
76365             inputEl = me.inputEl;
76366
76367         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
76368             inputEl.removeCls(me.emptyCls);
76369         }
76370
76371         me.callParent(arguments);
76372
76373         me.applyEmptyText();
76374         return me;
76375     },
76376
76377     /**
76378      * Validates a value according to the field's validation rules and returns an array of errors
76379      * for any failing validations. Validation rules are processed in the following order:
76380      *
76381      * 1. **Field specific validator**
76382      *
76383      *     A validator offers a way to customize and reuse a validation specification.
76384      *     If a field is configured with a `{@link #validator}`
76385      *     function, it will be passed the current field value.  The `{@link #validator}`
76386      *     function is expected to return either:
76387      *
76388      *     - Boolean `true`  if the value is valid (validation continues).
76389      *     - a String to represent the invalid message if invalid (validation halts).
76390      *
76391      * 2. **Basic Validation**
76392      *
76393      *     If the `{@link #validator}` has not halted validation,
76394      *     basic validation proceeds as follows:
76395      *
76396      *     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
76397      *
76398      *         Depending on the configuration of `{@link #allowBlank}`, a
76399      *         blank field will cause validation to halt at this step and return
76400      *         Boolean true or false accordingly.
76401      *
76402      *     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
76403      *
76404      *         If the passed value does not satisfy the `{@link #minLength}`
76405      *         specified, validation halts.
76406      *
76407      *     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
76408      *
76409      *         If the passed value does not satisfy the `{@link #maxLength}`
76410      *         specified, validation halts.
76411      *
76412      * 3. **Preconfigured Validation Types (VTypes)**
76413      *
76414      *     If none of the prior validation steps halts validation, a field
76415      *     configured with a `{@link #vtype}` will utilize the
76416      *     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
76417      *     If invalid, either the field's `{@link #vtypeText}` or
76418      *     the VTypes vtype Text property will be used for the invalid message.
76419      *     Keystrokes on the field will be filtered according to the VTypes
76420      *     vtype Mask property.
76421      *
76422      * 4. **Field specific regex test**
76423      *
76424      *     If none of the prior validation steps halts validation, a field's
76425      *     configured <code>{@link #regex}</code> test will be processed.
76426      *     The invalid message for this test is configured with `{@link #regexText}`
76427      *
76428      * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
76429      * @return {String[]} Array of any validation errors
76430      */
76431     getErrors: function(value) {
76432         var me = this,
76433             errors = me.callParent(arguments),
76434             validator = me.validator,
76435             emptyText = me.emptyText,
76436             allowBlank = me.allowBlank,
76437             vtype = me.vtype,
76438             vtypes = Ext.form.field.VTypes,
76439             regex = me.regex,
76440             format = Ext.String.format,
76441             msg;
76442
76443         value = value || me.processRawValue(me.getRawValue());
76444
76445         if (Ext.isFunction(validator)) {
76446             msg = validator.call(me, value);
76447             if (msg !== true) {
76448                 errors.push(msg);
76449             }
76450         }
76451
76452         if (value.length < 1 || value === emptyText) {
76453             if (!allowBlank) {
76454                 errors.push(me.blankText);
76455             }
76456             //if value is blank, there cannot be any additional errors
76457             return errors;
76458         }
76459
76460         if (value.length < me.minLength) {
76461             errors.push(format(me.minLengthText, me.minLength));
76462         }
76463
76464         if (value.length > me.maxLength) {
76465             errors.push(format(me.maxLengthText, me.maxLength));
76466         }
76467
76468         if (vtype) {
76469             if(!vtypes[vtype](value, me)){
76470                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
76471             }
76472         }
76473
76474         if (regex && !regex.test(value)) {
76475             errors.push(me.regexText || me.invalidText);
76476         }
76477
76478         return errors;
76479     },
76480
76481     /**
76482      * Selects text in this field
76483      * @param {Number} [start=0] The index where the selection should start
76484      * @param {Number} [end] The index where the selection should end (defaults to the text length)
76485      */
76486     selectText : function(start, end){
76487         var me = this,
76488             v = me.getRawValue(),
76489             doFocus = true,
76490             el = me.inputEl.dom,
76491             undef,
76492             range;
76493
76494         if (v.length > 0) {
76495             start = start === undef ? 0 : start;
76496             end = end === undef ? v.length : end;
76497             if (el.setSelectionRange) {
76498                 el.setSelectionRange(start, end);
76499             }
76500             else if(el.createTextRange) {
76501                 range = el.createTextRange();
76502                 range.moveStart('character', start);
76503                 range.moveEnd('character', end - v.length);
76504                 range.select();
76505             }
76506             doFocus = Ext.isGecko || Ext.isOpera;
76507         }
76508         if (doFocus) {
76509             me.focus();
76510         }
76511     },
76512
76513     /**
76514      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
76515      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
76516      */
76517     autoSize: function() {
76518         var me = this,
76519             width;
76520         if (me.grow && me.rendered) {
76521             me.doComponentLayout();
76522             width = me.inputEl.getWidth();
76523             if (width !== me.lastInputWidth) {
76524                 me.fireEvent('autosize', width);
76525                 me.lastInputWidth = width;
76526             }
76527         }
76528     },
76529
76530     initAria: function() {
76531         this.callParent();
76532         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
76533     },
76534
76535     /**
76536      * To get the natural width of the inputEl, we do a simple calculation based on the 'size' config. We use
76537      * hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which would hurt
76538      * performance. Overrides Labelable method.
76539      * @protected
76540      */
76541     getBodyNaturalWidth: function() {
76542         return Math.round(this.size * 6.5) + 20;
76543     }
76544
76545 });
76546
76547 /**
76548  * @docauthor Robert Dougan <rob@sencha.com>
76549  *
76550  * This class creates a multiline text field, which can be used as a direct replacement for traditional
76551  * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
76552  * fit its content.
76553  *
76554  * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
76555  *
76556  * Example usage:
76557  *
76558  *     @example
76559  *     Ext.create('Ext.form.FormPanel', {
76560  *         title      : 'Sample TextArea',
76561  *         width      : 400,
76562  *         bodyPadding: 10,
76563  *         renderTo   : Ext.getBody(),
76564  *         items: [{
76565  *             xtype     : 'textareafield',
76566  *             grow      : true,
76567  *             name      : 'message',
76568  *             fieldLabel: 'Message',
76569  *             anchor    : '100%'
76570  *         }]
76571  *     });
76572  *
76573  * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
76574  * These allow you to set the minimum and maximum grow heights for the textarea.
76575  */
76576 Ext.define('Ext.form.field.TextArea', {
76577     extend:'Ext.form.field.Text',
76578     alias: ['widget.textareafield', 'widget.textarea'],
76579     alternateClassName: 'Ext.form.TextArea',
76580     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
76581
76582     fieldSubTpl: [
76583         '<textarea id="{id}" ',
76584             '<tpl if="name">name="{name}" </tpl>',
76585             '<tpl if="rows">rows="{rows}" </tpl>',
76586             '<tpl if="cols">cols="{cols}" </tpl>',
76587             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
76588             'class="{fieldCls} {typeCls}" ',
76589             'autocomplete="off">',
76590         '</textarea>',
76591         {
76592             compiled: true,
76593             disableFormats: true
76594         }
76595     ],
76596
76597     /**
76598      * @cfg {Number} growMin
76599      * The minimum height to allow when {@link #grow}=true
76600      */
76601     growMin: 60,
76602
76603     /**
76604      * @cfg {Number} growMax
76605      * The maximum height to allow when {@link #grow}=true
76606      */
76607     growMax: 1000,
76608
76609     /**
76610      * @cfg {String} growAppend
76611      * A string that will be appended to the field's current value for the purposes of calculating the target field
76612      * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
76613      * always a space below the current line.
76614      */
76615     growAppend: '\n-',
76616
76617     /**
76618      * @cfg {Number} cols
76619      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
76620      * configured {@link #width} and is not given a width by its container's layout.
76621      */
76622     cols: 20,
76623
76624     /**
76625      * @cfg {Number} cols
76626      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
76627      * configured {@link #width} and is not given a width by its container's layout.
76628      */
76629     rows: 4,
76630
76631     /**
76632      * @cfg {Boolean} enterIsSpecial
76633      * True if you want the enter key to be classed as a special key. Special keys are generally navigation keys
76634      * (arrows, space, enter). Setting the config property to true would mean that you could not insert returns into the
76635      * textarea.
76636      */
76637     enterIsSpecial: false,
76638
76639     /**
76640      * @cfg {Boolean} preventScrollbars
76641      * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
76642      * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
76643      */
76644     preventScrollbars: false,
76645
76646     // private
76647     componentLayout: 'textareafield',
76648
76649     // private
76650     onRender: function(ct, position) {
76651         var me = this;
76652         Ext.applyIf(me.subTplData, {
76653             cols: me.cols,
76654             rows: me.rows
76655         });
76656
76657         me.callParent(arguments);
76658     },
76659
76660     // private
76661     afterRender: function(){
76662         var me = this;
76663
76664         me.callParent(arguments);
76665
76666         if (me.grow) {
76667             if (me.preventScrollbars) {
76668                 me.inputEl.setStyle('overflow', 'hidden');
76669             }
76670             me.inputEl.setHeight(me.growMin);
76671         }
76672     },
76673
76674     // private
76675     fireKey: function(e) {
76676         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
76677             this.fireEvent('specialkey', this, e);
76678         }
76679     },
76680
76681     /**
76682      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
76683      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
76684      */
76685     autoSize: function() {
76686         var me = this,
76687             height;
76688
76689         if (me.grow && me.rendered) {
76690             me.doComponentLayout();
76691             height = me.inputEl.getHeight();
76692             if (height !== me.lastInputHeight) {
76693                 me.fireEvent('autosize', height);
76694                 me.lastInputHeight = height;
76695             }
76696         }
76697     },
76698
76699     // private
76700     initAria: function() {
76701         this.callParent(arguments);
76702         this.getActionEl().dom.setAttribute('aria-multiline', true);
76703     },
76704
76705     /**
76706      * To get the natural width of the textarea element, we do a simple calculation based on the 'cols' config.
76707      * We use hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which
76708      * would hurt performance. Overrides Labelable method.
76709      * @protected
76710      */
76711     getBodyNaturalWidth: function() {
76712         return Math.round(this.cols * 6.5) + 20;
76713     }
76714
76715 });
76716
76717
76718 /**
76719  * Utility class for generating different styles of message boxes.  The singleton instance, Ext.MessageBox
76720  * alias `Ext.Msg` can also be used.
76721  *
76722  * Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
76723  * browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
76724  * that should only run *after* some user feedback from the MessageBox, you must use a callback function
76725  * (see the `function` parameter for {@link #show} for more details).
76726  *
76727  * Basic alert
76728  *
76729  *     @example
76730  *     Ext.Msg.alert('Status', 'Changes saved successfully.');
76731  *
76732  * Prompt for user data and process the result using a callback
76733  *
76734  *     @example
76735  *     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
76736  *         if (btn == 'ok'){
76737  *             // process text value and close...
76738  *         }
76739  *     });
76740  *
76741  * Show a dialog using config options
76742  *
76743  *     @example
76744  *     Ext.Msg.show({
76745  *          title:'Save Changes?',
76746  *          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
76747  *          buttons: Ext.Msg.YESNOCANCEL,
76748  *          icon: Ext.Msg.QUESTION
76749  *     });
76750  */
76751 Ext.define('Ext.window.MessageBox', {
76752     extend: 'Ext.window.Window',
76753
76754     requires: [
76755         'Ext.toolbar.Toolbar',
76756         'Ext.form.field.Text',
76757         'Ext.form.field.TextArea',
76758         'Ext.button.Button',
76759         'Ext.layout.container.Anchor',
76760         'Ext.layout.container.HBox',
76761         'Ext.ProgressBar'
76762     ],
76763
76764     alias: 'widget.messagebox',
76765
76766     /**
76767      * Button config that displays a single OK button
76768      * @type Number
76769      */
76770     OK : 1,
76771     /**
76772      * Button config that displays a single Yes button
76773      * @type Number
76774      */
76775     YES : 2,
76776     /**
76777      * Button config that displays a single No button
76778      * @type Number
76779      */
76780     NO : 4,
76781     /**
76782      * Button config that displays a single Cancel button
76783      * @type Number
76784      */
76785     CANCEL : 8,
76786     /**
76787      * Button config that displays OK and Cancel buttons
76788      * @type Number
76789      */
76790     OKCANCEL : 9,
76791     /**
76792      * Button config that displays Yes and No buttons
76793      * @type Number
76794      */
76795     YESNO : 6,
76796     /**
76797      * Button config that displays Yes, No and Cancel buttons
76798      * @type Number
76799      */
76800     YESNOCANCEL : 14,
76801     /**
76802      * The CSS class that provides the INFO icon image
76803      * @type String
76804      */
76805     INFO : 'ext-mb-info',
76806     /**
76807      * The CSS class that provides the WARNING icon image
76808      * @type String
76809      */
76810     WARNING : 'ext-mb-warning',
76811     /**
76812      * The CSS class that provides the QUESTION icon image
76813      * @type String
76814      */
76815     QUESTION : 'ext-mb-question',
76816     /**
76817      * The CSS class that provides the ERROR icon image
76818      * @type String
76819      */
76820     ERROR : 'ext-mb-error',
76821
76822     // hide it by offsets. Windows are hidden on render by default.
76823     hideMode: 'offsets',
76824     closeAction: 'hide',
76825     resizable: false,
76826     title: '&#160;',
76827
76828     width: 600,
76829     height: 500,
76830     minWidth: 250,
76831     maxWidth: 600,
76832     minHeight: 110,
76833     maxHeight: 500,
76834     constrain: true,
76835
76836     cls: Ext.baseCSSPrefix + 'message-box',
76837
76838     layout: {
76839         type: 'anchor'
76840     },
76841
76842     /**
76843      * The default height in pixels of the message box's multiline textarea if displayed.
76844      * @type Number
76845      */
76846     defaultTextHeight : 75,
76847     /**
76848      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
76849      * for setting a different minimum width than text-only dialogs may need.
76850      * @type Number
76851      */
76852     minProgressWidth : 250,
76853     /**
76854      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
76855      * for setting a different minimum width than text-only dialogs may need.
76856      * @type Number
76857      */
76858     minPromptWidth: 250,
76859     /**
76860      * An object containing the default button text strings that can be overriden for localized language support.
76861      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
76862      * resource file for handling language support across the framework.
76863      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
76864      * @type Object
76865      */
76866     buttonText: {
76867         ok: 'OK',
76868         yes: 'Yes',
76869         no: 'No',
76870         cancel: 'Cancel'
76871     },
76872
76873     buttonIds: [
76874         'ok', 'yes', 'no', 'cancel'
76875     ],
76876
76877     titleText: {
76878         confirm: 'Confirm',
76879         prompt: 'Prompt',
76880         wait: 'Loading...',
76881         alert: 'Attention'
76882     },
76883
76884     iconHeight: 35,
76885
76886     makeButton: function(btnIdx) {
76887         var btnId = this.buttonIds[btnIdx];
76888         return Ext.create('Ext.button.Button', {
76889             handler: this.btnCallback,
76890             itemId: btnId,
76891             scope: this,
76892             text: this.buttonText[btnId],
76893             minWidth: 75
76894         });
76895     },
76896
76897     btnCallback: function(btn) {
76898         var me = this,
76899             value,
76900             field;
76901
76902         if (me.cfg.prompt || me.cfg.multiline) {
76903             if (me.cfg.multiline) {
76904                 field = me.textArea;
76905             } else {
76906                 field = me.textField;
76907             }
76908             value = field.getValue();
76909             field.reset();
76910         }
76911
76912         // Important not to have focus remain in the hidden Window; Interferes with DnD.
76913         btn.blur();
76914         me.hide();
76915         me.userCallback(btn.itemId, value, me.cfg);
76916     },
76917
76918     hide: function() {
76919         var me = this;
76920         me.dd.endDrag();
76921         me.progressBar.reset();
76922         me.removeCls(me.cfg.cls);
76923         me.callParent();
76924     },
76925
76926     initComponent: function() {
76927         var me = this,
76928             i, button;
76929
76930         me.title = '&#160;';
76931
76932         me.topContainer = Ext.create('Ext.container.Container', {
76933             anchor: '100%',
76934             style: {
76935                 padding: '10px',
76936                 overflow: 'hidden'
76937             },
76938             items: [
76939                 me.iconComponent = Ext.create('Ext.Component', {
76940                     cls: 'ext-mb-icon',
76941                     width: 50,
76942                     height: me.iconHeight,
76943                     style: {
76944                         'float': 'left'
76945                     }
76946                 }),
76947                 me.promptContainer = Ext.create('Ext.container.Container', {
76948                     layout: {
76949                         type: 'anchor'
76950                     },
76951                     items: [
76952                         me.msg = Ext.create('Ext.Component', {
76953                             autoEl: { tag: 'span' },
76954                             cls: 'ext-mb-text'
76955                         }),
76956                         me.textField = Ext.create('Ext.form.field.Text', {
76957                             anchor: '100%',
76958                             enableKeyEvents: true,
76959                             listeners: {
76960                                 keydown: me.onPromptKey,
76961                                 scope: me
76962                             }
76963                         }),
76964                         me.textArea = Ext.create('Ext.form.field.TextArea', {
76965                             anchor: '100%',
76966                             height: 75
76967                         })
76968                     ]
76969                 })
76970             ]
76971         });
76972         me.progressBar = Ext.create('Ext.ProgressBar', {
76973             anchor: '-10',
76974             style: 'margin-left:10px'
76975         });
76976
76977         me.items = [me.topContainer, me.progressBar];
76978
76979         // Create the buttons based upon passed bitwise config
76980         me.msgButtons = [];
76981         for (i = 0; i < 4; i++) {
76982             button = me.makeButton(i);
76983             me.msgButtons[button.itemId] = button;
76984             me.msgButtons.push(button);
76985         }
76986         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
76987             ui: 'footer',
76988             dock: 'bottom',
76989             layout: {
76990                 pack: 'center'
76991             },
76992             items: [
76993                 me.msgButtons[0],
76994                 me.msgButtons[1],
76995                 me.msgButtons[2],
76996                 me.msgButtons[3]
76997             ]
76998         });
76999         me.dockedItems = [me.bottomTb];
77000
77001         me.callParent();
77002     },
77003
77004     onPromptKey: function(textField, e) {
77005         var me = this,
77006             blur;
77007
77008         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
77009             if (me.msgButtons.ok.isVisible()) {
77010                 blur = true;
77011                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
77012             } else if (me.msgButtons.yes.isVisible()) {
77013                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
77014                 blur = true;
77015             }
77016
77017             if (blur) {
77018                 me.textField.blur();
77019             }
77020         }
77021     },
77022
77023     reconfigure: function(cfg) {
77024         var me = this,
77025             buttons = cfg.buttons || 0,
77026             hideToolbar = true,
77027             initialWidth = me.maxWidth,
77028             i;
77029
77030         cfg = cfg || {};
77031         me.cfg = cfg;
77032         if (cfg.width) {
77033             initialWidth = cfg.width;
77034         }
77035
77036         // Default to allowing the Window to take focus.
77037         delete me.defaultFocus;
77038
77039         // clear any old animateTarget
77040         me.animateTarget = cfg.animateTarget || undefined;
77041
77042         // Defaults to modal
77043         me.modal = cfg.modal !== false;
77044
77045         // Show the title
77046         if (cfg.title) {
77047             me.setTitle(cfg.title||'&#160;');
77048         }
77049
77050         if (!me.rendered) {
77051             me.width = initialWidth;
77052             me.render(Ext.getBody());
77053         } else {
77054             me.setSize(initialWidth, me.maxHeight);
77055         }
77056         me.setPosition(-10000, -10000);
77057
77058         // Hide or show the close tool
77059         me.closable = cfg.closable && !cfg.wait;
77060         me.header.child('[type=close]').setVisible(cfg.closable !== false);
77061
77062         // Hide or show the header
77063         if (!cfg.title && !me.closable) {
77064             me.header.hide();
77065         } else {
77066             me.header.show();
77067         }
77068
77069         // Default to dynamic drag: drag the window, not a ghost
77070         me.liveDrag = !cfg.proxyDrag;
77071
77072         // wrap the user callback
77073         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
77074
77075         // Hide or show the icon Component
77076         me.setIcon(cfg.icon);
77077
77078         // Hide or show the message area
77079         if (cfg.msg) {
77080             me.msg.update(cfg.msg);
77081             me.msg.show();
77082         } else {
77083             me.msg.hide();
77084         }
77085
77086         // Hide or show the input field
77087         if (cfg.prompt || cfg.multiline) {
77088             me.multiline = cfg.multiline;
77089             if (cfg.multiline) {
77090                 me.textArea.setValue(cfg.value);
77091                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
77092                 me.textArea.show();
77093                 me.textField.hide();
77094                 me.defaultFocus = me.textArea;
77095             } else {
77096                 me.textField.setValue(cfg.value);
77097                 me.textArea.hide();
77098                 me.textField.show();
77099                 me.defaultFocus = me.textField;
77100             }
77101         } else {
77102             me.textArea.hide();
77103             me.textField.hide();
77104         }
77105
77106         // Hide or show the progress bar
77107         if (cfg.progress || cfg.wait) {
77108             me.progressBar.show();
77109             me.updateProgress(0, cfg.progressText);
77110             if(cfg.wait === true){
77111                 me.progressBar.wait(cfg.waitConfig);
77112             }
77113         } else {
77114             me.progressBar.hide();
77115         }
77116
77117         // Hide or show buttons depending on flag value sent.
77118         for (i = 0; i < 4; i++) {
77119             if (buttons & Math.pow(2, i)) {
77120
77121                 // Default to focus on the first visible button if focus not already set
77122                 if (!me.defaultFocus) {
77123                     me.defaultFocus = me.msgButtons[i];
77124                 }
77125                 me.msgButtons[i].show();
77126                 hideToolbar = false;
77127             } else {
77128                 me.msgButtons[i].hide();
77129             }
77130         }
77131
77132         // Hide toolbar if no buttons to show
77133         if (hideToolbar) {
77134             me.bottomTb.hide();
77135         } else {
77136             me.bottomTb.show();
77137         }
77138     },
77139
77140     /**
77141      * Displays a new message box, or reinitializes an existing message box, based on the config options
77142      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
77143      * although those calls are basic shortcuts and do not support all of the config options allowed here.
77144      * @param {Object} config The following config options are supported: <ul>
77145      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
77146      * opens and closes (defaults to undefined)</div></li>
77147      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
77148      * <li>Ext.window.MessageBox.OK</li>
77149      * <li>Ext.window.MessageBox.YES</li>
77150      * <li>Ext.window.MessageBox.NO</li>
77151      * <li>Ext.window.MessageBox.CANCEL</li>
77152      * </ul>Or false to not show any buttons (defaults to false)</div></li>
77153      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
77154      * progress and wait dialogs will ignore this property and always hide the close button as they can only
77155      * be closed programmatically.</div></li>
77156      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
77157      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
77158      * if displayed (defaults to 75)</div></li>
77159      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
77160      * by clicking on the configured buttons, or on the dialog close button, or by pressing
77161      * the return button to enter input.
77162      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
77163      * actions and can only be closed programmatically, so any required function should be called
77164      * by the same code after it closes the dialog. Parameters passed:<ul>
77165      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
77166      * <li><tt>ok</tt></li>
77167      * <li><tt>yes</tt></li>
77168      * <li><tt>no</tt></li>
77169      * <li><tt>cancel</tt></li>
77170      * </ul></div></div></li>
77171      * <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>
77172      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
77173      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
77174      * </ul></p></div></li>
77175      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
77176      * <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
77177      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
77178      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
77179      * add an optional header icon (defaults to '')</div></li>
77180      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
77181      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
77182      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
77183      * displayed (defaults to true)</div></li>
77184      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
77185      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
77186      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
77187      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
77188      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
77189      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
77190      * <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>
77191      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
77192      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
77193      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
77194      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
77195      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)</div></li>
77196      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
77197      * </ul>
77198      * Example usage:
77199      * <pre><code>
77200 Ext.Msg.show({
77201 title: 'Address',
77202 msg: 'Please enter your address:',
77203 width: 300,
77204 buttons: Ext.Msg.OKCANCEL,
77205 multiline: true,
77206 fn: saveAddress,
77207 animateTarget: 'addAddressBtn',
77208 icon: Ext.window.MessageBox.INFO
77209 });
77210 </code></pre>
77211      * @return {Ext.window.MessageBox} this
77212      */
77213     show: function(cfg) {
77214         var me = this;
77215
77216         me.reconfigure(cfg);
77217         me.addCls(cfg.cls);
77218         if (cfg.animateTarget) {
77219             me.doAutoSize(true);
77220             me.callParent();
77221         } else {
77222             me.callParent();
77223             me.doAutoSize(true);
77224         }
77225         return me;
77226     },
77227
77228     afterShow: function(){
77229         if (this.animateTarget) {
77230             this.center();
77231         }
77232         this.callParent(arguments);
77233     },
77234
77235     doAutoSize: function(center) {
77236         var me = this,
77237             icon = me.iconComponent,
77238             iconHeight = me.iconHeight;
77239
77240         if (!Ext.isDefined(me.frameWidth)) {
77241             me.frameWidth = me.el.getWidth() - me.body.getWidth();
77242         }
77243
77244         // reset to the original dimensions
77245         icon.setHeight(iconHeight);
77246
77247         // Allow per-invocation override of minWidth
77248         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
77249
77250         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
77251         // then constraining it to within the max with. Then adding up constituent element heights.
77252         me.topContainer.doLayout();
77253         if (Ext.isIE6 || Ext.isIEQuirks) {
77254             // In IE quirks, the initial full width of the prompt fields will prevent the container element
77255             // from collapsing once sized down, so temporarily force them to a small width. They'll get
77256             // layed out to their final width later when setting the final window size.
77257             me.textField.setCalculatedSize(9);
77258             me.textArea.setCalculatedSize(9);
77259         }
77260         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
77261             height = (me.header.rendered ? me.header.getHeight() : 0) +
77262             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
77263             me.progressBar.getHeight() +
77264             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
77265
77266         // Update to the size of the content, this way the text won't wrap under the icon.
77267         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
77268         me.setSize(width + me.frameWidth, height + me.frameWidth);
77269         if (center) {
77270             me.center();
77271         }
77272         return me;
77273     },
77274
77275     updateText: function(text) {
77276         this.msg.update(text);
77277         return this.doAutoSize(true);
77278     },
77279
77280     /**
77281      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
77282      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
77283      * to clear any existing icon. This method must be called before the MessageBox is shown.
77284      * The following built-in icon classes are supported, but you can also pass in a custom class name:
77285      * <pre>
77286 Ext.window.MessageBox.INFO
77287 Ext.window.MessageBox.WARNING
77288 Ext.window.MessageBox.QUESTION
77289 Ext.window.MessageBox.ERROR
77290      *</pre>
77291      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
77292      * @return {Ext.window.MessageBox} this
77293      */
77294     setIcon : function(icon) {
77295         var me = this;
77296         me.iconComponent.removeCls(me.iconCls);
77297         if (icon) {
77298             me.iconComponent.show();
77299             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
77300             me.iconComponent.addCls(me.iconCls = icon);
77301         } else {
77302             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
77303             me.iconComponent.hide();
77304         }
77305         return me;
77306     },
77307
77308     /**
77309      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
77310      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
77311      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
77312      * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
77313      * @param {String} [progressText=''] The progress text to display inside the progress bar.
77314      * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
77315      * so that any existing body text will not get overwritten by default unless a new value is passed in)
77316      * @return {Ext.window.MessageBox} this
77317      */
77318     updateProgress : function(value, progressText, msg){
77319         this.progressBar.updateProgress(value, progressText);
77320         if (msg){
77321             this.updateText(msg);
77322         }
77323         return this;
77324     },
77325
77326     onEsc: function() {
77327         if (this.closable !== false) {
77328             this.callParent(arguments);
77329         }
77330     },
77331
77332     /**
77333      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
77334      * If a callback function is passed it will be called after the user clicks either button,
77335      * and the id of the button that was clicked will be passed as the only parameter to the callback
77336      * (could also be the top-right close button).
77337      * @param {String} title The title bar text
77338      * @param {String} msg The message box body text
77339      * @param {Function} fn (optional) The callback function invoked after the message box is closed
77340      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
77341      * @return {Ext.window.MessageBox} this
77342      */
77343     confirm: function(cfg, msg, fn, scope) {
77344         if (Ext.isString(cfg)) {
77345             cfg = {
77346                 title: cfg,
77347                 icon: 'ext-mb-question',
77348                 msg: msg,
77349                 buttons: this.YESNO,
77350                 callback: fn,
77351                 scope: scope
77352             };
77353         }
77354         return this.show(cfg);
77355     },
77356
77357     /**
77358      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
77359      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
77360      * clicks either button, and the id of the button that was clicked (could also be the top-right
77361      * close button) and the text that was entered will be passed as the two parameters to the callback.
77362      * @param {String} title The title bar text
77363      * @param {String} msg The message box body text
77364      * @param {Function} [fn] The callback function invoked after the message box is closed
77365      * @param {Object} [scope] The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
77366      * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
77367      * property, or the height in pixels to create the textbox/
77368      * @param {String} [value=''] Default value of the text input element
77369      * @return {Ext.window.MessageBox} this
77370      */
77371     prompt : function(cfg, msg, fn, scope, multiline, value){
77372         if (Ext.isString(cfg)) {
77373             cfg = {
77374                 prompt: true,
77375                 title: cfg,
77376                 minWidth: this.minPromptWidth,
77377                 msg: msg,
77378                 buttons: this.OKCANCEL,
77379                 callback: fn,
77380                 scope: scope,
77381                 multiline: multiline,
77382                 value: value
77383             };
77384         }
77385         return this.show(cfg);
77386     },
77387
77388     /**
77389      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
77390      * interaction while waiting for a long-running process to complete that does not have defined intervals.
77391      * You are responsible for closing the message box when the process is complete.
77392      * @param {String} msg The message box body text
77393      * @param {String} title (optional) The title bar text
77394      * @param {Object} config (optional) A {@link Ext.ProgressBar#wait} config object
77395      * @return {Ext.window.MessageBox} this
77396      */
77397     wait : function(cfg, title, config){
77398         if (Ext.isString(cfg)) {
77399             cfg = {
77400                 title : title,
77401                 msg : cfg,
77402                 closable: false,
77403                 wait: true,
77404                 modal: true,
77405                 minWidth: this.minProgressWidth,
77406                 waitConfig: config
77407             };
77408         }
77409         return this.show(cfg);
77410     },
77411
77412     /**
77413      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
77414      * If a callback function is passed it will be called after the user clicks the button, and the
77415      * id of the button that was clicked will be passed as the only parameter to the callback
77416      * (could also be the top-right close button).
77417      * @param {String} title The title bar text
77418      * @param {String} msg The message box body text
77419      * @param {Function} fn (optional) The callback function invoked after the message box is closed
77420      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
77421      * @return {Ext.window.MessageBox} this
77422      */
77423     alert: function(cfg, msg, fn, scope) {
77424         if (Ext.isString(cfg)) {
77425             cfg = {
77426                 title : cfg,
77427                 msg : msg,
77428                 buttons: this.OK,
77429                 fn: fn,
77430                 scope : scope,
77431                 minWidth: this.minWidth
77432             };
77433         }
77434         return this.show(cfg);
77435     },
77436
77437     /**
77438      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
77439      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
77440      * and closing the message box when the process is complete.
77441      * @param {String} title The title bar text
77442      * @param {String} msg The message box body text
77443      * @param {String} [progressText=''] The text to display inside the progress bar
77444      * @return {Ext.window.MessageBox} this
77445      */
77446     progress : function(cfg, msg, progressText){
77447         if (Ext.isString(cfg)) {
77448             cfg = {
77449                 title: cfg,
77450                 msg: msg,
77451                 progress: true,
77452                 progressText: progressText
77453             };
77454         }
77455         return this.show(cfg);
77456     }
77457 }, function() {
77458     /**
77459      * @class Ext.MessageBox
77460      * @alternateClassName Ext.Msg
77461      * @extends Ext.window.MessageBox
77462      * @singleton
77463      * Singleton instance of {@link Ext.window.MessageBox}.
77464      */
77465     Ext.MessageBox = Ext.Msg = new this();
77466 });
77467 /**
77468  * @class Ext.form.Basic
77469  * @extends Ext.util.Observable
77470  *
77471  * Provides input field management, validation, submission, and form loading services for the collection
77472  * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
77473  * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
77474  * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
77475  *
77476  * ## Form Actions
77477  *
77478  * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
77479  * See the various Action implementations for specific details of each one's functionality, as well as the
77480  * documentation for {@link #doAction} which details the configuration options that can be specified in
77481  * each action call.
77482  *
77483  * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
77484  * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
77485  * {@link #standardSubmit} config option.
77486  *
77487  * ## File uploads
77488  *
77489  * File uploads are not performed using normal 'Ajax' techniques; see the description for
77490  * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
77491  *
77492  * ## Example usage:
77493  *
77494  *     Ext.create('Ext.form.Panel', {
77495  *         title: 'Basic Form',
77496  *         renderTo: Ext.getBody(),
77497  *         bodyPadding: 5,
77498  *         width: 350,
77499  *
77500  *         // Any configuration items here will be automatically passed along to
77501  *         // the Ext.form.Basic instance when it gets created.
77502  *
77503  *         // The form will submit an AJAX request to this URL when submitted
77504  *         url: 'save-form.php',
77505  *
77506  *         items: [{
77507  *             fieldLabel: 'Field',
77508  *             name: 'theField'
77509  *         }],
77510  *
77511  *         buttons: [{
77512  *             text: 'Submit',
77513  *             handler: function() {
77514  *                 // The getForm() method returns the Ext.form.Basic instance:
77515  *                 var form = this.up('form').getForm();
77516  *                 if (form.isValid()) {
77517  *                     // Submit the Ajax request and handle the response
77518  *                     form.submit({
77519  *                         success: function(form, action) {
77520  *                            Ext.Msg.alert('Success', action.result.msg);
77521  *                         },
77522  *                         failure: function(form, action) {
77523  *                             Ext.Msg.alert('Failed', action.result.msg);
77524  *                         }
77525  *                     });
77526  *                 }
77527  *             }
77528  *         }]
77529  *     });
77530  *
77531  * @docauthor Jason Johnston <jason@sencha.com>
77532  */
77533 Ext.define('Ext.form.Basic', {
77534     extend: 'Ext.util.Observable',
77535     alternateClassName: 'Ext.form.BasicForm',
77536     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
77537                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
77538
77539     /**
77540      * Creates new form.
77541      * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
77542      * @param {Object} config Configuration options. These are normally specified in the config to the
77543      * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
77544      */
77545     constructor: function(owner, config) {
77546         var me = this,
77547             onItemAddOrRemove = me.onItemAddOrRemove;
77548
77549         /**
77550          * @property owner
77551          * @type Ext.container.Container
77552          * The container component to which this BasicForm is attached.
77553          */
77554         me.owner = owner;
77555
77556         // Listen for addition/removal of fields in the owner container
77557         me.mon(owner, {
77558             add: onItemAddOrRemove,
77559             remove: onItemAddOrRemove,
77560             scope: me
77561         });
77562
77563         Ext.apply(me, config);
77564
77565         // Normalize the paramOrder to an Array
77566         if (Ext.isString(me.paramOrder)) {
77567             me.paramOrder = me.paramOrder.split(/[\s,|]/);
77568         }
77569
77570         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
77571
77572         me.addEvents(
77573             /**
77574              * @event beforeaction
77575              * Fires before any action is performed. Return false to cancel the action.
77576              * @param {Ext.form.Basic} this
77577              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
77578              */
77579             'beforeaction',
77580             /**
77581              * @event actionfailed
77582              * Fires when an action fails.
77583              * @param {Ext.form.Basic} this
77584              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
77585              */
77586             'actionfailed',
77587             /**
77588              * @event actioncomplete
77589              * Fires when an action is completed.
77590              * @param {Ext.form.Basic} this
77591              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
77592              */
77593             'actioncomplete',
77594             /**
77595              * @event validitychange
77596              * Fires when the validity of the entire form changes.
77597              * @param {Ext.form.Basic} this
77598              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
77599              */
77600             'validitychange',
77601             /**
77602              * @event dirtychange
77603              * Fires when the dirty state of the entire form changes.
77604              * @param {Ext.form.Basic} this
77605              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
77606              */
77607             'dirtychange'
77608         );
77609         me.callParent();
77610     },
77611
77612     /**
77613      * Do any post constructor initialization
77614      * @private
77615      */
77616     initialize: function(){
77617         this.initialized = true;
77618         this.onValidityChange(!this.hasInvalidField());
77619     },
77620
77621     /**
77622      * @cfg {String} method
77623      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
77624      */
77625
77626     /**
77627      * @cfg {Ext.data.reader.Reader} reader
77628      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
77629      * data when executing 'load' actions. This is optional as there is built-in
77630      * support for processing JSON responses.
77631      */
77632
77633     /**
77634      * @cfg {Ext.data.reader.Reader} errorReader
77635      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
77636      * read field error messages returned from 'submit' actions. This is optional
77637      * as there is built-in support for processing JSON responses.</p>
77638      * <p>The Records which provide messages for the invalid Fields must use the
77639      * Field name (or id) as the Record ID, and must contain a field called 'msg'
77640      * which contains the error message.</p>
77641      * <p>The errorReader does not have to be a full-blown implementation of a
77642      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
77643      * which returns an Array of Records in an object with the following
77644      * structure:</p><pre><code>
77645 {
77646     records: recordArray
77647 }
77648 </code></pre>
77649      */
77650
77651     /**
77652      * @cfg {String} url
77653      * The URL to use for form actions if one isn't supplied in the
77654      * {@link #doAction doAction} options.
77655      */
77656
77657     /**
77658      * @cfg {Object} baseParams
77659      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
77660      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.</p>
77661      */
77662
77663     /**
77664      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
77665      */
77666     timeout: 30,
77667
77668     /**
77669      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
77670      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
77671      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
77672      * forms.
77673      * Such as the following:<pre><code>
77674 api: {
77675     load: App.ss.MyProfile.load,
77676     submit: App.ss.MyProfile.submit
77677 }
77678 </code></pre>
77679      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
77680      * to customize how the load method is invoked.
77681      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
77682      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
77683      */
77684
77685     /**
77686      * @cfg {String/String[]} paramOrder <p>A list of params to be executed server side.
77687      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
77688      * <code>load</code> configuration.</p>
77689      * <p>Specify the params in the order in which they must be executed on the
77690      * server-side as either (1) an Array of String values, or (2) a String of params
77691      * delimited by either whitespace, comma, or pipe. For example,
77692      * any of the following would be acceptable:</p><pre><code>
77693 paramOrder: ['param1','param2','param3']
77694 paramOrder: 'param1 param2 param3'
77695 paramOrder: 'param1,param2,param3'
77696 paramOrder: 'param1|param2|param'
77697      </code></pre>
77698      */
77699
77700     /**
77701      * @cfg {Boolean} paramsAsHash
77702      * Only used for the <code>{@link #api}</code>
77703      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
77704      * single hash collection of named arguments. Providing a
77705      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
77706      */
77707     paramsAsHash: false,
77708
77709     /**
77710      * @cfg {String} waitTitle
77711      * The default title to show for the waiting message box
77712      */
77713     waitTitle: 'Please Wait...',
77714
77715     /**
77716      * @cfg {Boolean} trackResetOnLoad
77717      * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
77718      * when the form was first created.
77719      */
77720     trackResetOnLoad: false,
77721
77722     /**
77723      * @cfg {Boolean} standardSubmit
77724      * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
77725      * All of the field values, plus any additional params configured via {@link #baseParams}
77726      * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
77727      */
77728
77729     /**
77730      * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
77731      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
77732      * element by passing it or its id or mask the form itself by passing in true.
77733      */
77734
77735
77736     // Private
77737     wasDirty: false,
77738
77739
77740     /**
77741      * Destroys this object.
77742      */
77743     destroy: function() {
77744         this.clearListeners();
77745         this.checkValidityTask.cancel();
77746     },
77747
77748     /**
77749      * @private
77750      * Handle addition or removal of descendant items. Invalidates the cached list of fields
77751      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
77752      * for state change events on added fields, and tracks components with formBind=true.
77753      */
77754     onItemAddOrRemove: function(parent, child) {
77755         var me = this,
77756             isAdding = !!child.ownerCt,
77757             isContainer = child.isContainer;
77758
77759         function handleField(field) {
77760             // Listen for state change events on fields
77761             me[isAdding ? 'mon' : 'mun'](field, {
77762                 validitychange: me.checkValidity,
77763                 dirtychange: me.checkDirty,
77764                 scope: me,
77765                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
77766             });
77767             // Flush the cached list of fields
77768             delete me._fields;
77769         }
77770
77771         if (child.isFormField) {
77772             handleField(child);
77773         } else if (isContainer) {
77774             // Walk down
77775             if (child.isDestroyed) {
77776                 // the container is destroyed, this means we may have child fields, so here
77777                 // we just invalidate all the fields to be sure.
77778                 delete me._fields;
77779             } else {
77780                 Ext.Array.forEach(child.query('[isFormField]'), handleField);
77781             }
77782         }
77783
77784         // Flush the cached list of formBind components
77785         delete this._boundItems;
77786
77787         // Check form bind, but only after initial add. Batch it to prevent excessive validation
77788         // calls when many fields are being added at once.
77789         if (me.initialized) {
77790             me.checkValidityTask.delay(10);
77791         }
77792     },
77793
77794     /**
77795      * Return all the {@link Ext.form.field.Field} components in the owner container.
77796      * @return {Ext.util.MixedCollection} Collection of the Field objects
77797      */
77798     getFields: function() {
77799         var fields = this._fields;
77800         if (!fields) {
77801             fields = this._fields = Ext.create('Ext.util.MixedCollection');
77802             fields.addAll(this.owner.query('[isFormField]'));
77803         }
77804         return fields;
77805     },
77806
77807     /**
77808      * @private
77809      * Finds and returns the set of all items bound to fields inside this form
77810      * @return {Ext.util.MixedCollection} The set of all bound form field items
77811      */
77812     getBoundItems: function() {
77813         var boundItems = this._boundItems;
77814         
77815         if (!boundItems || boundItems.getCount() === 0) {
77816             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
77817             boundItems.addAll(this.owner.query('[formBind]'));
77818         }
77819         
77820         return boundItems;
77821     },
77822
77823     /**
77824      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
77825      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
77826      */
77827     hasInvalidField: function() {
77828         return !!this.getFields().findBy(function(field) {
77829             var preventMark = field.preventMark,
77830                 isValid;
77831             field.preventMark = true;
77832             isValid = field.isValid();
77833             field.preventMark = preventMark;
77834             return !isValid;
77835         });
77836     },
77837
77838     /**
77839      * Returns true if client-side validation on the form is successful. Any invalid fields will be
77840      * marked as invalid. If you only want to determine overall form validity without marking anything,
77841      * use {@link #hasInvalidField} instead.
77842      * @return Boolean
77843      */
77844     isValid: function() {
77845         var me = this,
77846             invalid;
77847         me.batchLayouts(function() {
77848             invalid = me.getFields().filterBy(function(field) {
77849                 return !field.validate();
77850             });
77851         });
77852         return invalid.length < 1;
77853     },
77854
77855     /**
77856      * Check whether the validity of the entire form has changed since it was last checked, and
77857      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
77858      * when an individual field's validity changes.
77859      */
77860     checkValidity: function() {
77861         var me = this,
77862             valid = !me.hasInvalidField();
77863         if (valid !== me.wasValid) {
77864             me.onValidityChange(valid);
77865             me.fireEvent('validitychange', me, valid);
77866             me.wasValid = valid;
77867         }
77868     },
77869
77870     /**
77871      * @private
77872      * Handle changes in the form's validity. If there are any sub components with
77873      * formBind=true then they are enabled/disabled based on the new validity.
77874      * @param {Boolean} valid
77875      */
77876     onValidityChange: function(valid) {
77877         var boundItems = this.getBoundItems();
77878         if (boundItems) {
77879             boundItems.each(function(cmp) {
77880                 if (cmp.disabled === valid) {
77881                     cmp.setDisabled(!valid);
77882                 }
77883             });
77884         }
77885     },
77886
77887     /**
77888      * <p>Returns true if any fields in this form have changed from their original values.</p>
77889      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
77890      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
77891      * or {@link #loadRecord}.</p>
77892      * @return Boolean
77893      */
77894     isDirty: function() {
77895         return !!this.getFields().findBy(function(f) {
77896             return f.isDirty();
77897         });
77898     },
77899
77900     /**
77901      * Check whether the dirty state of the entire form has changed since it was last checked, and
77902      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
77903      * when an individual field's dirty state changes.
77904      */
77905     checkDirty: function() {
77906         var dirty = this.isDirty();
77907         if (dirty !== this.wasDirty) {
77908             this.fireEvent('dirtychange', this, dirty);
77909             this.wasDirty = dirty;
77910         }
77911     },
77912
77913     /**
77914      * <p>Returns true if the form contains a file upload field. This is used to determine the
77915      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
77916      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
77917      * element containing all the fields is created temporarily and submitted with its
77918      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
77919      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
77920      * but removed after the return data has been gathered.</p>
77921      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
77922      * server is using JSON to send the return object, then the
77923      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
77924      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
77925      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
77926      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
77927      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
77928      * is created containing a <tt>responseText</tt> property in order to conform to the
77929      * requirements of event handlers and callbacks.</p>
77930      * <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>
77931      * and some server technologies (notably JEE) may require some custom processing in order to
77932      * retrieve parameter names and parameter values from the packet content.</p>
77933      * @return Boolean
77934      */
77935     hasUpload: function() {
77936         return !!this.getFields().findBy(function(f) {
77937             return f.isFileUpload();
77938         });
77939     },
77940
77941     /**
77942      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
77943      * to perform application-specific processing.
77944      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
77945      * or instance of {@link Ext.form.action.Action} to perform.
77946      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
77947      * that will get created, if the <tt>action</tt> argument is a String.
77948      * <p>All of the config options listed below are supported by both the
77949      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
77950      * actions unless otherwise noted (custom actions could also accept
77951      * other config options):</p><ul>
77952      *
77953      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
77954      * to the form's {@link #url}.)</div></li>
77955      *
77956      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
77957      * to the form's method, or POST if not defined)</div></li>
77958      *
77959      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
77960      * (defaults to the form's baseParams, or none if not defined)</p>
77961      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
77962      *
77963      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
77964      *
77965      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
77966      * be invoked after a successful response (see top of
77967      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
77968      * for a description of what constitutes a successful response).
77969      * The function is passed the following parameters:<ul>
77970      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
77971      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
77972      * <div class="sub-desc">The action object contains these properties of interest:<ul>
77973      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
77974      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
77975      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
77976      * </ul></div></li></ul></div></li>
77977      *
77978      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
77979      * failed transaction attempt. The function is passed the following parameters:<ul>
77980      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
77981      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
77982      * <div class="sub-desc">The action object contains these properties of interest:<ul>
77983      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
77984      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
77985      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
77986      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
77987      * </ul></div></li></ul></div></li>
77988      *
77989      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
77990      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
77991      *
77992      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
77993      * Determines whether a Form's fields are validated in a final call to
77994      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
77995      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
77996      *
77997      * @return {Ext.form.Basic} this
77998      */
77999     doAction: function(action, options) {
78000         if (Ext.isString(action)) {
78001             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
78002         }
78003         if (this.fireEvent('beforeaction', this, action) !== false) {
78004             this.beforeAction(action);
78005             Ext.defer(action.run, 100, action);
78006         }
78007         return this;
78008     },
78009
78010     /**
78011      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
78012      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config is
78013      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
78014      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
78015      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
78016      * <p>The following code:</p><pre><code>
78017 myFormPanel.getForm().submit({
78018     clientValidation: true,
78019     url: 'updateConsignment.php',
78020     params: {
78021         newStatus: 'delivered'
78022     },
78023     success: function(form, action) {
78024        Ext.Msg.alert('Success', action.result.msg);
78025     },
78026     failure: function(form, action) {
78027         switch (action.failureType) {
78028             case Ext.form.action.Action.CLIENT_INVALID:
78029                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
78030                 break;
78031             case Ext.form.action.Action.CONNECT_FAILURE:
78032                 Ext.Msg.alert('Failure', 'Ajax communication failed');
78033                 break;
78034             case Ext.form.action.Action.SERVER_INVALID:
78035                Ext.Msg.alert('Failure', action.result.msg);
78036        }
78037     }
78038 });
78039 </code></pre>
78040      * would process the following server response for a successful submission:<pre><code>
78041 {
78042     "success":true, // note this is Boolean, not string
78043     "msg":"Consignment updated"
78044 }
78045 </code></pre>
78046      * and the following server response for a failed submission:<pre><code>
78047 {
78048     "success":false, // note this is Boolean, not string
78049     "msg":"You do not have permission to perform this operation"
78050 }
78051 </code></pre>
78052      * @return {Ext.form.Basic} this
78053      */
78054     submit: function(options) {
78055         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
78056     },
78057
78058     /**
78059      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
78060      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
78061      * @return {Ext.form.Basic} this
78062      */
78063     load: function(options) {
78064         return this.doAction(this.api ? 'directload' : 'load', options);
78065     },
78066
78067     /**
78068      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
78069      * @param {Ext.data.Model} record The record to edit
78070      * @return {Ext.form.Basic} this
78071      */
78072     updateRecord: function(record) {
78073         var fields = record.fields,
78074             values = this.getFieldValues(),
78075             name,
78076             obj = {};
78077
78078         fields.each(function(f) {
78079             name = f.name;
78080             if (name in values) {
78081                 obj[name] = values[name];
78082             }
78083         });
78084
78085         record.beginEdit();
78086         record.set(obj);
78087         record.endEdit();
78088
78089         return this;
78090     },
78091
78092     /**
78093      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
78094      * {@link Ext.data.Model#raw record data}.
78095      * See also {@link #trackResetOnLoad}.
78096      * @param {Ext.data.Model} record The record to load
78097      * @return {Ext.form.Basic} this
78098      */
78099     loadRecord: function(record) {
78100         this._record = record;
78101         return this.setValues(record.data);
78102     },
78103
78104     /**
78105      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
78106      * @return {Ext.data.Model} The record
78107      */
78108     getRecord: function() {
78109         return this._record;
78110     },
78111
78112     /**
78113      * @private
78114      * Called before an action is performed via {@link #doAction}.
78115      * @param {Ext.form.action.Action} action The Action instance that was invoked
78116      */
78117     beforeAction: function(action) {
78118         var waitMsg = action.waitMsg,
78119             maskCls = Ext.baseCSSPrefix + 'mask-loading',
78120             waitMsgTarget;
78121
78122         // Call HtmlEditor's syncValue before actions
78123         this.getFields().each(function(f) {
78124             if (f.isFormField && f.syncValue) {
78125                 f.syncValue();
78126             }
78127         });
78128
78129         if (waitMsg) {
78130             waitMsgTarget = this.waitMsgTarget;
78131             if (waitMsgTarget === true) {
78132                 this.owner.el.mask(waitMsg, maskCls);
78133             } else if (waitMsgTarget) {
78134                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
78135                 waitMsgTarget.mask(waitMsg, maskCls);
78136             } else {
78137                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
78138             }
78139         }
78140     },
78141
78142     /**
78143      * @private
78144      * Called after an action is performed via {@link #doAction}.
78145      * @param {Ext.form.action.Action} action The Action instance that was invoked
78146      * @param {Boolean} success True if the action completed successfully, false, otherwise.
78147      */
78148     afterAction: function(action, success) {
78149         if (action.waitMsg) {
78150             var MessageBox = Ext.MessageBox,
78151                 waitMsgTarget = this.waitMsgTarget;
78152             if (waitMsgTarget === true) {
78153                 this.owner.el.unmask();
78154             } else if (waitMsgTarget) {
78155                 waitMsgTarget.unmask();
78156             } else {
78157                 MessageBox.updateProgress(1);
78158                 MessageBox.hide();
78159             }
78160         }
78161         if (success) {
78162             if (action.reset) {
78163                 this.reset();
78164             }
78165             Ext.callback(action.success, action.scope || action, [this, action]);
78166             this.fireEvent('actioncomplete', this, action);
78167         } else {
78168             Ext.callback(action.failure, action.scope || action, [this, action]);
78169             this.fireEvent('actionfailed', this, action);
78170         }
78171     },
78172
78173
78174     /**
78175      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
78176      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
78177      * {@link Ext.form.field.Field#getName name or hiddenName}).
78178      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
78179      */
78180     findField: function(id) {
78181         return this.getFields().findBy(function(f) {
78182             return f.id === id || f.getName() === id;
78183         });
78184     },
78185
78186
78187     /**
78188      * Mark fields in this form invalid in bulk.
78189      * @param {Object/Object[]/Ext.data.Errors} errors
78190      * Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
78191      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
78192      * @return {Ext.form.Basic} this
78193      */
78194     markInvalid: function(errors) {
78195         var me = this;
78196
78197         function mark(fieldId, msg) {
78198             var field = me.findField(fieldId);
78199             if (field) {
78200                 field.markInvalid(msg);
78201             }
78202         }
78203
78204         if (Ext.isArray(errors)) {
78205             Ext.each(errors, function(err) {
78206                 mark(err.id, err.msg);
78207             });
78208         }
78209         else if (errors instanceof Ext.data.Errors) {
78210             errors.each(function(err) {
78211                 mark(err.field, err.message);
78212             });
78213         }
78214         else {
78215             Ext.iterate(errors, mark);
78216         }
78217         return this;
78218     },
78219
78220     /**
78221      * Set values for fields in this form in bulk.
78222      * @param {Object/Object[]} values Either an array in the form:<pre><code>
78223 [{id:'clientName', value:'Fred. Olsen Lines'},
78224  {id:'portOfLoading', value:'FXT'},
78225  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
78226      * or an object hash of the form:<pre><code>
78227 {
78228     clientName: 'Fred. Olsen Lines',
78229     portOfLoading: 'FXT',
78230     portOfDischarge: 'OSL'
78231 }</code></pre>
78232      * @return {Ext.form.Basic} this
78233      */
78234     setValues: function(values) {
78235         var me = this;
78236
78237         function setVal(fieldId, val) {
78238             var field = me.findField(fieldId);
78239             if (field) {
78240                 field.setValue(val);
78241                 if (me.trackResetOnLoad) {
78242                     field.resetOriginalValue();
78243                 }
78244             }
78245         }
78246
78247         if (Ext.isArray(values)) {
78248             // array of objects
78249             Ext.each(values, function(val) {
78250                 setVal(val.id, val.value);
78251             });
78252         } else {
78253             // object hash
78254             Ext.iterate(values, setVal);
78255         }
78256         return this;
78257     },
78258
78259     /**
78260      * Retrieves the fields in the form as a set of key/value pairs, using their
78261      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
78262      * If multiple fields return values under the same name those values will be combined into an Array.
78263      * This is similar to {@link #getFieldValues} except that this method collects only String values for
78264      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
78265      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
78266      * URL-encoded param string. Defaults to false.
78267      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
78268      * Defaults to false.
78269      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
78270      * Defaults to false.
78271      * @return {String/Object}
78272      */
78273     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
78274         var values = {};
78275
78276         this.getFields().each(function(field) {
78277             if (!dirtyOnly || field.isDirty()) {
78278                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
78279                 if (Ext.isObject(data)) {
78280                     Ext.iterate(data, function(name, val) {
78281                         if (includeEmptyText && val === '') {
78282                             val = field.emptyText || '';
78283                         }
78284                         if (name in values) {
78285                             var bucket = values[name],
78286                                 isArray = Ext.isArray;
78287                             if (!isArray(bucket)) {
78288                                 bucket = values[name] = [bucket];
78289                             }
78290                             if (isArray(val)) {
78291                                 values[name] = bucket.concat(val);
78292                             } else {
78293                                 bucket.push(val);
78294                             }
78295                         } else {
78296                             values[name] = val;
78297                         }
78298                     });
78299                 }
78300             }
78301         });
78302
78303         if (asString) {
78304             values = Ext.Object.toQueryString(values);
78305         }
78306         return values;
78307     },
78308
78309     /**
78310      * Retrieves the fields in the form as a set of key/value pairs, using their
78311      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
78312      * If multiple fields return values under the same name those values will be combined into an Array.
78313      * This is similar to {@link #getValues} except that this method collects type-specific data values
78314      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
78315      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
78316      * Defaults to false.
78317      * @return {Object}
78318      */
78319     getFieldValues: function(dirtyOnly) {
78320         return this.getValues(false, dirtyOnly, false, true);
78321     },
78322
78323     /**
78324      * Clears all invalid field messages in this form.
78325      * @return {Ext.form.Basic} this
78326      */
78327     clearInvalid: function() {
78328         var me = this;
78329         me.batchLayouts(function() {
78330             me.getFields().each(function(f) {
78331                 f.clearInvalid();
78332             });
78333         });
78334         return me;
78335     },
78336
78337     /**
78338      * Resets all fields in this form.
78339      * @return {Ext.form.Basic} this
78340      */
78341     reset: function() {
78342         var me = this;
78343         me.batchLayouts(function() {
78344             me.getFields().each(function(f) {
78345                 f.reset();
78346             });
78347         });
78348         return me;
78349     },
78350
78351     /**
78352      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
78353      * @param {Object} obj The object to be applied
78354      * @return {Ext.form.Basic} this
78355      */
78356     applyToFields: function(obj) {
78357         this.getFields().each(function(f) {
78358             Ext.apply(f, obj);
78359         });
78360         return this;
78361     },
78362
78363     /**
78364      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
78365      * @param {Object} obj The object to be applied
78366      * @return {Ext.form.Basic} this
78367      */
78368     applyIfToFields: function(obj) {
78369         this.getFields().each(function(f) {
78370             Ext.applyIf(f, obj);
78371         });
78372         return this;
78373     },
78374
78375     /**
78376      * @private
78377      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
78378      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
78379      * @param {Function} fn
78380      */
78381     batchLayouts: function(fn) {
78382         var me = this,
78383             suspended = new Ext.util.HashMap();
78384
78385         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
78386         me.getFields().each(function(field) {
78387             var ownerCt = field.ownerCt;
78388             if (!suspended.contains(ownerCt)) {
78389                 suspended.add(ownerCt);
78390                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
78391                 ownerCt.suspendLayout = true;
78392             }
78393         });
78394
78395         // Invoke the function
78396         fn();
78397
78398         // Un-suspend the container layouts
78399         suspended.each(function(id, ct) {
78400             ct.suspendLayout = ct.oldSuspendLayout;
78401             delete ct.oldSuspendLayout;
78402         });
78403
78404         // Trigger a single layout
78405         me.owner.doComponentLayout();
78406     }
78407 });
78408
78409 /**
78410  * @class Ext.form.FieldAncestor
78411
78412 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
78413 items subtree. Adds the following capabilities:
78414
78415 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
78416   instances at any depth within the container.
78417 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
78418   of individual fields at the container level.
78419 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
78420   container, to facilitate uniform configuration of all fields.
78421
78422 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
78423 and should not normally need to be used directly.
78424
78425  * @markdown
78426  * @docauthor Jason Johnston <jason@sencha.com>
78427  */
78428 Ext.define('Ext.form.FieldAncestor', {
78429
78430     /**
78431      * @cfg {Object} fieldDefaults
78432      * <p>If specified, the properties in this object are used as default config values for each
78433      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
78434      * that is added as a descendant of this container. Corresponding values specified in an individual field's
78435      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
78436      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
78437      * options may be specified in the <tt>fieldDefaults</tt>.</p>
78438      * <p>Example:</p>
78439      * <pre><code>new Ext.form.Panel({
78440     fieldDefaults: {
78441         labelAlign: 'left',
78442         labelWidth: 100
78443     },
78444     items: [{
78445         xtype: 'fieldset',
78446         defaults: {
78447             labelAlign: 'top'
78448         },
78449         items: [{
78450             name: 'field1'
78451         }, {
78452             name: 'field2'
78453         }]
78454     }, {
78455         xtype: 'fieldset',
78456         items: [{
78457             name: 'field3',
78458             labelWidth: 150
78459         }, {
78460             name: 'field4'
78461         }]
78462     }]
78463 });</code></pre>
78464      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
78465      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
78466      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
78467      */
78468
78469
78470     /**
78471      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
78472      * of any components importing this mixin.
78473      */
78474     initFieldAncestor: function() {
78475         var me = this,
78476             onSubtreeChange = me.onFieldAncestorSubtreeChange;
78477
78478         me.addEvents(
78479             /**
78480              * @event fieldvaliditychange
78481              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
78482              * container changes.
78483              * @param {Ext.form.FieldAncestor} this
78484              * @param {Ext.form.Labelable} The Field instance whose validity changed
78485              * @param {String} isValid The field's new validity state
78486              */
78487             'fieldvaliditychange',
78488
78489             /**
78490              * @event fielderrorchange
78491              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
78492              * instances within this container.
78493              * @param {Ext.form.FieldAncestor} this
78494              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
78495              * @param {String} error The active error message
78496              */
78497             'fielderrorchange'
78498         );
78499
78500         // Catch addition and removal of descendant fields
78501         me.on('add', onSubtreeChange, me);
78502         me.on('remove', onSubtreeChange, me);
78503
78504         me.initFieldDefaults();
78505     },
78506
78507     /**
78508      * @private Initialize the {@link #fieldDefaults} object
78509      */
78510     initFieldDefaults: function() {
78511         if (!this.fieldDefaults) {
78512             this.fieldDefaults = {};
78513         }
78514     },
78515
78516     /**
78517      * @private
78518      * Handle the addition and removal of components in the FieldAncestor component's child tree.
78519      */
78520     onFieldAncestorSubtreeChange: function(parent, child) {
78521         var me = this,
78522             isAdding = !!child.ownerCt;
78523
78524         function handleCmp(cmp) {
78525             var isLabelable = cmp.isFieldLabelable,
78526                 isField = cmp.isFormField;
78527             if (isLabelable || isField) {
78528                 if (isLabelable) {
78529                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
78530                 }
78531                 if (isField) {
78532                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
78533                 }
78534             }
78535             else if (cmp.isContainer) {
78536                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
78537             }
78538         }
78539         handleCmp(child);
78540     },
78541
78542     /**
78543      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
78544      * @param {Ext.form.Labelable} labelable The instance that was added
78545      */
78546     onLabelableAdded: function(labelable) {
78547         var me = this;
78548
78549         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
78550         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
78551
78552         labelable.setFieldDefaults(me.fieldDefaults);
78553     },
78554
78555     /**
78556      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
78557      * @param {Ext.form.field.Field} field The field which was added
78558      */
78559     onFieldAdded: function(field) {
78560         var me = this;
78561         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
78562     },
78563
78564     /**
78565      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
78566      * @param {Ext.form.Labelable} labelable The instance that was removed
78567      */
78568     onLabelableRemoved: function(labelable) {
78569         var me = this;
78570         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
78571     },
78572
78573     /**
78574      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
78575      * @param {Ext.form.field.Field} field The field which was removed
78576      */
78577     onFieldRemoved: function(field) {
78578         var me = this;
78579         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
78580     },
78581
78582     /**
78583      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
78584      */
78585     handleFieldValidityChange: function(field, isValid) {
78586         var me = this;
78587         me.fireEvent('fieldvaliditychange', me, field, isValid);
78588         me.onFieldValidityChange();
78589     },
78590
78591     /**
78592      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
78593      */
78594     handleFieldErrorChange: function(labelable, activeError) {
78595         var me = this;
78596         me.fireEvent('fielderrorchange', me, labelable, activeError);
78597         me.onFieldErrorChange();
78598     },
78599
78600     /**
78601      * @protected Fired when the validity of any field within the container changes.
78602      * @param {Ext.form.field.Field} The sub-field whose validity changed
78603      * @param {String} The new validity state
78604      */
78605     onFieldValidityChange: Ext.emptyFn,
78606
78607     /**
78608      * @protected Fired when the error message of any field within the container changes.
78609      * @param {Ext.form.Labelable} The sub-field whose active error changed
78610      * @param {String} The new active error message
78611      */
78612     onFieldErrorChange: Ext.emptyFn
78613
78614 });
78615 /**
78616  * @class Ext.layout.container.CheckboxGroup
78617  * @extends Ext.layout.container.Container
78618  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
78619  * It groups the component's sub-items into columns based on the component's
78620  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
78621  *
78622  */
78623 Ext.define('Ext.layout.container.CheckboxGroup', {
78624     extend: 'Ext.layout.container.Container',
78625     alias: ['layout.checkboxgroup'],
78626
78627
78628     onLayout: function() {
78629         var numCols = this.getColCount(),
78630             shadowCt = this.getShadowCt(),
78631             owner = this.owner,
78632             items = owner.items,
78633             shadowItems = shadowCt.items,
78634             numItems = items.length,
78635             colIndex = 0,
78636             i, numRows;
78637
78638         // Distribute the items into the appropriate column containers. We add directly to the
78639         // containers' items collection rather than calling container.add(), because we need the
78640         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
78641         // in case items have been added, removed, or reordered.
78642
78643         shadowItems.each(function(col) {
78644             col.items.clear();
78645         });
78646
78647         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
78648         // from the CheckboxGroup; adjust to match.
78649         while (shadowItems.length > numCols) {
78650             shadowCt.remove(shadowItems.last());
78651         }
78652         while (shadowItems.length < numCols) {
78653             shadowCt.add({
78654                 xtype: 'container',
78655                 cls: owner.groupCls,
78656                 flex: 1
78657             });
78658         }
78659
78660         if (owner.vertical) {
78661             numRows = Math.ceil(numItems / numCols);
78662             for (i = 0; i < numItems; i++) {
78663                 if (i > 0 && i % numRows === 0) {
78664                     colIndex++;
78665                 }
78666                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
78667             }
78668         } else {
78669             for (i = 0; i < numItems; i++) {
78670                 colIndex = i % numCols;
78671                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
78672             }
78673         }
78674
78675         if (!shadowCt.rendered) {
78676             shadowCt.render(this.getRenderTarget());
78677         } else {
78678             // Ensure all items are rendered in the correct place in the correct column - this won't
78679             // get done by the column containers themselves if their dimensions are not changing.
78680             shadowItems.each(function(col) {
78681                 var layout = col.getLayout();
78682                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
78683             });
78684         }
78685
78686         shadowCt.doComponentLayout();
78687     },
78688
78689
78690     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
78691     renderItems: Ext.emptyFn,
78692
78693
78694     /**
78695      * @private
78696      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
78697      * into columns.
78698      */
78699     getShadowCt: function() {
78700         var me = this,
78701             shadowCt = me.shadowCt,
78702             owner, items, item, columns, columnsIsArray, numCols, i;
78703
78704         if (!shadowCt) {
78705             // Create the column containers based on the owner's 'columns' config
78706             owner = me.owner;
78707             columns = owner.columns;
78708             columnsIsArray = Ext.isArray(columns);
78709             numCols = me.getColCount();
78710             items = [];
78711             for(i = 0; i < numCols; i++) {
78712                 item = {
78713                     xtype: 'container',
78714                     cls: owner.groupCls
78715                 };
78716                 if (columnsIsArray) {
78717                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
78718                     // numbers, used as relative flex values.
78719                     if (columns[i] < 1) {
78720                         item.flex = columns[i];
78721                     } else {
78722                         item.width = columns[i];
78723                     }
78724                 }
78725                 else {
78726                     // All columns the same width
78727                     item.flex = 1;
78728                 }
78729                 items.push(item);
78730             }
78731
78732             // Create the shadow container; delay rendering until after items are added to the columns
78733             shadowCt = me.shadowCt = Ext.createWidget('container', {
78734                 layout: 'hbox',
78735                 items: items,
78736                 ownerCt: owner
78737             });
78738         }
78739         
78740         return shadowCt;
78741     },
78742
78743
78744     /**
78745      * @private Get the number of columns in the checkbox group
78746      */
78747     getColCount: function() {
78748         var owner = this.owner,
78749             colsCfg = owner.columns;
78750         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
78751     }
78752
78753 });
78754
78755 /**
78756  * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
78757  * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
78758  * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
78759  * This is useful for arranging a group of fields or other components within a single item in a form, so
78760  * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
78761  * a single label in a form.
78762  * 
78763  * The container's configured {@link #items} will be layed out within the field body area according to the
78764  * configured {@link #layout} type. The default layout is `'autocontainer'`.
78765  * 
78766  * Like regular fields, FieldContainer can inherit its decoration configuration from the
78767  * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
78768  * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
78769  * it may itself contain.
78770  * 
78771  * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
78772  * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
78773  * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
78774  *
78775  * # Example
78776  * 
78777  *     @example
78778  *     Ext.create('Ext.form.Panel', {
78779  *         title: 'FieldContainer Example',
78780  *         width: 550,
78781  *         bodyPadding: 10,
78782  * 
78783  *         items: [{
78784  *             xtype: 'fieldcontainer',
78785  *             fieldLabel: 'Last Three Jobs',
78786  *             labelWidth: 100,
78787  * 
78788  *             // The body area will contain three text fields, arranged
78789  *             // horizontally, separated by draggable splitters.
78790  *             layout: 'hbox',
78791  *             items: [{
78792  *                 xtype: 'textfield',
78793  *                 flex: 1
78794  *             }, {
78795  *                 xtype: 'splitter'
78796  *             }, {
78797  *                 xtype: 'textfield',
78798  *                 flex: 1
78799  *             }, {
78800  *                 xtype: 'splitter'
78801  *             }, {
78802  *                 xtype: 'textfield',
78803  *                 flex: 1
78804  *             }]
78805  *         }],
78806  *         renderTo: Ext.getBody()
78807  *     });
78808  * 
78809  * # Usage of fieldDefaults
78810  *
78811  *     @example
78812  *     Ext.create('Ext.form.Panel', {
78813  *         title: 'FieldContainer Example',
78814  *         width: 350,
78815  *         bodyPadding: 10,
78816  * 
78817  *         items: [{
78818  *             xtype: 'fieldcontainer',
78819  *             fieldLabel: 'Your Name',
78820  *             labelWidth: 75,
78821  *             defaultType: 'textfield',
78822  * 
78823  *             // Arrange fields vertically, stretched to full width
78824  *             layout: 'anchor',
78825  *             defaults: {
78826  *                 layout: '100%'
78827  *             },
78828  * 
78829  *             // These config values will be applied to both sub-fields, except
78830  *             // for Last Name which will use its own msgTarget.
78831  *             fieldDefaults: {
78832  *                 msgTarget: 'under',
78833  *                 labelAlign: 'top'
78834  *             },
78835  * 
78836  *             items: [{
78837  *                 fieldLabel: 'First Name',
78838  *                 name: 'firstName'
78839  *             }, {
78840  *                 fieldLabel: 'Last Name',
78841  *                 name: 'lastName',
78842  *                 msgTarget: 'under'
78843  *             }]
78844  *         }],
78845  *         renderTo: Ext.getBody()
78846  *     });
78847  * 
78848  * @docauthor Jason Johnston <jason@sencha.com>
78849  */
78850 Ext.define('Ext.form.FieldContainer', {
78851     extend: 'Ext.container.Container',
78852     mixins: {
78853         labelable: 'Ext.form.Labelable',
78854         fieldAncestor: 'Ext.form.FieldAncestor'
78855     },
78856     alias: 'widget.fieldcontainer',
78857
78858     componentLayout: 'field',
78859
78860     /**
78861      * @cfg {Boolean} combineLabels
78862      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
78863      * generate its label by combining the labels of all the fields it contains. Defaults to false.
78864      */
78865     combineLabels: false,
78866
78867     /**
78868      * @cfg {String} labelConnector
78869      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
78870      * set to true. Defaults to ', '.
78871      */
78872     labelConnector: ', ',
78873
78874     /**
78875      * @cfg {Boolean} combineErrors
78876      * If set to true, the field container will automatically combine and display the validation errors from
78877      * all the fields it contains as a single error on the container, according to the configured
78878      * {@link #msgTarget}. Defaults to false.
78879      */
78880     combineErrors: false,
78881
78882     maskOnDisable: false,
78883
78884     initComponent: function() {
78885         var me = this,
78886             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
78887
78888         // Init mixins
78889         me.initLabelable();
78890         me.initFieldAncestor();
78891
78892         me.callParent();
78893     },
78894
78895     /**
78896      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
78897      * @param {Ext.form.Labelable} labelable The instance that was added
78898      */
78899     onLabelableAdded: function(labelable) {
78900         var me = this;
78901         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
78902         me.updateLabel();
78903     },
78904
78905     /**
78906      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
78907      * @param {Ext.form.Labelable} labelable The instance that was removed
78908      */
78909     onLabelableRemoved: function(labelable) {
78910         var me = this;
78911         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
78912         me.updateLabel();
78913     },
78914
78915     onRender: function() {
78916         var me = this;
78917
78918         me.onLabelableRender();
78919
78920         me.callParent(arguments);
78921     },
78922
78923     initRenderTpl: function() {
78924         var me = this;
78925         if (!me.hasOwnProperty('renderTpl')) {
78926             me.renderTpl = me.getTpl('labelableRenderTpl');
78927         }
78928         return me.callParent();
78929     },
78930
78931     initRenderData: function() {
78932         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
78933     },
78934
78935     /**
78936      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
78937      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
78938      * this method to provide a custom generated label.
78939      */
78940     getFieldLabel: function() {
78941         var label = this.fieldLabel || '';
78942         if (!label && this.combineLabels) {
78943             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
78944                 return field.getFieldLabel();
78945             }).join(this.labelConnector);
78946         }
78947         return label;
78948     },
78949
78950     /**
78951      * @private Updates the content of the labelEl if it is rendered
78952      */
78953     updateLabel: function() {
78954         var me = this,
78955             label = me.labelEl;
78956         if (label) {
78957             label.update(me.getFieldLabel());
78958         }
78959     },
78960
78961
78962     /**
78963      * @private Fired when the error message of any field within the container changes, and updates the
78964      * combined error message to match.
78965      */
78966     onFieldErrorChange: function(field, activeError) {
78967         if (this.combineErrors) {
78968             var me = this,
78969                 oldError = me.getActiveError(),
78970                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
78971                     return field.hasActiveError();
78972                 }),
78973                 newErrors = me.getCombinedErrors(invalidFields);
78974
78975             if (newErrors) {
78976                 me.setActiveErrors(newErrors);
78977             } else {
78978                 me.unsetActiveError();
78979             }
78980
78981             if (oldError !== me.getActiveError()) {
78982                 me.doComponentLayout();
78983             }
78984         }
78985     },
78986
78987     /**
78988      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
78989      * messages from them. Defaults to prepending each message by the field name and a colon. This
78990      * can be overridden to provide custom combined error message handling, for instance changing
78991      * the format of each message or sorting the array (it is sorted in order of appearance by default).
78992      * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
78993      * @return {String[]} The combined list of error messages
78994      */
78995     getCombinedErrors: function(invalidFields) {
78996         var forEach = Ext.Array.forEach,
78997             errors = [];
78998         forEach(invalidFields, function(field) {
78999             forEach(field.getActiveErrors(), function(error) {
79000                 var label = field.getFieldLabel();
79001                 errors.push((label ? label + ': ' : '') + error);
79002             });
79003         });
79004         return errors;
79005     },
79006
79007     getTargetEl: function() {
79008         return this.bodyEl || this.callParent();
79009     }
79010 });
79011
79012 /**
79013  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
79014  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
79015  * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
79016  * and {@link #validate validating} the group of checkboxes as a whole.
79017  *
79018  * # Validation
79019  *
79020  * Individual checkbox fields themselves have no default validation behavior, but
79021  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
79022  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
79023  * least one of the checkboxes, the entire group will be highlighted as invalid and the
79024  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
79025  *
79026  * # Layout
79027  *
79028  * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
79029  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
79030  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
79031  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
79032  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
79033  *
79034  *     @example
79035  *     Ext.create('Ext.form.Panel', {
79036  *         title: 'Checkbox Group',
79037  *         width: 300,
79038  *         height: 125,
79039  *         bodyPadding: 10,
79040  *         renderTo: Ext.getBody(),
79041  *         items:[{
79042  *             xtype: 'checkboxgroup',
79043  *             fieldLabel: 'Two Columns',
79044  *             // Arrange radio buttons into two columns, distributed vertically
79045  *             columns: 2,
79046  *             vertical: true,
79047  *             items: [
79048  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
79049  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
79050  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
79051  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
79052  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
79053  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
79054  *             ]
79055  *         }]
79056  *     });
79057  */
79058 Ext.define('Ext.form.CheckboxGroup', {
79059     extend:'Ext.form.FieldContainer',
79060     mixins: {
79061         field: 'Ext.form.field.Field'
79062     },
79063     alias: 'widget.checkboxgroup',
79064     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
79065
79066     /**
79067      * @cfg {String} name
79068      * @hide
79069      */
79070
79071     /**
79072      * @cfg {Ext.form.field.Checkbox[]/Object[]} items
79073      * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
79074      */
79075
79076     /**
79077      * @cfg {String/Number/Number[]} columns
79078      * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
79079      * This config can take several types of values:
79080      *
79081      * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
79082      *   distributed based on the width of the overall field container. This is the default.
79083      * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
79084      *   will be automatically distributed based on the value of {@link #vertical}.
79085      * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
79086      *   width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
79087      *   values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
79088      *   (100%) although if you want the controls to take up the entire field container you should do so.
79089      */
79090     columns : 'auto',
79091
79092     /**
79093      * @cfg {Boolean} vertical
79094      * True to distribute contained controls across columns, completely filling each column top to bottom before
79095      * starting on the next column. The number of controls in each column will be automatically calculated to keep
79096      * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
79097      * completely filling each row left to right before starting on the next row.
79098      */
79099     vertical : false,
79100
79101     /**
79102      * @cfg {Boolean} allowBlank
79103      * False to validate that at least one item in the group is checked. If no items are selected at
79104      * validation time, {@link #blankText} will be used as the error text.
79105      */
79106     allowBlank : true,
79107
79108     /**
79109      * @cfg {String} blankText
79110      * Error text to display if the {@link #allowBlank} validation fails
79111      */
79112     blankText : "You must select at least one item in this group",
79113
79114     // private
79115     defaultType : 'checkboxfield',
79116
79117     // private
79118     groupCls : Ext.baseCSSPrefix + 'form-check-group',
79119
79120     /**
79121      * @cfg {String} fieldBodyCls
79122      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
79123      * Defaults to 'x-form-checkboxgroup-body'.
79124      */
79125     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
79126
79127     // private
79128     layout: 'checkboxgroup',
79129
79130     initComponent: function() {
79131         var me = this;
79132         me.callParent();
79133         me.initField();
79134     },
79135
79136     /**
79137      * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
79138      * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
79139      * after they have been initialized.
79140      * @protected
79141      */
79142     initValue: function() {
79143         var me = this,
79144             valueCfg = me.value;
79145         me.originalValue = me.lastValue = valueCfg || me.getValue();
79146         if (valueCfg) {
79147             me.setValue(valueCfg);
79148         }
79149     },
79150
79151     /**
79152      * When a checkbox is added to the group, monitor it for changes
79153      * @param {Object} field
79154      * @protected
79155      */
79156     onFieldAdded: function(field) {
79157         var me = this;
79158         if (field.isCheckbox) {
79159             me.mon(field, 'change', me.checkChange, me);
79160         }
79161         me.callParent(arguments);
79162     },
79163
79164     onFieldRemoved: function(field) {
79165         var me = this;
79166         if (field.isCheckbox) {
79167             me.mun(field, 'change', me.checkChange, me);
79168         }
79169         me.callParent(arguments);
79170     },
79171
79172     // private override - the group value is a complex object, compare using object serialization
79173     isEqual: function(value1, value2) {
79174         var toQueryString = Ext.Object.toQueryString;
79175         return toQueryString(value1) === toQueryString(value2);
79176     },
79177
79178     /**
79179      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
79180      * is set to true and no items are checked.
79181      * @return {String[]} Array of all validation errors
79182      */
79183     getErrors: function() {
79184         var errors = [];
79185         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
79186             errors.push(this.blankText);
79187         }
79188         return errors;
79189     },
79190
79191     /**
79192      * @private Returns all checkbox components within the container
79193      */
79194     getBoxes: function() {
79195         return this.query('[isCheckbox]');
79196     },
79197
79198     /**
79199      * @private Convenience function which calls the given function for every checkbox in the group
79200      * @param {Function} fn The function to call
79201      * @param {Object} scope (Optional) scope object
79202      */
79203     eachBox: function(fn, scope) {
79204         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
79205     },
79206
79207     /**
79208      * Returns an Array of all checkboxes in the container which are currently checked
79209      * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
79210      */
79211     getChecked: function() {
79212         return Ext.Array.filter(this.getBoxes(), function(cb) {
79213             return cb.getValue();
79214         });
79215     },
79216
79217     // private override
79218     isDirty: function(){
79219         return Ext.Array.some(this.getBoxes(), function(cb) {
79220             return cb.isDirty();
79221         });
79222     },
79223
79224     // private override
79225     setReadOnly: function(readOnly) {
79226         this.eachBox(function(cb) {
79227             cb.setReadOnly(readOnly);
79228         });
79229         this.readOnly = readOnly;
79230     },
79231
79232     /**
79233      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
79234      * loaded values and clears any validation messages.
79235      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
79236      */
79237     reset: function() {
79238         var me = this,
79239             hadError = me.hasActiveError(),
79240             preventMark = me.preventMark;
79241         me.preventMark = true;
79242         me.batchChanges(function() {
79243             me.eachBox(function(cb) {
79244                 cb.reset();
79245             });
79246         });
79247         me.preventMark = preventMark;
79248         me.unsetActiveError();
79249         if (hadError) {
79250             me.doComponentLayout();
79251         }
79252     },
79253
79254     // private override
79255     resetOriginalValue: function() {
79256         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
79257         // the correct data from getValue()
79258         Ext.defer(function() {
79259             this.callParent();
79260         }, 1, this);
79261     },
79262
79263
79264     /**
79265      * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
79266      * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
79267      *
79268      *   - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
79269      *     See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
79270      *   - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
79271      *     of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
79272      *     checked and others will be unchecked.
79273      *
79274      * If a checkbox's name is not in the mapping at all, it will be unchecked.
79275      *
79276      * An example:
79277      *
79278      *     var myCheckboxGroup = new Ext.form.CheckboxGroup({
79279      *         columns: 3,
79280      *         items: [{
79281      *             name: 'cb1',
79282      *             boxLabel: 'Single 1'
79283      *         }, {
79284      *             name: 'cb2',
79285      *             boxLabel: 'Single 2'
79286      *         }, {
79287      *             name: 'cb3',
79288      *             boxLabel: 'Single 3'
79289      *         }, {
79290      *             name: 'cbGroup',
79291      *             boxLabel: 'Grouped 1'
79292      *             inputValue: 'value1'
79293      *         }, {
79294      *             name: 'cbGroup',
79295      *             boxLabel: 'Grouped 2'
79296      *             inputValue: 'value2'
79297      *         }, {
79298      *             name: 'cbGroup',
79299      *             boxLabel: 'Grouped 3'
79300      *             inputValue: 'value3'
79301      *         }]
79302      *     });
79303      *
79304      *     myCheckboxGroup.setValue({
79305      *         cb1: true,
79306      *         cb3: false,
79307      *         cbGroup: ['value1', 'value3']
79308      *     });
79309      *
79310      * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
79311      * 'cbGroup'. The other three checkboxes will be unchecked.
79312      *
79313      * @param {Object} value The mapping of checkbox names to values.
79314      * @return {Ext.form.CheckboxGroup} this
79315      */
79316     setValue: function(value) {
79317         var me = this;
79318         me.batchChanges(function() {
79319             me.eachBox(function(cb) {
79320                 var name = cb.getName(),
79321                     cbValue = false;
79322                 if (value && name in value) {
79323                     if (Ext.isArray(value[name])) {
79324                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
79325                     } else {
79326                         // single value, let the checkbox's own setValue handle conversion
79327                         cbValue = value[name];
79328                     }
79329                 }
79330                 cb.setValue(cbValue);
79331             });
79332         });
79333         return me;
79334     },
79335
79336
79337     /**
79338      * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
79339      * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
79340      * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
79341      * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
79342      * will be an Array of the selected inputValues.
79343      *
79344      * The object format returned from this method can also be passed directly to the {@link #setValue} method.
79345      *
79346      * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
79347      * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
79348      * Ext 4+, use the {@link #getChecked} method instead.
79349      */
79350     getValue: function() {
79351         var values = {};
79352         this.eachBox(function(cb) {
79353             var name = cb.getName(),
79354                 inputValue = cb.inputValue,
79355                 bucket;
79356             if (cb.getValue()) {
79357                 if (name in values) {
79358                     bucket = values[name];
79359                     if (!Ext.isArray(bucket)) {
79360                         bucket = values[name] = [bucket];
79361                     }
79362                     bucket.push(inputValue);
79363                 } else {
79364                     values[name] = inputValue;
79365                 }
79366             }
79367         });
79368         return values;
79369     },
79370
79371     /*
79372      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
79373      */
79374     getSubmitData: function() {
79375         return null;
79376     },
79377
79378     /*
79379      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
79380      */
79381     getModelData: function() {
79382         return null;
79383     },
79384
79385     validate: function() {
79386         var me = this,
79387             errors = me.getErrors(),
79388             isValid = Ext.isEmpty(errors),
79389             wasValid = !me.hasActiveError();
79390
79391         if (isValid) {
79392             me.unsetActiveError();
79393         } else {
79394             me.setActiveError(errors);
79395         }
79396         if (isValid !== wasValid) {
79397             me.fireEvent('validitychange', me, isValid);
79398             me.doComponentLayout();
79399         }
79400
79401         return isValid;
79402     }
79403
79404 }, function() {
79405
79406     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
79407
79408 });
79409
79410
79411 /**
79412  * @private
79413  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
79414  */
79415 Ext.define('Ext.form.CheckboxManager', {
79416     extend: 'Ext.util.MixedCollection',
79417     singleton: true,
79418
79419     getByName: function(name) {
79420         return this.filterBy(function(item) {
79421             return item.name == name;
79422         });
79423     },
79424
79425     getWithValue: function(name, value) {
79426         return this.filterBy(function(item) {
79427             return item.name == name && item.inputValue == value;
79428         });
79429     },
79430
79431     getChecked: function(name) {
79432         return this.filterBy(function(item) {
79433             return item.name == name && item.checked;
79434         });
79435     }
79436 });
79437
79438 /**
79439  * @docauthor Jason Johnston <jason@sencha.com>
79440  *
79441  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
79442  * config will be rendered as the fieldset's `legend`.
79443  *
79444  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
79445  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
79446  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
79447  * layout type.
79448  *
79449  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
79450  *
79451  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
79452  *    the {@link #title legend title}, or:
79453  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
79454  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
79455  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
79456  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
79457  *
79458  * # Example usage
79459  *
79460  *     @example
79461  *     Ext.create('Ext.form.Panel', {
79462  *         title: 'Simple Form with FieldSets',
79463  *         labelWidth: 75, // label settings here cascade unless overridden
79464  *         url: 'save-form.php',
79465  *         frame: true,
79466  *         bodyStyle: 'padding:5px 5px 0',
79467  *         width: 550,
79468  *         renderTo: Ext.getBody(),
79469  *         layout: 'column', // arrange fieldsets side by side
79470  *         defaults: {
79471  *             bodyPadding: 4
79472  *         },
79473  *         items: [{
79474  *             // Fieldset in Column 1 - collapsible via toggle button
79475  *             xtype:'fieldset',
79476  *             columnWidth: 0.5,
79477  *             title: 'Fieldset 1',
79478  *             collapsible: true,
79479  *             defaultType: 'textfield',
79480  *             defaults: {anchor: '100%'},
79481  *             layout: 'anchor',
79482  *             items :[{
79483  *                 fieldLabel: 'Field 1',
79484  *                 name: 'field1'
79485  *             }, {
79486  *                 fieldLabel: 'Field 2',
79487  *                 name: 'field2'
79488  *             }]
79489  *         }, {
79490  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
79491  *             xtype:'fieldset',
79492  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
79493  *             columnWidth: 0.5,
79494  *             checkboxToggle: true,
79495  *             collapsed: true, // fieldset initially collapsed
79496  *             layout:'anchor',
79497  *             items :[{
79498  *                 xtype: 'panel',
79499  *                 anchor: '100%',
79500  *                 title: 'Panel inside a fieldset',
79501  *                 frame: true,
79502  *                 height: 52
79503  *             }]
79504  *         }]
79505  *     });
79506  */
79507 Ext.define('Ext.form.FieldSet', {
79508     extend: 'Ext.container.Container',
79509     alias: 'widget.fieldset',
79510     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
79511
79512     /**
79513      * @cfg {String} title
79514      * A title to be displayed in the fieldset's legend. May contain HTML markup.
79515      */
79516
79517     /**
79518      * @cfg {Boolean} [checkboxToggle=false]
79519      * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
79520      * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
79521      * the {@link #checkboxName}.
79522      */
79523
79524     /**
79525      * @cfg {String} checkboxName
79526      * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
79527      * (defaults to '[fieldset id]-checkbox').
79528      */
79529
79530     /**
79531      * @cfg {Boolean} [collapsible=false]
79532      * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
79533      * into the legend element, false to keep the fieldset statically sized with no collapse button.
79534      * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
79535      * fieldset by default.
79536      */
79537
79538     /**
79539      * @cfg {Boolean} collapsed
79540      * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
79541      * will also be unchecked by default.
79542      */
79543     collapsed: false,
79544
79545     /**
79546      * @property {Ext.Component} legend
79547      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
79548      * created, by setting the {@link #title} or {@link #checkboxToggle} options.
79549      */
79550
79551     /**
79552      * @cfg {String} [baseCls='x-fieldset']
79553      * The base CSS class applied to the fieldset.
79554      */
79555     baseCls: Ext.baseCSSPrefix + 'fieldset',
79556
79557     /**
79558      * @cfg {String} layout
79559      * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
79560      */
79561     layout: 'anchor',
79562
79563     componentLayout: 'fieldset',
79564
79565     // No aria role necessary as fieldset has its own recognized semantics
79566     ariaRole: '',
79567
79568     renderTpl: ['<div id="{id}-body" class="{baseCls}-body"></div>'],
79569
79570     maskOnDisable: false,
79571
79572     getElConfig: function(){
79573         return {tag: 'fieldset', id: this.id};
79574     },
79575
79576     initComponent: function() {
79577         var me = this,
79578             baseCls = me.baseCls;
79579
79580         me.callParent();
79581
79582         // Create the Legend component if needed
79583         me.initLegend();
79584
79585         // Add body el
79586         me.addChildEls('body');
79587
79588         if (me.collapsed) {
79589             me.addCls(baseCls + '-collapsed');
79590             me.collapse();
79591         }
79592     },
79593
79594     // private
79595     onRender: function(container, position) {
79596         this.callParent(arguments);
79597         // Make sure the legend is created and rendered
79598         this.initLegend();
79599     },
79600
79601     /**
79602      * @private
79603      * Initialize and render the legend component if necessary
79604      */
79605     initLegend: function() {
79606         var me = this,
79607             legendItems,
79608             legend = me.legend;
79609
79610         // Create the legend component if needed and it hasn't been already
79611         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
79612             legendItems = [];
79613
79614             // Checkbox
79615             if (me.checkboxToggle) {
79616                 legendItems.push(me.createCheckboxCmp());
79617             }
79618             // Toggle button
79619             else if (me.collapsible) {
79620                 legendItems.push(me.createToggleCmp());
79621             }
79622
79623             // Title
79624             legendItems.push(me.createTitleCmp());
79625
79626             legend = me.legend = Ext.create('Ext.container.Container', {
79627                 baseCls: me.baseCls + '-header',
79628                 ariaRole: '',
79629                 ownerCt: this,
79630                 getElConfig: function(){
79631                     var result = {
79632                         tag: 'legend',
79633                         cls: this.baseCls
79634                     };
79635
79636                     // Gecko3 will kick every <div> out of <legend> and mess up every thing.
79637                     // So here we change every <div> into <span>s. Therefore the following
79638                     // clearer is not needed and since div introduces a lot of subsequent
79639                     // problems, it is actually harmful.
79640                     if (!Ext.isGecko3) {
79641                         result.children = [{
79642                             cls: Ext.baseCSSPrefix + 'clear'
79643                         }];
79644                     }
79645                     return result;
79646                 },
79647                 items: legendItems
79648             });
79649         }
79650
79651         // Make sure legend is rendered if the fieldset is rendered
79652         if (legend && !legend.rendered && me.rendered) {
79653             me.legend.render(me.el, me.body); //insert before body element
79654         }
79655     },
79656
79657     /**
79658      * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
79659      * customize the title component.
79660      * @return Ext.Component
79661      * @protected
79662      */
79663     createTitleCmp: function() {
79664         var me = this;
79665         me.titleCmp = Ext.create('Ext.Component', {
79666             html: me.title,
79667             getElConfig: function() {
79668                 return {
79669                     tag: Ext.isGecko3 ? 'span' : 'div',
79670                     cls: me.titleCmp.cls,
79671                     id: me.titleCmp.id
79672                 };
79673             },
79674             cls: me.baseCls + '-header-text'
79675         });
79676         return me.titleCmp;
79677     },
79678
79679     /**
79680      * @property {Ext.form.field.Checkbox} checkboxCmp
79681      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
79682      * populated if the fieldset is configured with {@link #checkboxToggle}:true.
79683      */
79684
79685     /**
79686      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
79687      * customize the checkbox's configuration or even return an entirely different component type.
79688      * @return Ext.Component
79689      * @protected
79690      */
79691     createCheckboxCmp: function() {
79692         var me = this,
79693             suffix = '-checkbox';
79694
79695         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
79696             getElConfig: function() {
79697                 return {
79698                     tag: Ext.isGecko3 ? 'span' : 'div',
79699                     id: me.checkboxCmp.id,
79700                     cls: me.checkboxCmp.cls
79701                 };
79702             },
79703             name: me.checkboxName || me.id + suffix,
79704             cls: me.baseCls + '-header' + suffix,
79705             checked: !me.collapsed,
79706             listeners: {
79707                 change: me.onCheckChange,
79708                 scope: me
79709             }
79710         });
79711         return me.checkboxCmp;
79712     },
79713
79714     /**
79715      * @property {Ext.panel.Tool} toggleCmp
79716      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
79717      * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
79718      */
79719
79720     /**
79721      * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
79722      * customize the toggle component.
79723      * @return Ext.Component
79724      * @protected
79725      */
79726     createToggleCmp: function() {
79727         var me = this;
79728         me.toggleCmp = Ext.create('Ext.panel.Tool', {
79729             getElConfig: function() {
79730                 return {
79731                     tag: Ext.isGecko3 ? 'span' : 'div',
79732                     id: me.toggleCmp.id,
79733                     cls: me.toggleCmp.cls
79734                 };
79735             },
79736             type: 'toggle',
79737             handler: me.toggle,
79738             scope: me
79739         });
79740         return me.toggleCmp;
79741     },
79742
79743     /**
79744      * Sets the title of this fieldset
79745      * @param {String} title The new title
79746      * @return {Ext.form.FieldSet} this
79747      */
79748     setTitle: function(title) {
79749         var me = this;
79750         me.title = title;
79751         me.initLegend();
79752         me.titleCmp.update(title);
79753         return me;
79754     },
79755
79756     getTargetEl : function() {
79757         return this.body || this.frameBody || this.el;
79758     },
79759
79760     getContentTarget: function() {
79761         return this.body;
79762     },
79763
79764     /**
79765      * @private
79766      * Include the legend component in the items for ComponentQuery
79767      */
79768     getRefItems: function(deep) {
79769         var refItems = this.callParent(arguments),
79770             legend = this.legend;
79771
79772         // Prepend legend items to ensure correct order
79773         if (legend) {
79774             refItems.unshift(legend);
79775             if (deep) {
79776                 refItems.unshift.apply(refItems, legend.getRefItems(true));
79777             }
79778         }
79779         return refItems;
79780     },
79781
79782     /**
79783      * Expands the fieldset.
79784      * @return {Ext.form.FieldSet} this
79785      */
79786     expand : function(){
79787         return this.setExpanded(true);
79788     },
79789
79790     /**
79791      * Collapses the fieldset.
79792      * @return {Ext.form.FieldSet} this
79793      */
79794     collapse : function() {
79795         return this.setExpanded(false);
79796     },
79797
79798     /**
79799      * @private Collapse or expand the fieldset
79800      */
79801     setExpanded: function(expanded) {
79802         var me = this,
79803             checkboxCmp = me.checkboxCmp;
79804
79805         expanded = !!expanded;
79806
79807         if (checkboxCmp) {
79808             checkboxCmp.setValue(expanded);
79809         }
79810
79811         if (expanded) {
79812             me.removeCls(me.baseCls + '-collapsed');
79813         } else {
79814             me.addCls(me.baseCls + '-collapsed');
79815         }
79816         me.collapsed = !expanded;
79817         if (expanded) {
79818             // ensure subitems will get rendered and layed out when expanding
79819             me.getComponentLayout().childrenChanged = true;
79820         }
79821         me.doComponentLayout();
79822         return me;
79823     },
79824
79825     /**
79826      * Toggle the fieldset's collapsed state to the opposite of what it is currently
79827      */
79828     toggle: function() {
79829         this.setExpanded(!!this.collapsed);
79830     },
79831
79832     /**
79833      * @private
79834      * Handle changes in the checkbox checked state
79835      */
79836     onCheckChange: function(cmp, checked) {
79837         this.setExpanded(checked);
79838     },
79839
79840     beforeDestroy : function() {
79841         var legend = this.legend;
79842         if (legend) {
79843             legend.destroy();
79844         }
79845         this.callParent();
79846     }
79847 });
79848
79849 /**
79850  * @docauthor Jason Johnston <jason@sencha.com>
79851  *
79852  * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
79853  * in that form using the {@link #forId} property.
79854  * 
79855  * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
79856  * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
79857  * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
79858  * Ext.form.Label should only be used when your layout can not be achieved with the standard
79859  * {@link Ext.form.Labelable field layout}.
79860  * 
79861  * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
79862  * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
79863  * of that field.
79864  * 
79865  * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
79866  * difference between the two is that the former will automatically escape HTML characters when rendering, while
79867  * the latter will not.
79868  *
79869  * # Example
79870  * 
79871  * This example creates a Label after its associated Text field, an arrangement that cannot currently
79872  * be achieved using the standard Field layout's labelAlign.
79873  * 
79874  *     @example
79875  *     Ext.create('Ext.form.Panel', {
79876  *         title: 'Field with Label',
79877  *         width: 400,
79878  *         bodyPadding: 10,
79879  *         renderTo: Ext.getBody(),
79880  *         layout: {
79881  *             type: 'hbox',
79882  *             align: 'middle'
79883  *         },
79884  *         items: [{
79885  *             xtype: 'textfield',
79886  *             hideLabel: true,
79887  *             flex: 1
79888  *         }, {
79889  *             xtype: 'label',
79890  *             forId: 'myFieldId',
79891  *             text: 'My Awesome Field',
79892  *             margins: '0 0 0 10'
79893  *         }]
79894  *     });
79895  */
79896 Ext.define('Ext.form.Label', {
79897     extend:'Ext.Component',
79898     alias: 'widget.label',
79899     requires: ['Ext.util.Format'],
79900
79901     /**
79902      * @cfg {String} [text='']
79903      * The plain text to display within the label. If you need to include HTML
79904      * tags within the label's innerHTML, use the {@link #html} config instead.
79905      */
79906     /**
79907      * @cfg {String} forId
79908      * The id of the input element to which this label will be bound via the standard HTML 'for'
79909      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
79910      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
79911      * the {@link Ext.form.field.Base#inputId inputId} of that field.
79912      */
79913     /**
79914      * @cfg {String} [html='']
79915      * An HTML fragment that will be used as the label's innerHTML.
79916      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
79917      */
79918     
79919     maskOnDisable: false,
79920     getElConfig: function(){
79921         var me = this;
79922         return {
79923             tag: 'label', 
79924             id: me.id, 
79925             htmlFor: me.forId || '',
79926             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
79927         };
79928     },
79929
79930     /**
79931      * Updates the label's innerHTML with the specified string.
79932      * @param {String} text The new label text
79933      * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
79934      * to the label. This might be useful if you want to include tags in the label's innerHTML rather
79935      * than rendering them as string literals per the default logic.
79936      * @return {Ext.form.Label} this
79937      */
79938     setText : function(text, encode){
79939         var me = this;
79940         
79941         encode = encode !== false;
79942         if(encode) {
79943             me.text = text;
79944             delete me.html;
79945         } else {
79946             me.html = text;
79947             delete me.text;
79948         }
79949         
79950         if(me.rendered){
79951             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
79952         }
79953         return this;
79954     }
79955 });
79956
79957
79958 /**
79959  * @docauthor Jason Johnston <jason@sencha.com>
79960  * 
79961  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
79962  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
79963  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
79964  * working with the BasicForm and the collection of Fields.
79965  * 
79966  * # Layout
79967  * 
79968  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
79969  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
79970  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
79971  * 
79972  * # BasicForm
79973  * 
79974  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
79975  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
79976  * the internal BasicForm when it is created.
79977  * 
79978  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
79979  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
79980  * configuration settings to `this` will *not* affect the BasicForm's configuration.
79981  * 
79982  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
79983  * listened for on the FormPanel itself:
79984  * 
79985  * - {@link Ext.form.Basic#beforeaction beforeaction}
79986  * - {@link Ext.form.Basic#actionfailed actionfailed}
79987  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
79988  * - {@link Ext.form.Basic#validitychange validitychange}
79989  * - {@link Ext.form.Basic#dirtychange dirtychange}
79990  * 
79991  * # Field Defaults
79992  * 
79993  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
79994  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
79995  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
79996  * for details of how the defaults are applied.
79997  * 
79998  * # Form Validation
79999  * 
80000  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
80001  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
80002  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
80003  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
80004  * 
80005  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
80006  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
80007  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
80008  * any component type.
80009  * 
80010  * For more information on form validation see the following:
80011  * 
80012  * - {@link Ext.form.field.Field#validateOnChange}
80013  * - {@link #pollForChanges} and {@link #pollInterval}
80014  * - {@link Ext.form.field.VTypes}
80015  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
80016  * 
80017  * # Form Submission
80018  * 
80019  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
80020  * {@link Ext.form.Basic} for details.
80021  *
80022  * # Example usage
80023  * 
80024  *     @example
80025  *     Ext.create('Ext.form.Panel', {
80026  *         title: 'Simple Form',
80027  *         bodyPadding: 5,
80028  *         width: 350,
80029  * 
80030  *         // The form will submit an AJAX request to this URL when submitted
80031  *         url: 'save-form.php',
80032  * 
80033  *         // Fields will be arranged vertically, stretched to full width
80034  *         layout: 'anchor',
80035  *         defaults: {
80036  *             anchor: '100%'
80037  *         },
80038  * 
80039  *         // The fields
80040  *         defaultType: 'textfield',
80041  *         items: [{
80042  *             fieldLabel: 'First Name',
80043  *             name: 'first',
80044  *             allowBlank: false
80045  *         },{
80046  *             fieldLabel: 'Last Name',
80047  *             name: 'last',
80048  *             allowBlank: false
80049  *         }],
80050  * 
80051  *         // Reset and Submit buttons
80052  *         buttons: [{
80053  *             text: 'Reset',
80054  *             handler: function() {
80055  *                 this.up('form').getForm().reset();
80056  *             }
80057  *         }, {
80058  *             text: 'Submit',
80059  *             formBind: true, //only enabled once the form is valid
80060  *             disabled: true,
80061  *             handler: function() {
80062  *                 var form = this.up('form').getForm();
80063  *                 if (form.isValid()) {
80064  *                     form.submit({
80065  *                         success: function(form, action) {
80066  *                            Ext.Msg.alert('Success', action.result.msg);
80067  *                         },
80068  *                         failure: function(form, action) {
80069  *                             Ext.Msg.alert('Failed', action.result.msg);
80070  *                         }
80071  *                     });
80072  *                 }
80073  *             }
80074  *         }],
80075  *         renderTo: Ext.getBody()
80076  *     });
80077  *
80078  */
80079 Ext.define('Ext.form.Panel', {
80080     extend:'Ext.panel.Panel',
80081     mixins: {
80082         fieldAncestor: 'Ext.form.FieldAncestor'
80083     },
80084     alias: 'widget.form',
80085     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
80086     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
80087
80088     /**
80089      * @cfg {Boolean} pollForChanges
80090      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
80091      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
80092      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
80093      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
80094      * do not fire native events. Defaults to `false`.
80095      */
80096
80097     /**
80098      * @cfg {Number} pollInterval
80099      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
80100      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
80101      */
80102
80103     /**
80104      * @cfg {String} layout
80105      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
80106      * Defaults to `'anchor'`.
80107      */
80108     layout: 'anchor',
80109
80110     ariaRole: 'form',
80111
80112     initComponent: function() {
80113         var me = this;
80114
80115         if (me.frame) {
80116             me.border = false;
80117         }
80118
80119         me.initFieldAncestor();
80120         me.callParent();
80121
80122         me.relayEvents(me.form, [
80123             'beforeaction',
80124             'actionfailed',
80125             'actioncomplete',
80126             'validitychange',
80127             'dirtychange'
80128         ]);
80129
80130         // Start polling if configured
80131         if (me.pollForChanges) {
80132             me.startPolling(me.pollInterval || 500);
80133         }
80134     },
80135
80136     initItems: function() {
80137         // Create the BasicForm
80138         var me = this;
80139
80140         me.form = me.createForm();
80141         me.callParent();
80142         me.form.initialize();
80143     },
80144
80145     /**
80146      * @private
80147      */
80148     createForm: function() {
80149         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
80150     },
80151
80152     /**
80153      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
80154      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
80155      */
80156     getForm: function() {
80157         return this.form;
80158     },
80159
80160     /**
80161      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
80162      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
80163      * @param {Ext.data.Model} record The record to load
80164      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
80165      */
80166     loadRecord: function(record) {
80167         return this.getForm().loadRecord(record);
80168     },
80169
80170     /**
80171      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
80172      * @return {Ext.data.Model} The loaded instance
80173      */
80174     getRecord: function() {
80175         return this.getForm().getRecord();
80176     },
80177
80178     /**
80179      * Convenience function for fetching the current value of each field in the form. This is the same as calling
80180      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
80181      * @return {Object} The current form field values, keyed by field name
80182      */
80183     getValues: function() {
80184         return this.getForm().getValues();
80185     },
80186
80187     beforeDestroy: function() {
80188         this.stopPolling();
80189         this.form.destroy();
80190         this.callParent();
80191     },
80192
80193     /**
80194      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
80195      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
80196      * {@link Ext.form.Basic#doAction} for details)
80197      */
80198     load: function(options) {
80199         this.form.load(options);
80200     },
80201
80202     /**
80203      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
80204      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
80205      * {@link Ext.form.Basic#doAction} for details)
80206      */
80207     submit: function(options) {
80208         this.form.submit(options);
80209     },
80210
80211     /*
80212      * Inherit docs, not using onDisable because it only gets fired
80213      * when the component is rendered.
80214      */
80215     disable: function(silent) {
80216         this.callParent(arguments);
80217         this.form.getFields().each(function(field) {
80218             field.disable();
80219         });
80220     },
80221
80222     /*
80223      * Inherit docs, not using onEnable because it only gets fired
80224      * when the component is rendered.
80225      */
80226     enable: function(silent) {
80227         this.callParent(arguments);
80228         this.form.getFields().each(function(field) {
80229             field.enable();
80230         });
80231     },
80232
80233     /**
80234      * Start an interval task to continuously poll all the fields in the form for changes in their
80235      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
80236      * @param {Number} interval The interval in milliseconds at which the check should run.
80237      */
80238     startPolling: function(interval) {
80239         this.stopPolling();
80240         var task = Ext.create('Ext.util.TaskRunner', interval);
80241         task.start({
80242             interval: 0,
80243             run: this.checkChange,
80244             scope: this
80245         });
80246         this.pollTask = task;
80247     },
80248
80249     /**
80250      * Stop a running interval task that was started by {@link #startPolling}.
80251      */
80252     stopPolling: function() {
80253         var task = this.pollTask;
80254         if (task) {
80255             task.stopAll();
80256             delete this.pollTask;
80257         }
80258     },
80259
80260     /**
80261      * Forces each field within the form panel to
80262      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
80263      */
80264     checkChange: function() {
80265         this.form.getFields().each(function(field) {
80266             field.checkChange();
80267         });
80268     }
80269 });
80270
80271 /**
80272  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
80273  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
80274  * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
80275  * group of radio buttons as a whole.
80276  *
80277  * # Validation
80278  *
80279  * Individual radio buttons themselves have no default validation behavior, but
80280  * sometimes you want to require a user to select one of a group of radios. RadioGroup
80281  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
80282  * one of the radio buttons, the entire group will be highlighted as invalid and the
80283  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
80284  *
80285  * # Layout
80286  *
80287  * The default layout for RadioGroup makes it easy to arrange the radio buttons into
80288  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
80289  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
80290  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
80291  * the Radio components at any depth will still be managed by the RadioGroup's validation.
80292  *
80293  * # Example usage
80294  *
80295  *     @example
80296  *     Ext.create('Ext.form.Panel', {
80297  *         title: 'RadioGroup Example',
80298  *         width: 300,
80299  *         height: 125,
80300  *         bodyPadding: 10,
80301  *         renderTo: Ext.getBody(),
80302  *         items:[{
80303  *             xtype: 'radiogroup',
80304  *             fieldLabel: 'Two Columns',
80305  *             // Arrange radio buttons into two columns, distributed vertically
80306  *             columns: 2,
80307  *             vertical: true,
80308  *             items: [
80309  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
80310  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
80311  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
80312  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
80313  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
80314  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
80315  *             ]
80316  *         }]
80317  *     });
80318  *
80319  */
80320 Ext.define('Ext.form.RadioGroup', {
80321     extend: 'Ext.form.CheckboxGroup',
80322     alias: 'widget.radiogroup',
80323
80324     /**
80325      * @cfg {Ext.form.field.Radio[]/Object[]} items
80326      * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
80327      */
80328     /**
80329      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank.
80330      * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
80331      * be used as the error text.
80332      */
80333     allowBlank : true,
80334     /**
80335      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
80336      */
80337     blankText : 'You must select one item in this group',
80338
80339     // private
80340     defaultType : 'radiofield',
80341
80342     // private
80343     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
80344
80345     getBoxes: function() {
80346         return this.query('[isRadio]');
80347     },
80348
80349     /**
80350      * Sets the value of the radio group. The radio with corresponding name and value will be set.
80351      * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
80352      * for each name.
80353      * 
80354      * @param {Object} value The map from names to values to be set.
80355      * @return {Ext.form.CheckboxGroup} this
80356      */
80357     setValue: function(value) {
80358         var me = this;
80359         if (Ext.isObject(value)) {
80360             Ext.Object.each(value, function(name, cbValue) {
80361                 var radios = Ext.form.RadioManager.getWithValue(name, cbValue);
80362                 radios.each(function(cb) {
80363                     cb.setValue(true);
80364                 });
80365             });
80366         }
80367         return me;
80368     }
80369 });
80370
80371 /**
80372  * @private
80373  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
80374  */
80375 Ext.define('Ext.form.RadioManager', {
80376     extend: 'Ext.util.MixedCollection',
80377     singleton: true,
80378
80379     getByName: function(name) {
80380         return this.filterBy(function(item) {
80381             return item.name == name;
80382         });
80383     },
80384
80385     getWithValue: function(name, value) {
80386         return this.filterBy(function(item) {
80387             return item.name == name && item.inputValue == value;
80388         });
80389     },
80390
80391     getChecked: function(name) {
80392         return this.findBy(function(item) {
80393             return item.name == name && item.checked;
80394         });
80395     }
80396 });
80397
80398 /**
80399  * @class Ext.form.action.DirectLoad
80400  * @extends Ext.form.action.Load
80401  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
80402  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
80403  * <pre><code>
80404 var myFormPanel = new Ext.form.Panel({
80405     // configs for FormPanel
80406     title: 'Basic Information',
80407     renderTo: document.body,
80408     width: 300, height: 160,
80409     padding: 10,
80410
80411     // configs apply to child items
80412     defaults: {anchor: '100%'},
80413     defaultType: 'textfield',
80414     items: [{
80415         fieldLabel: 'Name',
80416         name: 'name'
80417     },{
80418         fieldLabel: 'Email',
80419         name: 'email'
80420     },{
80421         fieldLabel: 'Company',
80422         name: 'company'
80423     }],
80424
80425     // configs for BasicForm
80426     api: {
80427         // The server-side method to call for load() requests
80428         load: Profile.getBasicInfo,
80429         // The server-side must mark the submit handler as a 'formHandler'
80430         submit: Profile.updateBasicInfo
80431     },
80432     // specify the order for the passed params
80433     paramOrder: ['uid', 'foo']
80434 });
80435
80436 // load the form
80437 myFormPanel.getForm().load({
80438     // pass 2 arguments to server side getBasicInfo method (len=2)
80439     params: {
80440         foo: 'bar',
80441         uid: 34
80442     }
80443 });
80444  * </code></pre>
80445  * The data packet sent to the server will resemble something like:
80446  * <pre><code>
80447 [
80448     {
80449         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
80450         "data":[34,"bar"] // note the order of the params
80451     }
80452 ]
80453  * </code></pre>
80454  * The form will process a data packet returned by the server that is similar
80455  * to the following format:
80456  * <pre><code>
80457 [
80458     {
80459         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
80460         "result":{
80461             "success":true,
80462             "data":{
80463                 "name":"Fred Flintstone",
80464                 "company":"Slate Rock and Gravel",
80465                 "email":"fred.flintstone@slaterg.com"
80466             }
80467         }
80468     }
80469 ]
80470  * </code></pre>
80471  */
80472 Ext.define('Ext.form.action.DirectLoad', {
80473     extend:'Ext.form.action.Load',
80474     requires: ['Ext.direct.Manager'],
80475     alternateClassName: 'Ext.form.Action.DirectLoad',
80476     alias: 'formaction.directload',
80477
80478     type: 'directload',
80479
80480     run: function() {
80481         this.form.api.load.apply(window, this.getArgs());
80482     },
80483
80484     /**
80485      * @private
80486      * Build the arguments to be sent to the Direct call.
80487      * @return Array
80488      */
80489     getArgs: function() {
80490         var me = this,
80491             args = [],
80492             form = me.form,
80493             paramOrder = form.paramOrder,
80494             params = me.getParams(),
80495             i, len;
80496
80497         // If a paramOrder was specified, add the params into the argument list in that order.
80498         if (paramOrder) {
80499             for (i = 0, len = paramOrder.length; i < len; i++) {
80500                 args.push(params[paramOrder[i]]);
80501             }
80502         }
80503         // If paramsAsHash was specified, add all the params as a single object argument.
80504         else if (form.paramsAsHash) {
80505             args.push(params);
80506         }
80507
80508         // Add the callback and scope to the end of the arguments list
80509         args.push(me.onSuccess, me);
80510
80511         return args;
80512     },
80513
80514     // Direct actions have already been processed and therefore
80515     // we can directly set the result; Direct Actions do not have
80516     // a this.response property.
80517     processResponse: function(result) {
80518         return (this.result = result);
80519     },
80520
80521     onSuccess: function(result, trans) {
80522         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
80523             result = {};
80524         }
80525         this.callParent([result]);
80526     }
80527 });
80528
80529
80530
80531 /**
80532  * @class Ext.form.action.DirectSubmit
80533  * @extends Ext.form.action.Submit
80534  * <p>Provides Ext.direct support for submitting form data.</p>
80535  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
80536  * <pre><code>
80537 var myFormPanel = new Ext.form.Panel({
80538     // configs for FormPanel
80539     title: 'Basic Information',
80540     renderTo: document.body,
80541     width: 300, height: 160,
80542     padding: 10,
80543     buttons:[{
80544         text: 'Submit',
80545         handler: function(){
80546             myFormPanel.getForm().submit({
80547                 params: {
80548                     foo: 'bar',
80549                     uid: 34
80550                 }
80551             });
80552         }
80553     }],
80554
80555     // configs apply to child items
80556     defaults: {anchor: '100%'},
80557     defaultType: 'textfield',
80558     items: [{
80559         fieldLabel: 'Name',
80560         name: 'name'
80561     },{
80562         fieldLabel: 'Email',
80563         name: 'email'
80564     },{
80565         fieldLabel: 'Company',
80566         name: 'company'
80567     }],
80568
80569     // configs for BasicForm
80570     api: {
80571         // The server-side method to call for load() requests
80572         load: Profile.getBasicInfo,
80573         // The server-side must mark the submit handler as a 'formHandler'
80574         submit: Profile.updateBasicInfo
80575     },
80576     // specify the order for the passed params
80577     paramOrder: ['uid', 'foo']
80578 });
80579  * </code></pre>
80580  * The data packet sent to the server will resemble something like:
80581  * <pre><code>
80582 {
80583     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
80584     "result":{
80585         "success":true,
80586         "id":{
80587             "extAction":"Profile","extMethod":"updateBasicInfo",
80588             "extType":"rpc","extTID":"6","extUpload":"false",
80589             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
80590         }
80591     }
80592 }
80593  * </code></pre>
80594  * The form will process a data packet returned by the server that is similar
80595  * to the following:
80596  * <pre><code>
80597 // sample success packet (batched requests)
80598 [
80599     {
80600         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
80601         "result":{
80602             "success":true
80603         }
80604     }
80605 ]
80606
80607 // sample failure packet (one request)
80608 {
80609         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
80610         "result":{
80611             "errors":{
80612                 "email":"already taken"
80613             },
80614             "success":false,
80615             "foo":"bar"
80616         }
80617 }
80618  * </code></pre>
80619  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
80620  */
80621 Ext.define('Ext.form.action.DirectSubmit', {
80622     extend:'Ext.form.action.Submit',
80623     requires: ['Ext.direct.Manager'],
80624     alternateClassName: 'Ext.form.Action.DirectSubmit',
80625     alias: 'formaction.directsubmit',
80626
80627     type: 'directsubmit',
80628
80629     doSubmit: function() {
80630         var me = this,
80631             callback = Ext.Function.bind(me.onSuccess, me),
80632             formEl = me.buildForm();
80633         me.form.api.submit(formEl, callback, me);
80634         Ext.removeNode(formEl);
80635     },
80636
80637     // Direct actions have already been processed and therefore
80638     // we can directly set the result; Direct Actions do not have
80639     // a this.response property.
80640     processResponse: function(result) {
80641         return (this.result = result);
80642     },
80643
80644     onSuccess: function(response, trans) {
80645         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
80646             response = {};
80647         }
80648         this.callParent([response]);
80649     }
80650 });
80651
80652 /**
80653  * @class Ext.form.action.StandardSubmit
80654  * @extends Ext.form.action.Submit
80655  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
80656  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
80657  * <p>If validation of the form fields fails, the Form's afterAction method
80658  * will be called. Otherwise, afterAction will not be called.</p>
80659  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
80660  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
80661  * config option is <tt>true</tt>.</p>
80662  */
80663 Ext.define('Ext.form.action.StandardSubmit', {
80664     extend:'Ext.form.action.Submit',
80665     alias: 'formaction.standardsubmit',
80666
80667     /**
80668      * @cfg {String} target
80669      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
80670      * the target will be the current window/frame.
80671      */
80672
80673     /**
80674      * @private
80675      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
80676      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
80677      * {@link Ext.form.Basic#baseParams baseParams}.
80678      */
80679     doSubmit: function() {
80680         var form = this.buildForm();
80681         form.submit();
80682         Ext.removeNode(form);
80683     }
80684
80685 });
80686
80687 /**
80688  * @docauthor Robert Dougan <rob@sencha.com>
80689  *
80690  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
80691  * parent class for {@link Ext.form.field.Radio radio buttons}.
80692  *
80693  * # Labeling
80694  *
80695  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
80696  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
80697  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
80698  *
80699  * # Values
80700  *
80701  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
80702  * The following values will check the checkbox:
80703  *
80704  * - `true`
80705  * - `'true'`
80706  * - `'1'`
80707  * - `'on'`
80708  *
80709  * Any other value will uncheck the checkbox.
80710  *
80711  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
80712  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
80713  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
80714  * will be used.
80715  *
80716  * # Example usage
80717  *
80718  *     @example
80719  *     Ext.create('Ext.form.Panel', {
80720  *         bodyPadding: 10,
80721  *         width: 300,
80722  *         title: 'Pizza Order',
80723  *         items: [
80724  *             {
80725  *                 xtype: 'fieldcontainer',
80726  *                 fieldLabel: 'Toppings',
80727  *                 defaultType: 'checkboxfield',
80728  *                 items: [
80729  *                     {
80730  *                         boxLabel  : 'Anchovies',
80731  *                         name      : 'topping',
80732  *                         inputValue: '1',
80733  *                         id        : 'checkbox1'
80734  *                     }, {
80735  *                         boxLabel  : 'Artichoke Hearts',
80736  *                         name      : 'topping',
80737  *                         inputValue: '2',
80738  *                         checked   : true,
80739  *                         id        : 'checkbox2'
80740  *                     }, {
80741  *                         boxLabel  : 'Bacon',
80742  *                         name      : 'topping',
80743  *                         inputValue: '3',
80744  *                         id        : 'checkbox3'
80745  *                     }
80746  *                 ]
80747  *             }
80748  *         ],
80749  *         bbar: [
80750  *             {
80751  *                 text: 'Select Bacon',
80752  *                 handler: function() {
80753  *                     Ext.getCmp('checkbox3').setValue(true);
80754  *                 }
80755  *             },
80756  *             '-',
80757  *             {
80758  *                 text: 'Select All',
80759  *                 handler: function() {
80760  *                     Ext.getCmp('checkbox1').setValue(true);
80761  *                     Ext.getCmp('checkbox2').setValue(true);
80762  *                     Ext.getCmp('checkbox3').setValue(true);
80763  *                 }
80764  *             },
80765  *             {
80766  *                 text: 'Deselect All',
80767  *                 handler: function() {
80768  *                     Ext.getCmp('checkbox1').setValue(false);
80769  *                     Ext.getCmp('checkbox2').setValue(false);
80770  *                     Ext.getCmp('checkbox3').setValue(false);
80771  *                 }
80772  *             }
80773  *         ],
80774  *         renderTo: Ext.getBody()
80775  *     });
80776  */
80777 Ext.define('Ext.form.field.Checkbox', {
80778     extend: 'Ext.form.field.Base',
80779     alias: ['widget.checkboxfield', 'widget.checkbox'],
80780     alternateClassName: 'Ext.form.Checkbox',
80781     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
80782
80783     // note: {id} here is really {inputId}, but {cmpId} is available
80784     fieldSubTpl: [
80785         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
80786             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
80787         '</tpl>',
80788         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
80789         // styled with a custom checkbox image. This allows greater control and consistency in
80790         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
80791         '<input type="button" id="{id}" ',
80792             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
80793             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
80794         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
80795             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
80796         '</tpl>',
80797         {
80798             disableFormats: true,
80799             compiled: true
80800         }
80801     ],
80802
80803     isCheckbox: true,
80804
80805     /**
80806      * @cfg {String} [focusCls='x-form-cb-focus']
80807      * The CSS class to use when the checkbox receives focus
80808      */
80809     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
80810
80811     /**
80812      * @cfg {String} [fieldCls='x-form-field']
80813      * The default CSS class for the checkbox
80814      */
80815
80816     /**
80817      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
80818      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
80819      * .
80820      */
80821     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
80822
80823     /**
80824      * @cfg {Boolean} checked
80825      * true if the checkbox should render initially checked
80826      */
80827     checked: false,
80828
80829     /**
80830      * @cfg {String} [checkedCls='x-form-cb-checked']
80831      * The CSS class added to the component's main element when it is in the checked state.
80832      */
80833     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
80834
80835     /**
80836      * @cfg {String} boxLabel
80837      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
80838      * determined by the {@link #boxLabelAlign} config.
80839      */
80840
80841     /**
80842      * @cfg {String} [boxLabelCls='x-form-cb-label']
80843      * The CSS class to be applied to the {@link #boxLabel} element
80844      */
80845     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
80846
80847     /**
80848      * @cfg {String} boxLabelAlign
80849      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
80850      * and 'after'.
80851      */
80852     boxLabelAlign: 'after',
80853
80854     /**
80855      * @cfg {String} inputValue
80856      * The value that should go into the generated input element's value attribute and should be used as the parameter
80857      * value when submitting as part of a form.
80858      */
80859     inputValue: 'on',
80860
80861     /**
80862      * @cfg {String} uncheckedValue
80863      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
80864      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
80865      * submitted (the default behavior of HTML checkboxes).
80866      */
80867
80868     /**
80869      * @cfg {Function} handler
80870      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
80871      * change event}).
80872      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
80873      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
80874      */
80875
80876     /**
80877      * @cfg {Object} scope
80878      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
80879      */
80880
80881     // private overrides
80882     checkChangeEvents: [],
80883     inputType: 'checkbox',
80884     ariaRole: 'checkbox',
80885
80886     // private
80887     onRe: /^on$/i,
80888
80889     initComponent: function(){
80890         this.callParent(arguments);
80891         this.getManager().add(this);
80892     },
80893
80894     initValue: function() {
80895         var me = this,
80896             checked = !!me.checked;
80897
80898         /**
80899          * @property {Object} originalValue
80900          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
80901          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
80902          */
80903         me.originalValue = me.lastValue = checked;
80904
80905         // Set the initial checked state
80906         me.setValue(checked);
80907     },
80908
80909     // private
80910     onRender : function(ct, position) {
80911         var me = this;
80912
80913         /**
80914          * @property {Ext.Element} boxLabelEl
80915          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
80916          * rendered and has a boxLabel configured.
80917          */
80918         me.addChildEls('boxLabelEl');
80919
80920         Ext.applyIf(me.subTplData, {
80921             boxLabel: me.boxLabel,
80922             boxLabelCls: me.boxLabelCls,
80923             boxLabelAlign: me.boxLabelAlign
80924         });
80925
80926         me.callParent(arguments);
80927     },
80928
80929     initEvents: function() {
80930         var me = this;
80931         me.callParent();
80932         me.mon(me.inputEl, 'click', me.onBoxClick, me);
80933     },
80934
80935     /**
80936      * @private Handle click on the checkbox button
80937      */
80938     onBoxClick: function(e) {
80939         var me = this;
80940         if (!me.disabled && !me.readOnly) {
80941             this.setValue(!this.checked);
80942         }
80943     },
80944
80945     /**
80946      * Returns the checked state of the checkbox.
80947      * @return {Boolean} True if checked, else false
80948      */
80949     getRawValue: function() {
80950         return this.checked;
80951     },
80952
80953     /**
80954      * Returns the checked state of the checkbox.
80955      * @return {Boolean} True if checked, else false
80956      */
80957     getValue: function() {
80958         return this.checked;
80959     },
80960
80961     /**
80962      * Returns the submit value for the checkbox which can be used when submitting forms.
80963      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
80964      */
80965     getSubmitValue: function() {
80966         var unchecked = this.uncheckedValue,
80967             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
80968         return this.checked ? this.inputValue : uncheckedVal;
80969     },
80970
80971     /**
80972      * Sets the checked state of the checkbox.
80973      *
80974      * @param {Boolean/String/Number} value The following values will check the checkbox:
80975      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
80976      * Any other value will uncheck the checkbox.
80977      * @return {Boolean} the new checked state of the checkbox
80978      */
80979     setRawValue: function(value) {
80980         var me = this,
80981             inputEl = me.inputEl,
80982             inputValue = me.inputValue,
80983             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
80984                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
80985
80986         if (inputEl) {
80987             inputEl.dom.setAttribute('aria-checked', checked);
80988             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
80989         }
80990
80991         me.checked = me.rawValue = checked;
80992         return checked;
80993     },
80994
80995     /**
80996      * Sets the checked state of the checkbox, and invokes change detection.
80997      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
80998      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
80999      * @return {Ext.form.field.Checkbox} this
81000      */
81001     setValue: function(checked) {
81002         var me = this;
81003
81004         // If an array of strings is passed, find all checkboxes in the group with the same name as this
81005         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
81006         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
81007         // don't want users depending on this behavior.
81008         if (Ext.isArray(checked)) {
81009             me.getManager().getByName(me.name).each(function(cb) {
81010                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
81011             });
81012         } else {
81013             me.callParent(arguments);
81014         }
81015
81016         return me;
81017     },
81018
81019     // private
81020     valueToRaw: function(value) {
81021         // No extra conversion for checkboxes
81022         return value;
81023     },
81024
81025     /**
81026      * @private
81027      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
81028      * function if specified.
81029      */
81030     onChange: function(newVal, oldVal) {
81031         var me = this,
81032             handler = me.handler;
81033         if (handler) {
81034             handler.call(me.scope || me, me, newVal);
81035         }
81036         me.callParent(arguments);
81037     },
81038
81039     // inherit docs
81040     beforeDestroy: function(){
81041         this.callParent();
81042         this.getManager().removeAtKey(this.id);
81043     },
81044
81045     // inherit docs
81046     getManager: function() {
81047         return Ext.form.CheckboxManager;
81048     },
81049
81050     onEnable: function() {
81051         var me = this,
81052             inputEl = me.inputEl;
81053         me.callParent();
81054         if (inputEl) {
81055             // Can still be disabled if the field is readOnly
81056             inputEl.dom.disabled = me.readOnly;
81057         }
81058     },
81059
81060     setReadOnly: function(readOnly) {
81061         var me = this,
81062             inputEl = me.inputEl;
81063         if (inputEl) {
81064             // Set the button to disabled when readonly
81065             inputEl.dom.disabled = readOnly || me.disabled;
81066         }
81067         me.readOnly = readOnly;
81068     },
81069
81070     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
81071     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
81072     getBodyNaturalWidth: function() {
81073         var me = this,
81074             bodyEl = me.bodyEl,
81075             ws = 'white-space',
81076             width;
81077         bodyEl.setStyle(ws, 'nowrap');
81078         width = bodyEl.getWidth();
81079         bodyEl.setStyle(ws, '');
81080         return width;
81081     }
81082
81083 });
81084
81085 /**
81086  * @private
81087  * @class Ext.layout.component.field.Trigger
81088  * @extends Ext.layout.component.field.Field
81089  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
81090  * the trigger button(s).
81091  * @private
81092  */
81093
81094 Ext.define('Ext.layout.component.field.Trigger', {
81095
81096     /* Begin Definitions */
81097
81098     alias: ['layout.triggerfield'],
81099
81100     extend: 'Ext.layout.component.field.Field',
81101
81102     /* End Definitions */
81103
81104     type: 'triggerfield',
81105
81106     sizeBodyContents: function(width, height) {
81107         var me = this,
81108             owner = me.owner,
81109             inputEl = owner.inputEl,
81110             triggerWrap = owner.triggerWrap,
81111             triggerWidth = owner.getTriggerWidth();
81112
81113         // If we or our ancestor is hidden, we can get a triggerWidth calculation
81114         // of 0.  We don't want to resize in this case.
81115         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
81116             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
81117             // are floated left in CSS so they'll stack up side by side.
81118             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
81119     
81120             // Explicitly set the triggerWrap's width, to prevent wrapping
81121             triggerWrap.setWidth(triggerWidth);
81122         }
81123     }
81124 });
81125 /**
81126  * A mechanism for displaying data using custom layout templates and formatting.
81127  *
81128  * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
81129  * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
81130  * to reflect the changes. The view also provides built-in behavior for many common events that can
81131  * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
81132  * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
81133  * be provided for the DataView to determine what nodes it will be working with.**
81134  *
81135  * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
81136  *
81137  *     @example
81138  *     Ext.define('Image', {
81139  *         extend: 'Ext.data.Model',
81140  *         fields: [
81141  *             { name:'src', type:'string' },
81142  *             { name:'caption', type:'string' }
81143  *         ]
81144  *     });
81145  *
81146  *     Ext.create('Ext.data.Store', {
81147  *         id:'imagesStore',
81148  *         model: 'Image',
81149  *         data: [
81150  *             { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
81151  *             { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
81152  *             { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
81153  *             { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
81154  *         ]
81155  *     });
81156  *
81157  *     var imageTpl = new Ext.XTemplate(
81158  *         '<tpl for=".">',
81159  *             '<div style="margin-bottom: 10px;" class="thumb-wrap">',
81160  *               '<img src="{src}" />',
81161  *               '<br/><span>{caption}</span>',
81162  *             '</div>',
81163  *         '</tpl>'
81164  *     );
81165  *
81166  *     Ext.create('Ext.view.View', {
81167  *         store: Ext.data.StoreManager.lookup('imagesStore'),
81168  *         tpl: imageTpl,
81169  *         itemSelector: 'div.thumb-wrap',
81170  *         emptyText: 'No images available',
81171  *         renderTo: Ext.getBody()
81172  *     });
81173  */
81174 Ext.define('Ext.view.View', {
81175     extend: 'Ext.view.AbstractView',
81176     alternateClassName: 'Ext.DataView',
81177     alias: 'widget.dataview',
81178
81179     inheritableStatics: {
81180         EventMap: {
81181             mousedown: 'MouseDown',
81182             mouseup: 'MouseUp',
81183             click: 'Click',
81184             dblclick: 'DblClick',
81185             contextmenu: 'ContextMenu',
81186             mouseover: 'MouseOver',
81187             mouseout: 'MouseOut',
81188             mouseenter: 'MouseEnter',
81189             mouseleave: 'MouseLeave',
81190             keydown: 'KeyDown',
81191             focus: 'Focus'
81192         }
81193     },
81194
81195     addCmpEvents: function() {
81196         this.addEvents(
81197             /**
81198              * @event beforeitemmousedown
81199              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
81200              * @param {Ext.view.View} this
81201              * @param {Ext.data.Model} record The record that belongs to the item
81202              * @param {HTMLElement} item The item's element
81203              * @param {Number} index The item's index
81204              * @param {Ext.EventObject} e The raw event object
81205              */
81206             'beforeitemmousedown',
81207             /**
81208              * @event beforeitemmouseup
81209              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
81210              * @param {Ext.view.View} this
81211              * @param {Ext.data.Model} record The record that belongs to the item
81212              * @param {HTMLElement} item The item's element
81213              * @param {Number} index The item's index
81214              * @param {Ext.EventObject} e The raw event object
81215              */
81216             'beforeitemmouseup',
81217             /**
81218              * @event beforeitemmouseenter
81219              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
81220              * @param {Ext.view.View} this
81221              * @param {Ext.data.Model} record The record that belongs to the item
81222              * @param {HTMLElement} item The item's element
81223              * @param {Number} index The item's index
81224              * @param {Ext.EventObject} e The raw event object
81225              */
81226             'beforeitemmouseenter',
81227             /**
81228              * @event beforeitemmouseleave
81229              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
81230              * @param {Ext.view.View} this
81231              * @param {Ext.data.Model} record The record that belongs to the item
81232              * @param {HTMLElement} item The item's element
81233              * @param {Number} index The item's index
81234              * @param {Ext.EventObject} e The raw event object
81235              */
81236             'beforeitemmouseleave',
81237             /**
81238              * @event beforeitemclick
81239              * Fires before the click event on an item is processed. Returns false to cancel the default action.
81240              * @param {Ext.view.View} this
81241              * @param {Ext.data.Model} record The record that belongs to the item
81242              * @param {HTMLElement} item The item's element
81243              * @param {Number} index The item's index
81244              * @param {Ext.EventObject} e The raw event object
81245              */
81246             'beforeitemclick',
81247             /**
81248              * @event beforeitemdblclick
81249              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
81250              * @param {Ext.view.View} this
81251              * @param {Ext.data.Model} record The record that belongs to the item
81252              * @param {HTMLElement} item The item's element
81253              * @param {Number} index The item's index
81254              * @param {Ext.EventObject} e The raw event object
81255              */
81256             'beforeitemdblclick',
81257             /**
81258              * @event beforeitemcontextmenu
81259              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
81260              * @param {Ext.view.View} this
81261              * @param {Ext.data.Model} record The record that belongs to the item
81262              * @param {HTMLElement} item The item's element
81263              * @param {Number} index The item's index
81264              * @param {Ext.EventObject} e The raw event object
81265              */
81266             'beforeitemcontextmenu',
81267             /**
81268              * @event beforeitemkeydown
81269              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
81270              * @param {Ext.view.View} this
81271              * @param {Ext.data.Model} record The record that belongs to the item
81272              * @param {HTMLElement} item The item's element
81273              * @param {Number} index The item's index
81274              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
81275              */
81276             'beforeitemkeydown',
81277             /**
81278              * @event itemmousedown
81279              * Fires when there is a mouse down on an item
81280              * @param {Ext.view.View} this
81281              * @param {Ext.data.Model} record The record that belongs to the item
81282              * @param {HTMLElement} item The item's element
81283              * @param {Number} index The item's index
81284              * @param {Ext.EventObject} e The raw event object
81285              */
81286             'itemmousedown',
81287             /**
81288              * @event itemmouseup
81289              * Fires when there is a mouse up on an item
81290              * @param {Ext.view.View} this
81291              * @param {Ext.data.Model} record The record that belongs to the item
81292              * @param {HTMLElement} item The item's element
81293              * @param {Number} index The item's index
81294              * @param {Ext.EventObject} e The raw event object
81295              */
81296             'itemmouseup',
81297             /**
81298              * @event itemmouseenter
81299              * Fires when the mouse enters an item.
81300              * @param {Ext.view.View} this
81301              * @param {Ext.data.Model} record The record that belongs to the item
81302              * @param {HTMLElement} item The item's element
81303              * @param {Number} index The item's index
81304              * @param {Ext.EventObject} e The raw event object
81305              */
81306             'itemmouseenter',
81307             /**
81308              * @event itemmouseleave
81309              * Fires when the mouse leaves an item.
81310              * @param {Ext.view.View} this
81311              * @param {Ext.data.Model} record The record that belongs to the item
81312              * @param {HTMLElement} item The item's element
81313              * @param {Number} index The item's index
81314              * @param {Ext.EventObject} e The raw event object
81315              */
81316             'itemmouseleave',
81317             /**
81318              * @event itemclick
81319              * Fires when an item is clicked.
81320              * @param {Ext.view.View} this
81321              * @param {Ext.data.Model} record The record that belongs to the item
81322              * @param {HTMLElement} item The item's element
81323              * @param {Number} index The item's index
81324              * @param {Ext.EventObject} e The raw event object
81325              */
81326             'itemclick',
81327             /**
81328              * @event itemdblclick
81329              * Fires when an item is double clicked.
81330              * @param {Ext.view.View} this
81331              * @param {Ext.data.Model} record The record that belongs to the item
81332              * @param {HTMLElement} item The item's element
81333              * @param {Number} index The item's index
81334              * @param {Ext.EventObject} e The raw event object
81335              */
81336             'itemdblclick',
81337             /**
81338              * @event itemcontextmenu
81339              * Fires when an item is right clicked.
81340              * @param {Ext.view.View} this
81341              * @param {Ext.data.Model} record The record that belongs to the item
81342              * @param {HTMLElement} item The item's element
81343              * @param {Number} index The item's index
81344              * @param {Ext.EventObject} e The raw event object
81345              */
81346             'itemcontextmenu',
81347             /**
81348              * @event itemkeydown
81349              * Fires when a key is pressed while an item is currently selected.
81350              * @param {Ext.view.View} this
81351              * @param {Ext.data.Model} record The record that belongs to the item
81352              * @param {HTMLElement} item The item's element
81353              * @param {Number} index The item's index
81354              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
81355              */
81356             'itemkeydown',
81357             /**
81358              * @event beforecontainermousedown
81359              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
81360              * @param {Ext.view.View} this
81361              * @param {Ext.EventObject} e The raw event object
81362              */
81363             'beforecontainermousedown',
81364             /**
81365              * @event beforecontainermouseup
81366              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
81367              * @param {Ext.view.View} this
81368              * @param {Ext.EventObject} e The raw event object
81369              */
81370             'beforecontainermouseup',
81371             /**
81372              * @event beforecontainermouseover
81373              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
81374              * @param {Ext.view.View} this
81375              * @param {Ext.EventObject} e The raw event object
81376              */
81377             'beforecontainermouseover',
81378             /**
81379              * @event beforecontainermouseout
81380              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
81381              * @param {Ext.view.View} this
81382              * @param {Ext.EventObject} e The raw event object
81383              */
81384             'beforecontainermouseout',
81385             /**
81386              * @event beforecontainerclick
81387              * Fires before the click event on the container is processed. Returns false to cancel the default action.
81388              * @param {Ext.view.View} this
81389              * @param {Ext.EventObject} e The raw event object
81390              */
81391             'beforecontainerclick',
81392             /**
81393              * @event beforecontainerdblclick
81394              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
81395              * @param {Ext.view.View} this
81396              * @param {Ext.EventObject} e The raw event object
81397              */
81398             'beforecontainerdblclick',
81399             /**
81400              * @event beforecontainercontextmenu
81401              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
81402              * @param {Ext.view.View} this
81403              * @param {Ext.EventObject} e The raw event object
81404              */
81405             'beforecontainercontextmenu',
81406             /**
81407              * @event beforecontainerkeydown
81408              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
81409              * @param {Ext.view.View} this
81410              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
81411              */
81412             'beforecontainerkeydown',
81413             /**
81414              * @event containermouseup
81415              * Fires when there is a mouse up on the container
81416              * @param {Ext.view.View} this
81417              * @param {Ext.EventObject} e The raw event object
81418              */
81419             'containermouseup',
81420             /**
81421              * @event containermouseover
81422              * Fires when you move the mouse over the container.
81423              * @param {Ext.view.View} this
81424              * @param {Ext.EventObject} e The raw event object
81425              */
81426             'containermouseover',
81427             /**
81428              * @event containermouseout
81429              * Fires when you move the mouse out of the container.
81430              * @param {Ext.view.View} this
81431              * @param {Ext.EventObject} e The raw event object
81432              */
81433             'containermouseout',
81434             /**
81435              * @event containerclick
81436              * Fires when the container is clicked.
81437              * @param {Ext.view.View} this
81438              * @param {Ext.EventObject} e The raw event object
81439              */
81440             'containerclick',
81441             /**
81442              * @event containerdblclick
81443              * Fires when the container is double clicked.
81444              * @param {Ext.view.View} this
81445              * @param {Ext.EventObject} e The raw event object
81446              */
81447             'containerdblclick',
81448             /**
81449              * @event containercontextmenu
81450              * Fires when the container is right clicked.
81451              * @param {Ext.view.View} this
81452              * @param {Ext.EventObject} e The raw event object
81453              */
81454             'containercontextmenu',
81455             /**
81456              * @event containerkeydown
81457              * Fires when a key is pressed while the container is focused, and no item is currently selected.
81458              * @param {Ext.view.View} this
81459              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
81460              */
81461             'containerkeydown',
81462
81463             /**
81464              * @event selectionchange
81465              * Fires when the selected nodes change. Relayed event from the underlying selection model.
81466              * @param {Ext.view.View} this
81467              * @param {HTMLElement[]} selections Array of the selected nodes
81468              */
81469             'selectionchange',
81470             /**
81471              * @event beforeselect
81472              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
81473              * @param {Ext.view.View} this
81474              * @param {HTMLElement} node The node to be selected
81475              * @param {HTMLElement[]} selections Array of currently selected nodes
81476              */
81477             'beforeselect'
81478         );
81479     },
81480     // private
81481     afterRender: function(){
81482         var me = this,
81483             listeners;
81484
81485         me.callParent();
81486
81487         listeners = {
81488             scope: me,
81489             /*
81490              * We need to make copies of this since some of the events fired here will end up triggering
81491              * a new event to be called and the shared event object will be mutated. In future we should
81492              * investigate if there are any issues with creating a new event object for each event that
81493              * is fired.
81494              */
81495             freezeEvent: true,
81496             click: me.handleEvent,
81497             mousedown: me.handleEvent,
81498             mouseup: me.handleEvent,
81499             dblclick: me.handleEvent,
81500             contextmenu: me.handleEvent,
81501             mouseover: me.handleEvent,
81502             mouseout: me.handleEvent,
81503             keydown: me.handleEvent
81504         };
81505
81506         me.mon(me.getTargetEl(), listeners);
81507
81508         if (me.store) {
81509             me.bindStore(me.store, true);
81510         }
81511     },
81512
81513     handleEvent: function(e) {
81514         if (this.processUIEvent(e) !== false) {
81515             this.processSpecialEvent(e);
81516         }
81517     },
81518
81519     // Private template method
81520     processItemEvent: Ext.emptyFn,
81521     processContainerEvent: Ext.emptyFn,
81522     processSpecialEvent: Ext.emptyFn,
81523
81524     /*
81525      * Returns true if this mouseover/out event is still over the overItem.
81526      */
81527     stillOverItem: function (event, overItem) {
81528         var nowOver;
81529
81530         // There is this weird bug when you hover over the border of a cell it is saying
81531         // the target is the table.
81532         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
81533         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
81534         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
81535         // this hard exception.
81536         if (overItem && typeof(overItem.offsetParent) === "object") {
81537             // mouseout : relatedTarget == nowOver, target == wasOver
81538             // mouseover: relatedTarget == wasOver, target == nowOver
81539             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
81540             return Ext.fly(overItem).contains(nowOver);
81541         }
81542
81543         return false;
81544     },
81545
81546     processUIEvent: function(e) {
81547         var me = this,
81548             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
81549             map = this.statics().EventMap,
81550             index, record,
81551             type = e.type,
81552             overItem = me.mouseOverItem,
81553             newType;
81554
81555         if (!item) {
81556             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
81557                 item = overItem;
81558             }
81559
81560             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
81561             if (type == 'keydown') {
81562                 record = me.getSelectionModel().getLastSelected();
81563                 if (record) {
81564                     item = me.getNode(record);
81565                 }
81566             }
81567         }
81568
81569         if (item) {
81570             index = me.indexOf(item);
81571             if (!record) {
81572                 record = me.getRecord(item);
81573             }
81574
81575             if (me.processItemEvent(record, item, index, e) === false) {
81576                 return false;
81577             }
81578
81579             newType = me.isNewItemEvent(item, e);
81580             if (newType === false) {
81581                 return false;
81582             }
81583
81584             if (
81585                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
81586                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
81587                 (me['onItem' + map[newType]](record, item, index, e) === false)
81588             ) {
81589                 return false;
81590             }
81591
81592             me.fireEvent('item' + newType, me, record, item, index, e);
81593         }
81594         else {
81595             if (
81596                 (me.processContainerEvent(e) === false) ||
81597                 (me['onBeforeContainer' + map[type]](e) === false) ||
81598                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
81599                 (me['onContainer' + map[type]](e) === false)
81600             ) {
81601                 return false;
81602             }
81603
81604             me.fireEvent('container' + type, me, e);
81605         }
81606
81607         return true;
81608     },
81609
81610     isNewItemEvent: function (item, e) {
81611         var me = this,
81612             overItem = me.mouseOverItem,
81613             type = e.type;
81614
81615         switch (type) {
81616             case 'mouseover':
81617                 if (item === overItem) {
81618                     return false;
81619                 }
81620                 me.mouseOverItem = item;
81621                 return 'mouseenter';
81622
81623             case 'mouseout':
81624                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
81625                 if (me.stillOverItem(e, overItem)) {
81626                     return false;
81627                 }
81628                 me.mouseOverItem = null;
81629                 return 'mouseleave';
81630         }
81631         return type;
81632     },
81633
81634     // private
81635     onItemMouseEnter: function(record, item, index, e) {
81636         if (this.trackOver) {
81637             this.highlightItem(item);
81638         }
81639     },
81640
81641     // private
81642     onItemMouseLeave : function(record, item, index, e) {
81643         if (this.trackOver) {
81644             this.clearHighlight();
81645         }
81646     },
81647
81648     // @private, template methods
81649     onItemMouseDown: Ext.emptyFn,
81650     onItemMouseUp: Ext.emptyFn,
81651     onItemFocus: Ext.emptyFn,
81652     onItemClick: Ext.emptyFn,
81653     onItemDblClick: Ext.emptyFn,
81654     onItemContextMenu: Ext.emptyFn,
81655     onItemKeyDown: Ext.emptyFn,
81656     onBeforeItemMouseDown: Ext.emptyFn,
81657     onBeforeItemMouseUp: Ext.emptyFn,
81658     onBeforeItemFocus: Ext.emptyFn,
81659     onBeforeItemMouseEnter: Ext.emptyFn,
81660     onBeforeItemMouseLeave: Ext.emptyFn,
81661     onBeforeItemClick: Ext.emptyFn,
81662     onBeforeItemDblClick: Ext.emptyFn,
81663     onBeforeItemContextMenu: Ext.emptyFn,
81664     onBeforeItemKeyDown: Ext.emptyFn,
81665
81666     // @private, template methods
81667     onContainerMouseDown: Ext.emptyFn,
81668     onContainerMouseUp: Ext.emptyFn,
81669     onContainerMouseOver: Ext.emptyFn,
81670     onContainerMouseOut: Ext.emptyFn,
81671     onContainerClick: Ext.emptyFn,
81672     onContainerDblClick: Ext.emptyFn,
81673     onContainerContextMenu: Ext.emptyFn,
81674     onContainerKeyDown: Ext.emptyFn,
81675     onBeforeContainerMouseDown: Ext.emptyFn,
81676     onBeforeContainerMouseUp: Ext.emptyFn,
81677     onBeforeContainerMouseOver: Ext.emptyFn,
81678     onBeforeContainerMouseOut: Ext.emptyFn,
81679     onBeforeContainerClick: Ext.emptyFn,
81680     onBeforeContainerDblClick: Ext.emptyFn,
81681     onBeforeContainerContextMenu: Ext.emptyFn,
81682     onBeforeContainerKeyDown: Ext.emptyFn,
81683
81684     /**
81685      * Highlights a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
81686      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
81687      * handle stepping through the list via keyboard navigation.
81688      * @param {HTMLElement} item The item to highlight
81689      */
81690     highlightItem: function(item) {
81691         var me = this;
81692         me.clearHighlight();
81693         me.highlightedItem = item;
81694         Ext.fly(item).addCls(me.overItemCls);
81695     },
81696
81697     /**
81698      * Un-highlights the currently highlighted item, if any.
81699      */
81700     clearHighlight: function() {
81701         var me = this,
81702             highlighted = me.highlightedItem;
81703
81704         if (highlighted) {
81705             Ext.fly(highlighted).removeCls(me.overItemCls);
81706             delete me.highlightedItem;
81707         }
81708     },
81709
81710     refresh: function() {
81711         var me = this;
81712         me.clearHighlight();
81713         me.callParent(arguments);
81714         if (!me.isFixedHeight()) {
81715             me.doComponentLayout();
81716         }
81717     }
81718 });
81719 /**
81720  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
81721  * @class Ext.layout.component.BoundList
81722  * @extends Ext.layout.component.Component
81723  * @private
81724  */
81725 Ext.define('Ext.layout.component.BoundList', {
81726     extend: 'Ext.layout.component.Component',
81727     alias: 'layout.boundlist',
81728
81729     type: 'component',
81730
81731     beforeLayout: function() {
81732         return this.callParent(arguments) || this.owner.refreshed > 0;
81733     },
81734
81735     onLayout : function(width, height) {
81736         var me = this,
81737             owner = me.owner,
81738             floating = owner.floating,
81739             el = owner.el,
81740             xy = el.getXY(),
81741             isNumber = Ext.isNumber,
81742             minWidth, maxWidth, minHeight, maxHeight,
81743             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
81744
81745         if (floating) {
81746             // Position offscreen so the natural width is not affected by the viewport's right edge
81747             el.setXY([-9999,-9999]);
81748         }
81749
81750         // Calculate initial layout
81751         me.setTargetSize(width, height);
81752
81753         // Handle min/maxWidth for auto-width
81754         if (!isNumber(width)) {
81755             minWidth = owner.minWidth;
81756             maxWidth = owner.maxWidth;
81757             if (isNumber(minWidth) || isNumber(maxWidth)) {
81758                 naturalWidth = el.getWidth();
81759                 if (naturalWidth < minWidth) {
81760                     constrainedWidth = minWidth;
81761                 }
81762                 else if (naturalWidth > maxWidth) {
81763                     constrainedWidth = maxWidth;
81764                 }
81765                 if (constrainedWidth) {
81766                     me.setTargetSize(constrainedWidth);
81767                 }
81768             }
81769         }
81770         // Handle min/maxHeight for auto-height
81771         if (!isNumber(height)) {
81772             minHeight = owner.minHeight;
81773             maxHeight = owner.maxHeight;
81774             if (isNumber(minHeight) || isNumber(maxHeight)) {
81775                 naturalHeight = el.getHeight();
81776                 if (naturalHeight < minHeight) {
81777                     constrainedHeight = minHeight;
81778                 }
81779                 else if (naturalHeight > maxHeight) {
81780                     constrainedHeight = maxHeight;
81781                 }
81782                 if (constrainedHeight) {
81783                     me.setTargetSize(undef, constrainedHeight);
81784                 }
81785             }
81786         }
81787
81788         if (floating) {
81789             // Restore position
81790             el.setXY(xy);
81791         }
81792     },
81793
81794     afterLayout: function() {
81795         var me = this,
81796             toolbar = me.owner.pagingToolbar;
81797         me.callParent();
81798         if (toolbar) {
81799             toolbar.doComponentLayout();
81800         }
81801     },
81802
81803     setTargetSize : function(width, height) {
81804         var me = this,
81805             owner = me.owner,
81806             listHeight = null,
81807             toolbar;
81808
81809         // Size the listEl
81810         if (Ext.isNumber(height)) {
81811             listHeight = height - owner.el.getFrameWidth('tb');
81812             toolbar = owner.pagingToolbar;
81813             if (toolbar) {
81814                 listHeight -= toolbar.getHeight();
81815             }
81816         }
81817         me.setElementSize(owner.listEl, null, listHeight);
81818
81819         me.callParent(arguments);
81820     }
81821
81822 });
81823
81824 /**
81825  * A simple class that renders text directly into a toolbar.
81826  *
81827  *     @example
81828  *     Ext.create('Ext.panel.Panel', {
81829  *         title: 'Panel with TextItem',
81830  *         width: 300,
81831  *         height: 200,
81832  *         tbar: [
81833  *             { xtype: 'tbtext', text: 'Sample Text Item' }
81834  *         ],
81835  *         renderTo: Ext.getBody()
81836  *     });
81837  *
81838  * @constructor
81839  * Creates a new TextItem
81840  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
81841  */
81842 Ext.define('Ext.toolbar.TextItem', {
81843     extend: 'Ext.toolbar.Item',
81844     requires: ['Ext.XTemplate'],
81845     alias: 'widget.tbtext',
81846     alternateClassName: 'Ext.Toolbar.TextItem',
81847
81848     /**
81849      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
81850      */
81851     text: '',
81852
81853     renderTpl: '{text}',
81854     //
81855     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
81856
81857     onRender : function() {
81858         Ext.apply(this.renderData, {
81859             text: this.text
81860         });
81861         this.callParent(arguments);
81862     },
81863
81864     /**
81865      * Updates this item's text, setting the text to be used as innerHTML.
81866      * @param {String} t The text to display (html accepted).
81867      */
81868     setText : function(t) {
81869         if (this.rendered) {
81870             this.el.update(t);
81871             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
81872         } else {
81873             this.text = t;
81874         }
81875     }
81876 });
81877 /**
81878  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
81879  * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
81880  * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
81881  * can provide a custom implementation.
81882  *
81883  * For example:
81884  *
81885  *     @example
81886  *     Ext.define('Ext.ux.CustomTrigger', {
81887  *         extend: 'Ext.form.field.Trigger',
81888  *         alias: 'widget.customtrigger',
81889  *
81890  *         // override onTriggerClick
81891  *         onTriggerClick: function() {
81892  *             Ext.Msg.alert('Status', 'You clicked my trigger!');
81893  *         }
81894  *     });
81895  *
81896  *     Ext.create('Ext.form.FormPanel', {
81897  *         title: 'Form with TriggerField',
81898  *         bodyPadding: 5,
81899  *         width: 350,
81900  *         renderTo: Ext.getBody(),
81901  *         items:[{
81902  *             xtype: 'customtrigger',
81903  *             fieldLabel: 'Sample Trigger',
81904  *             emptyText: 'click the trigger',
81905  *         }]
81906  *     });
81907  *
81908  * However, in general you will most likely want to use Trigger as the base class for a reusable component.
81909  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
81910  */
81911 Ext.define('Ext.form.field.Trigger', {
81912     extend:'Ext.form.field.Text',
81913     alias: ['widget.triggerfield', 'widget.trigger'],
81914     requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
81915     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
81916
81917     // note: {id} here is really {inputId}, but {cmpId} is available
81918     fieldSubTpl: [
81919         '<input id="{id}" type="{type}" ',
81920             '<tpl if="name">name="{name}" </tpl>',
81921             '<tpl if="size">size="{size}" </tpl>',
81922             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
81923             'class="{fieldCls} {typeCls}" autocomplete="off" />',
81924         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
81925             '{triggerEl}',
81926             '<div class="{clearCls}" role="presentation"></div>',
81927         '</div>',
81928         {
81929             compiled: true,
81930             disableFormats: true
81931         }
81932     ],
81933
81934     /**
81935      * @cfg {String} triggerCls
81936      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
81937      * by default and triggerCls will be **appended** if specified.
81938      */
81939
81940     /**
81941      * @cfg {String} [triggerBaseCls='x-form-trigger']
81942      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
81943      * addition to this class.
81944      */
81945     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
81946
81947     /**
81948      * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
81949      * The CSS class that is added to the div wrapping the trigger button(s).
81950      */
81951     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
81952
81953     /**
81954      * @cfg {Boolean} hideTrigger
81955      * true to hide the trigger element and display only the base text field
81956      */
81957     hideTrigger: false,
81958
81959     /**
81960      * @cfg {Boolean} editable
81961      * false to prevent the user from typing text directly into the field; the field can only have its value set via an
81962      * action invoked by the trigger.
81963      */
81964     editable: true,
81965
81966     /**
81967      * @cfg {Boolean} readOnly
81968      * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
81969      * options if the value is true.
81970      */
81971     readOnly: false,
81972
81973     /**
81974      * @cfg {Boolean} [selectOnFocus=false]
81975      * true to select any existing text in the field immediately on focus. Only applies when
81976      * {@link #editable editable} = true
81977      */
81978
81979     /**
81980      * @cfg {Boolean} repeatTriggerClick
81981      * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
81982      */
81983     repeatTriggerClick: false,
81984
81985
81986     /**
81987      * @hide
81988      * @method autoSize
81989      */
81990     autoSize: Ext.emptyFn,
81991     // private
81992     monitorTab: true,
81993     // private
81994     mimicing: false,
81995     // private
81996     triggerIndexRe: /trigger-index-(\d+)/,
81997
81998     componentLayout: 'triggerfield',
81999
82000     initComponent: function() {
82001         this.wrapFocusCls = this.triggerWrapCls + '-focus';
82002         this.callParent(arguments);
82003     },
82004
82005     // private
82006     onRender: function(ct, position) {
82007         var me = this,
82008             triggerCls,
82009             triggerBaseCls = me.triggerBaseCls,
82010             triggerWrapCls = me.triggerWrapCls,
82011             triggerConfigs = [],
82012             i;
82013
82014         // triggerCls is a synonym for trigger1Cls, so copy it.
82015         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
82016         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
82017         // trigger config objects that hold cls, handler, etc.
82018         if (!me.trigger1Cls) {
82019             me.trigger1Cls = me.triggerCls;
82020         }
82021
82022         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
82023         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
82024             triggerConfigs.push({
82025                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
82026                 role: 'button'
82027             });
82028         }
82029         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
82030
82031         /**
82032          * @property {Ext.Element} triggerWrap
82033          * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
82034          */
82035         me.addChildEls('triggerWrap');
82036
82037         Ext.applyIf(me.subTplData, {
82038             triggerWrapCls: triggerWrapCls,
82039             triggerEl: Ext.DomHelper.markup(triggerConfigs),
82040             clearCls: me.clearCls
82041         });
82042
82043         me.callParent(arguments);
82044
82045         /**
82046          * @property {Ext.CompositeElement} triggerEl
82047          * A composite of all the trigger button elements. Only set after the field has been rendered.
82048          */
82049         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
82050
82051         me.doc = Ext.getDoc();
82052         me.initTrigger();
82053     },
82054
82055     onEnable: function() {
82056         this.callParent();
82057         this.triggerWrap.unmask();
82058     },
82059     
82060     onDisable: function() {
82061         this.callParent();
82062         this.triggerWrap.mask();
82063     },
82064     
82065     afterRender: function() {
82066         this.callParent();
82067         this.updateEditState();
82068         this.triggerEl.unselectable();
82069     },
82070
82071     updateEditState: function() {
82072         var me = this,
82073             inputEl = me.inputEl,
82074             triggerWrap = me.triggerWrap,
82075             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
82076             displayed,
82077             readOnly;
82078
82079         if (me.rendered) {
82080             if (me.readOnly) {
82081                 inputEl.addCls(noeditCls);
82082                 readOnly = true;
82083                 displayed = false;
82084             } else {
82085                 if (me.editable) {
82086                     inputEl.removeCls(noeditCls);
82087                     readOnly = false;
82088                 } else {
82089                     inputEl.addCls(noeditCls);
82090                     readOnly = true;
82091                 }
82092                 displayed = !me.hideTrigger;
82093             }
82094
82095             triggerWrap.setDisplayed(displayed);
82096             inputEl.dom.readOnly = readOnly;
82097             me.doComponentLayout();
82098         }
82099     },
82100
82101     /**
82102      * Get the total width of the trigger button area. Only useful after the field has been rendered.
82103      * @return {Number} The trigger width
82104      */
82105     getTriggerWidth: function() {
82106         var me = this,
82107             triggerWrap = me.triggerWrap,
82108             totalTriggerWidth = 0;
82109         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
82110             me.triggerEl.each(function(trigger) {
82111                 totalTriggerWidth += trigger.getWidth();
82112             });
82113             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
82114         }
82115         return totalTriggerWidth;
82116     },
82117
82118     setHideTrigger: function(hideTrigger) {
82119         if (hideTrigger != this.hideTrigger) {
82120             this.hideTrigger = hideTrigger;
82121             this.updateEditState();
82122         }
82123     },
82124
82125     /**
82126      * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
82127      * option at config time.
82128      * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
82129      * will only be able to modify the field using the trigger. Will also add a click event to the text field which
82130      * will call the trigger. 
82131      */
82132     setEditable: function(editable) {
82133         if (editable != this.editable) {
82134             this.editable = editable;
82135             this.updateEditState();
82136         }
82137     },
82138
82139     /**
82140      * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
82141      * option at config time.
82142      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
82143      * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
82144      * and hideTrigger.
82145      */
82146     setReadOnly: function(readOnly) {
82147         if (readOnly != this.readOnly) {
82148             this.readOnly = readOnly;
82149             this.updateEditState();
82150         }
82151     },
82152
82153     // private
82154     initTrigger: function() {
82155         var me = this,
82156             triggerWrap = me.triggerWrap,
82157             triggerEl = me.triggerEl;
82158
82159         if (me.repeatTriggerClick) {
82160             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
82161                 preventDefault: true,
82162                 handler: function(cr, e) {
82163                     me.onTriggerWrapClick(e);
82164                 }
82165             });
82166         } else {
82167             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
82168         }
82169
82170         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
82171         triggerEl.each(function(el, c, i) {
82172             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
82173         });
82174         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
82175         triggerEl.each(function(el, c, i) {
82176             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
82177         });
82178     },
82179
82180     // private
82181     onDestroy: function() {
82182         var me = this;
82183         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
82184         delete me.doc;
82185         me.callParent();
82186     },
82187
82188     // private
82189     onFocus: function() {
82190         var me = this;
82191         me.callParent();
82192         if (!me.mimicing) {
82193             me.bodyEl.addCls(me.wrapFocusCls);
82194             me.mimicing = true;
82195             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
82196                 delay: 10
82197             });
82198             if (me.monitorTab) {
82199                 me.on('specialkey', me.checkTab, me);
82200             }
82201         }
82202     },
82203
82204     // private
82205     checkTab: function(me, e) {
82206         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
82207             this.triggerBlur();
82208         }
82209     },
82210
82211     // private
82212     onBlur: Ext.emptyFn,
82213
82214     // private
82215     mimicBlur: function(e) {
82216         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
82217             this.triggerBlur();
82218         }
82219     },
82220
82221     // private
82222     triggerBlur: function() {
82223         var me = this;
82224         me.mimicing = false;
82225         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
82226         if (me.monitorTab && me.inputEl) {
82227             me.un('specialkey', me.checkTab, me);
82228         }
82229         Ext.form.field.Trigger.superclass.onBlur.call(me);
82230         if (me.bodyEl) {
82231             me.bodyEl.removeCls(me.wrapFocusCls);
82232         }
82233     },
82234
82235     beforeBlur: Ext.emptyFn,
82236
82237     // private
82238     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
82239     validateBlur: function(e) {
82240         return true;
82241     },
82242
82243     // private
82244     // process clicks upon triggers.
82245     // determine which trigger index, and dispatch to the appropriate click handler
82246     onTriggerWrapClick: function(e) {
82247         var me = this,
82248             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
82249             match = t && t.className.match(me.triggerIndexRe),
82250             idx,
82251             triggerClickMethod;
82252
82253         if (match && !me.readOnly) {
82254             idx = parseInt(match[1], 10);
82255             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
82256             if (triggerClickMethod) {
82257                 triggerClickMethod.call(me, e);
82258             }
82259         }
82260     },
82261
82262     /**
82263      * @method onTriggerClick
82264      * @protected
82265      * The function that should handle the trigger's click event. This method does nothing by default until overridden
82266      * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
82267      * @param {Ext.EventObject} e
82268      */
82269     onTriggerClick: Ext.emptyFn
82270
82271     /**
82272      * @cfg {Boolean} grow @hide
82273      */
82274     /**
82275      * @cfg {Number} growMin @hide
82276      */
82277     /**
82278      * @cfg {Number} growMax @hide
82279      */
82280 });
82281
82282 /**
82283  * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
82284  * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
82285  * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
82286  * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
82287  * respectively.
82288  *
82289  * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
82290  * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
82291  * for the field.
82292  */
82293 Ext.define('Ext.form.field.Picker', {
82294     extend: 'Ext.form.field.Trigger',
82295     alias: 'widget.pickerfield',
82296     alternateClassName: 'Ext.form.Picker',
82297     requires: ['Ext.util.KeyNav'],
82298
82299     /**
82300      * @cfg {Boolean} matchFieldWidth
82301      * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
82302      */
82303     matchFieldWidth: true,
82304
82305     /**
82306      * @cfg {String} pickerAlign
82307      * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
82308      */
82309     pickerAlign: 'tl-bl?',
82310
82311     /**
82312      * @cfg {Number[]} pickerOffset
82313      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
82314      * Defaults to undefined.
82315      */
82316
82317     /**
82318      * @cfg {String} openCls
82319      * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
82320      * Defaults to 'x-pickerfield-open'.
82321      */
82322     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
82323
82324     /**
82325      * @property {Boolean} isExpanded
82326      * True if the picker is currently expanded, false if not.
82327      */
82328
82329     /**
82330      * @cfg {Boolean} editable
82331      * False to prevent the user from typing text directly into the field; the field can only have its value set via
82332      * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
82333      * field itself.
82334      */
82335     editable: true,
82336
82337
82338     initComponent: function() {
82339         this.callParent();
82340
82341         // Custom events
82342         this.addEvents(
82343             /**
82344              * @event expand
82345              * Fires when the field's picker is expanded.
82346              * @param {Ext.form.field.Picker} field This field instance
82347              */
82348             'expand',
82349             /**
82350              * @event collapse
82351              * Fires when the field's picker is collapsed.
82352              * @param {Ext.form.field.Picker} field This field instance
82353              */
82354             'collapse',
82355             /**
82356              * @event select
82357              * Fires when a value is selected via the picker.
82358              * @param {Ext.form.field.Picker} field This field instance
82359              * @param {Object} value The value that was selected. The exact type of this value is dependent on
82360              * the individual field and picker implementations.
82361              */
82362             'select'
82363         );
82364     },
82365
82366
82367     initEvents: function() {
82368         var me = this;
82369         me.callParent();
82370
82371         // Add handlers for keys to expand/collapse the picker
82372         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
82373             down: function() {
82374                 if (!me.isExpanded) {
82375                     // Don't call expand() directly as there may be additional processing involved before
82376                     // expanding, e.g. in the case of a ComboBox query.
82377                     me.onTriggerClick();
82378                 }
82379             },
82380             esc: me.collapse,
82381             scope: me,
82382             forceKeyDown: true
82383         });
82384
82385         // Non-editable allows opening the picker by clicking the field
82386         if (!me.editable) {
82387             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
82388         }
82389
82390         // Disable native browser autocomplete
82391         if (Ext.isGecko) {
82392             me.inputEl.dom.setAttribute('autocomplete', 'off');
82393         }
82394     },
82395
82396
82397     /**
82398      * Expands this field's picker dropdown.
82399      */
82400     expand: function() {
82401         var me = this,
82402             bodyEl, picker, collapseIf;
82403
82404         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
82405             bodyEl = me.bodyEl;
82406             picker = me.getPicker();
82407             collapseIf = me.collapseIf;
82408
82409             // show the picker and set isExpanded flag
82410             picker.show();
82411             me.isExpanded = true;
82412             me.alignPicker();
82413             bodyEl.addCls(me.openCls);
82414
82415             // monitor clicking and mousewheel
82416             me.mon(Ext.getDoc(), {
82417                 mousewheel: collapseIf,
82418                 mousedown: collapseIf,
82419                 scope: me
82420             });
82421             Ext.EventManager.onWindowResize(me.alignPicker, me);
82422             me.fireEvent('expand', me);
82423             me.onExpand();
82424         }
82425     },
82426
82427     onExpand: Ext.emptyFn,
82428
82429     /**
82430      * Aligns the picker to the input element
82431      * @protected
82432      */
82433     alignPicker: function() {
82434         var me = this,
82435             picker;
82436
82437         if (me.isExpanded) {
82438             picker = me.getPicker();
82439             if (me.matchFieldWidth) {
82440                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
82441                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
82442             }
82443             if (picker.isFloating()) {
82444                 me.doAlign();
82445             }
82446         }
82447     },
82448
82449     /**
82450      * Performs the alignment on the picker using the class defaults
82451      * @private
82452      */
82453     doAlign: function(){
82454         var me = this,
82455             picker = me.picker,
82456             aboveSfx = '-above',
82457             isAbove;
82458
82459         me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
82460         // add the {openCls}-above class if the picker was aligned above
82461         // the field due to hitting the bottom of the viewport
82462         isAbove = picker.el.getY() < me.inputEl.getY();
82463         me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
82464         picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
82465     },
82466
82467     /**
82468      * Collapses this field's picker dropdown.
82469      */
82470     collapse: function() {
82471         if (this.isExpanded && !this.isDestroyed) {
82472             var me = this,
82473                 openCls = me.openCls,
82474                 picker = me.picker,
82475                 doc = Ext.getDoc(),
82476                 collapseIf = me.collapseIf,
82477                 aboveSfx = '-above';
82478
82479             // hide the picker and set isExpanded flag
82480             picker.hide();
82481             me.isExpanded = false;
82482
82483             // remove the openCls
82484             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
82485             picker.el.removeCls(picker.baseCls + aboveSfx);
82486
82487             // remove event listeners
82488             doc.un('mousewheel', collapseIf, me);
82489             doc.un('mousedown', collapseIf, me);
82490             Ext.EventManager.removeResizeListener(me.alignPicker, me);
82491             me.fireEvent('collapse', me);
82492             me.onCollapse();
82493         }
82494     },
82495
82496     onCollapse: Ext.emptyFn,
82497
82498
82499     /**
82500      * @private
82501      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
82502      */
82503     collapseIf: function(e) {
82504         var me = this;
82505         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
82506             me.collapse();
82507         }
82508     },
82509
82510     /**
82511      * Returns a reference to the picker component for this field, creating it if necessary by
82512      * calling {@link #createPicker}.
82513      * @return {Ext.Component} The picker component
82514      */
82515     getPicker: function() {
82516         var me = this;
82517         return me.picker || (me.picker = me.createPicker());
82518     },
82519
82520     /**
82521      * @method
82522      * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
82523      * The current field should also be passed as a configuration option to the picker component as the pickerField
82524      * property.
82525      */
82526     createPicker: Ext.emptyFn,
82527
82528     /**
82529      * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
82530      * @protected
82531      */
82532     onTriggerClick: function() {
82533         var me = this;
82534         if (!me.readOnly && !me.disabled) {
82535             if (me.isExpanded) {
82536                 me.collapse();
82537             } else {
82538                 me.expand();
82539             }
82540             me.inputEl.focus();
82541         }
82542     },
82543
82544     mimicBlur: function(e) {
82545         var me = this,
82546             picker = me.picker;
82547         // ignore mousedown events within the picker element
82548         if (!picker || !e.within(picker.el, false, true)) {
82549             me.callParent(arguments);
82550         }
82551     },
82552
82553     onDestroy : function(){
82554         var me = this,
82555             picker = me.picker;
82556
82557         Ext.EventManager.removeResizeListener(me.alignPicker, me);
82558         Ext.destroy(me.keyNav);
82559         if (picker) {
82560             delete picker.pickerField;
82561             picker.destroy();
82562         }
82563         me.callParent();
82564     }
82565
82566 });
82567
82568
82569 /**
82570  * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
82571  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
82572  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
82573  * field which uses the spinner to increment and decrement the field's value by its
82574  * {@link Ext.form.field.Number#step step} config value.
82575  *
82576  * For example:
82577  *
82578  *     @example
82579  *     Ext.define('Ext.ux.CustomSpinner', {
82580  *         extend: 'Ext.form.field.Spinner',
82581  *         alias: 'widget.customspinner',
82582  *
82583  *         // override onSpinUp (using step isn't neccessary)
82584  *         onSpinUp: function() {
82585  *             var me = this;
82586  *             if (!me.readOnly) {
82587  *                 var val = me.step; // set the default value to the step value
82588  *                 if(me.getValue() !== '') {
82589  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
82590  *                 }
82591  *                 me.setValue((val + me.step) + ' Pack');
82592  *             }
82593  *         },
82594  *
82595  *         // override onSpinDown
82596  *         onSpinDown: function() {
82597  *             var val, me = this;
82598  *             if (!me.readOnly) {
82599  *                 if(me.getValue() !== '') {
82600  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
82601  *                 }
82602  *                 me.setValue((val - me.step) + ' Pack');
82603  *             }
82604  *         }
82605  *     });
82606  *
82607  *     Ext.create('Ext.form.FormPanel', {
82608  *         title: 'Form with SpinnerField',
82609  *         bodyPadding: 5,
82610  *         width: 350,
82611  *         renderTo: Ext.getBody(),
82612  *         items:[{
82613  *             xtype: 'customspinner',
82614  *             fieldLabel: 'How Much Beer?',
82615  *             step: 6
82616  *         }]
82617  *     });
82618  *
82619  * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
82620  * to prevent this, set `{@link #keyNavEnabled} = false`.
82621  */
82622 Ext.define('Ext.form.field.Spinner', {
82623     extend: 'Ext.form.field.Trigger',
82624     alias: 'widget.spinnerfield',
82625     alternateClassName: 'Ext.form.Spinner',
82626     requires: ['Ext.util.KeyNav'],
82627
82628     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
82629     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
82630
82631     /**
82632      * @cfg {Boolean} spinUpEnabled
82633      * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
82634      * created, use the {@link #setSpinUpEnabled} method.
82635      */
82636     spinUpEnabled: true,
82637
82638     /**
82639      * @cfg {Boolean} spinDownEnabled
82640      * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
82641      * created, use the {@link #setSpinDownEnabled} method.
82642      */
82643     spinDownEnabled: true,
82644
82645     /**
82646      * @cfg {Boolean} keyNavEnabled
82647      * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
82648      */
82649     keyNavEnabled: true,
82650
82651     /**
82652      * @cfg {Boolean} mouseWheelEnabled
82653      * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
82654      * Defaults to true.
82655      */
82656     mouseWheelEnabled: true,
82657
82658     /**
82659      * @cfg {Boolean} repeatTriggerClick
82660      * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
82661      * Defaults to true.
82662      */
82663     repeatTriggerClick: true,
82664
82665     /**
82666      * @method
82667      * @protected
82668      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
82669      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
82670      */
82671     onSpinUp: Ext.emptyFn,
82672
82673     /**
82674      * @method
82675      * @protected
82676      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
82677      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
82678      */
82679     onSpinDown: Ext.emptyFn,
82680
82681     initComponent: function() {
82682         this.callParent();
82683
82684         this.addEvents(
82685             /**
82686              * @event spin
82687              * Fires when the spinner is made to spin up or down.
82688              * @param {Ext.form.field.Spinner} this
82689              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
82690              */
82691             'spin',
82692
82693             /**
82694              * @event spinup
82695              * Fires when the spinner is made to spin up.
82696              * @param {Ext.form.field.Spinner} this
82697              */
82698             'spinup',
82699
82700             /**
82701              * @event spindown
82702              * Fires when the spinner is made to spin down.
82703              * @param {Ext.form.field.Spinner} this
82704              */
82705             'spindown'
82706         );
82707     },
82708
82709     /**
82710      * @private
82711      * Override.
82712      */
82713     onRender: function() {
82714         var me = this,
82715             triggers;
82716
82717         me.callParent(arguments);
82718         triggers = me.triggerEl;
82719
82720         /**
82721          * @property {Ext.Element} spinUpEl
82722          * The spinner up button element
82723          */
82724         me.spinUpEl = triggers.item(0);
82725         /**
82726          * @property {Ext.Element} spinDownEl
82727          * The spinner down button element
82728          */
82729         me.spinDownEl = triggers.item(1);
82730
82731         // Set initial enabled/disabled states
82732         me.setSpinUpEnabled(me.spinUpEnabled);
82733         me.setSpinDownEnabled(me.spinDownEnabled);
82734
82735         // Init up/down arrow keys
82736         if (me.keyNavEnabled) {
82737             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
82738                 scope: me,
82739                 up: me.spinUp,
82740                 down: me.spinDown
82741             });
82742         }
82743
82744         // Init mouse wheel
82745         if (me.mouseWheelEnabled) {
82746             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
82747         }
82748     },
82749
82750     /**
82751      * @private
82752      * Override. Since the triggers are stacked, only measure the width of one of them.
82753      */
82754     getTriggerWidth: function() {
82755         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
82756     },
82757
82758     /**
82759      * @private
82760      * Handles the spinner up button clicks.
82761      */
82762     onTrigger1Click: function() {
82763         this.spinUp();
82764     },
82765
82766     /**
82767      * @private
82768      * Handles the spinner down button clicks.
82769      */
82770     onTrigger2Click: function() {
82771         this.spinDown();
82772     },
82773
82774     /**
82775      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
82776      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
82777      * is false.
82778      */
82779     spinUp: function() {
82780         var me = this;
82781         if (me.spinUpEnabled && !me.disabled) {
82782             me.fireEvent('spin', me, 'up');
82783             me.fireEvent('spinup', me);
82784             me.onSpinUp();
82785         }
82786     },
82787
82788     /**
82789      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
82790      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
82791      * is false.
82792      */
82793     spinDown: function() {
82794         var me = this;
82795         if (me.spinDownEnabled && !me.disabled) {
82796             me.fireEvent('spin', me, 'down');
82797             me.fireEvent('spindown', me);
82798             me.onSpinDown();
82799         }
82800     },
82801
82802     /**
82803      * Sets whether the spinner up button is enabled.
82804      * @param {Boolean} enabled true to enable the button, false to disable it.
82805      */
82806     setSpinUpEnabled: function(enabled) {
82807         var me = this,
82808             wasEnabled = me.spinUpEnabled;
82809         me.spinUpEnabled = enabled;
82810         if (wasEnabled !== enabled && me.rendered) {
82811             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
82812         }
82813     },
82814
82815     /**
82816      * Sets whether the spinner down button is enabled.
82817      * @param {Boolean} enabled true to enable the button, false to disable it.
82818      */
82819     setSpinDownEnabled: function(enabled) {
82820         var me = this,
82821             wasEnabled = me.spinDownEnabled;
82822         me.spinDownEnabled = enabled;
82823         if (wasEnabled !== enabled && me.rendered) {
82824             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
82825         }
82826     },
82827
82828     /**
82829      * @private
82830      * Handles mousewheel events on the field
82831      */
82832     onMouseWheel: function(e) {
82833         var me = this,
82834             delta;
82835         if (me.hasFocus) {
82836             delta = e.getWheelDelta();
82837             if (delta > 0) {
82838                 me.spinUp();
82839             }
82840             else if (delta < 0) {
82841                 me.spinDown();
82842             }
82843             e.stopEvent();
82844         }
82845     },
82846
82847     onDestroy: function() {
82848         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
82849         this.callParent();
82850     }
82851
82852 });
82853 /**
82854  * @docauthor Jason Johnston <jason@sencha.com>
82855  *
82856  * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
82857  * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
82858  * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
82859  * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
82860  *
82861  * By default, the number field is also rendered with a set of up/down spinner buttons and has
82862  * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
82863  * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
82864  * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
82865  * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
82866  *
82867  * # Example usage
82868  *
82869  *     @example
82870  *     Ext.create('Ext.form.Panel', {
82871  *         title: 'On The Wall',
82872  *         width: 300,
82873  *         bodyPadding: 10,
82874  *         renderTo: Ext.getBody(),
82875  *         items: [{
82876  *             xtype: 'numberfield',
82877  *             anchor: '100%',
82878  *             name: 'bottles',
82879  *             fieldLabel: 'Bottles of Beer',
82880  *             value: 99,
82881  *             maxValue: 99,
82882  *             minValue: 0
82883  *         }],
82884  *         buttons: [{
82885  *             text: 'Take one down, pass it around',
82886  *             handler: function() {
82887  *                 this.up('form').down('[name=bottles]').spinDown();
82888  *             }
82889  *         }]
82890  *     });
82891  *
82892  * # Removing UI Enhancements
82893  *
82894  *     @example
82895  *     Ext.create('Ext.form.Panel', {
82896  *         title: 'Personal Info',
82897  *         width: 300,
82898  *         bodyPadding: 10,
82899  *         renderTo: Ext.getBody(),
82900  *         items: [{
82901  *             xtype: 'numberfield',
82902  *             anchor: '100%',
82903  *             name: 'age',
82904  *             fieldLabel: 'Age',
82905  *             minValue: 0, //prevents negative numbers
82906  *
82907  *             // Remove spinner buttons, and arrow key and mouse wheel listeners
82908  *             hideTrigger: true,
82909  *             keyNavEnabled: false,
82910  *             mouseWheelEnabled: false
82911  *         }]
82912  *     });
82913  *
82914  * # Using Step
82915  *
82916  *     @example
82917  *     Ext.create('Ext.form.Panel', {
82918  *         renderTo: Ext.getBody(),
82919  *         title: 'Step',
82920  *         width: 300,
82921  *         bodyPadding: 10,
82922  *         items: [{
82923  *             xtype: 'numberfield',
82924  *             anchor: '100%',
82925  *             name: 'evens',
82926  *             fieldLabel: 'Even Numbers',
82927  *
82928  *             // Set step so it skips every other number
82929  *             step: 2,
82930  *             value: 0,
82931  *
82932  *             // Add change handler to force user-entered numbers to evens
82933  *             listeners: {
82934  *                 change: function(field, value) {
82935  *                     value = parseInt(value, 10);
82936  *                     field.setValue(value + value % 2);
82937  *                 }
82938  *             }
82939  *         }]
82940  *     });
82941  */
82942 Ext.define('Ext.form.field.Number', {
82943     extend:'Ext.form.field.Spinner',
82944     alias: 'widget.numberfield',
82945     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
82946
82947     /**
82948      * @cfg {RegExp} stripCharsRe @hide
82949      */
82950     /**
82951      * @cfg {RegExp} maskRe @hide
82952      */
82953
82954     /**
82955      * @cfg {Boolean} allowDecimals
82956      * False to disallow decimal values
82957      */
82958     allowDecimals : true,
82959
82960     /**
82961      * @cfg {String} decimalSeparator
82962      * Character(s) to allow as the decimal separator
82963      */
82964     decimalSeparator : '.',
82965
82966     /**
82967      * @cfg {Number} decimalPrecision
82968      * The maximum precision to display after the decimal separator
82969      */
82970     decimalPrecision : 2,
82971
82972     /**
82973      * @cfg {Number} minValue
82974      * The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by the field's validation logic,
82975      * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
82976      */
82977     minValue: Number.NEGATIVE_INFINITY,
82978
82979     /**
82980      * @cfg {Number} maxValue
82981      * The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by the field's validation logic, and for
82982      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
82983      */
82984     maxValue: Number.MAX_VALUE,
82985
82986     /**
82987      * @cfg {Number} step
82988      * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
82989      * the spinner.
82990      */
82991     step: 1,
82992
82993     /**
82994      * @cfg {String} minText
82995      * Error text to display if the minimum value validation fails.
82996      */
82997     minText : 'The minimum value for this field is {0}',
82998
82999     /**
83000      * @cfg {String} maxText
83001      * Error text to display if the maximum value validation fails.
83002      */
83003     maxText : 'The maximum value for this field is {0}',
83004
83005     /**
83006      * @cfg {String} nanText
83007      * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
83008      * '.' or '-' is left in the field with no number.
83009      */
83010     nanText : '{0} is not a valid number',
83011
83012     /**
83013      * @cfg {String} negativeText
83014      * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
83015      * {@link #minText} in that circumstance only.
83016      */
83017     negativeText : 'The value cannot be negative',
83018
83019     /**
83020      * @cfg {String} baseChars
83021      * The base set of characters to evaluate as valid numbers.
83022      */
83023     baseChars : '0123456789',
83024
83025     /**
83026      * @cfg {Boolean} autoStripChars
83027      * True to automatically strip not allowed characters from the field.
83028      */
83029     autoStripChars: false,
83030
83031     initComponent: function() {
83032         var me = this,
83033             allowed;
83034
83035         me.callParent();
83036
83037         me.setMinValue(me.minValue);
83038         me.setMaxValue(me.maxValue);
83039
83040         // Build regexes for masking and stripping based on the configured options
83041         if (me.disableKeyFilter !== true) {
83042             allowed = me.baseChars + '';
83043             if (me.allowDecimals) {
83044                 allowed += me.decimalSeparator;
83045             }
83046             if (me.minValue < 0) {
83047                 allowed += '-';
83048             }
83049             allowed = Ext.String.escapeRegex(allowed);
83050             me.maskRe = new RegExp('[' + allowed + ']');
83051             if (me.autoStripChars) {
83052                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
83053             }
83054         }
83055     },
83056
83057     /**
83058      * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
83059      * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
83060      * that the value is a number, and that it is within the configured min and max values.
83061      * @param {Object} [value] The value to get errors for (defaults to the current field value)
83062      * @return {String[]} All validation errors for this field
83063      */
83064     getErrors: function(value) {
83065         var me = this,
83066             errors = me.callParent(arguments),
83067             format = Ext.String.format,
83068             num;
83069
83070         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
83071
83072         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
83073              return errors;
83074         }
83075
83076         value = String(value).replace(me.decimalSeparator, '.');
83077
83078         if(isNaN(value)){
83079             errors.push(format(me.nanText, value));
83080         }
83081
83082         num = me.parseValue(value);
83083
83084         if (me.minValue === 0 && num < 0) {
83085             errors.push(this.negativeText);
83086         }
83087         else if (num < me.minValue) {
83088             errors.push(format(me.minText, me.minValue));
83089         }
83090
83091         if (num > me.maxValue) {
83092             errors.push(format(me.maxText, me.maxValue));
83093         }
83094
83095
83096         return errors;
83097     },
83098
83099     rawToValue: function(rawValue) {
83100         var value = this.fixPrecision(this.parseValue(rawValue));
83101         if (value === null) {
83102             value = rawValue || null;
83103         }
83104         return  value;
83105     },
83106
83107     valueToRaw: function(value) {
83108         var me = this,
83109             decimalSeparator = me.decimalSeparator;
83110         value = me.parseValue(value);
83111         value = me.fixPrecision(value);
83112         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
83113         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
83114         return value;
83115     },
83116
83117     onChange: function() {
83118         var me = this,
83119             value = me.getValue(),
83120             valueIsNull = value === null;
83121
83122         me.callParent(arguments);
83123
83124         // Update the spinner buttons
83125         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
83126         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
83127     },
83128
83129     /**
83130      * Replaces any existing {@link #minValue} with the new value.
83131      * @param {Number} value The minimum value
83132      */
83133     setMinValue : function(value) {
83134         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
83135     },
83136
83137     /**
83138      * Replaces any existing {@link #maxValue} with the new value.
83139      * @param {Number} value The maximum value
83140      */
83141     setMaxValue: function(value) {
83142         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
83143     },
83144
83145     // private
83146     parseValue : function(value) {
83147         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
83148         return isNaN(value) ? null : value;
83149     },
83150
83151     /**
83152      * @private
83153      */
83154     fixPrecision : function(value) {
83155         var me = this,
83156             nan = isNaN(value),
83157             precision = me.decimalPrecision;
83158
83159         if (nan || !value) {
83160             return nan ? '' : value;
83161         } else if (!me.allowDecimals || precision <= 0) {
83162             precision = 0;
83163         }
83164
83165         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
83166     },
83167
83168     beforeBlur : function() {
83169         var me = this,
83170             v = me.parseValue(me.getRawValue());
83171
83172         if (!Ext.isEmpty(v)) {
83173             me.setValue(v);
83174         }
83175     },
83176
83177     onSpinUp: function() {
83178         var me = this;
83179         if (!me.readOnly) {
83180             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
83181         }
83182     },
83183
83184     onSpinDown: function() {
83185         var me = this;
83186         if (!me.readOnly) {
83187             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
83188         }
83189     }
83190 });
83191
83192 /**
83193  * As the number of records increases, the time required for the browser to render them increases. Paging is used to
83194  * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
83195  * available screen area, vertical scrollbars will be added.
83196  *
83197  * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
83198  * which the server needs to interpret and then respond with the appropriate data.
83199  *
83200  * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
83201  * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing
83202  * parameters used for paging criteria.
83203  *
83204  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
83205  *
83206  * Paging Toolbar is typically used as one of the Grid's toolbars:
83207  *
83208  *     @example
83209  *     var itemsPerPage = 2;   // set the number of items you want per page
83210  *
83211  *     var store = Ext.create('Ext.data.Store', {
83212  *         id:'simpsonsStore',
83213  *         autoLoad: false,
83214  *         fields:['name', 'email', 'phone'],
83215  *         pageSize: itemsPerPage, // items per page
83216  *         proxy: {
83217  *             type: 'ajax',
83218  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
83219  *             reader: {
83220  *                 type: 'json',
83221  *                 root: 'items',
83222  *                 totalProperty: 'total'
83223  *             }
83224  *         }
83225  *     });
83226  *
83227  *     // specify segment of data you want to load using params
83228  *     store.load({
83229  *         params:{
83230  *             start:0,
83231  *             limit: itemsPerPage
83232  *         }
83233  *     });
83234  *
83235  *     Ext.create('Ext.grid.Panel', {
83236  *         title: 'Simpsons',
83237  *         store: store,
83238  *         columns: [
83239  *             { header: 'Name',  dataIndex: 'name' },
83240  *             { header: 'Email', dataIndex: 'email', flex: 1 },
83241  *             { header: 'Phone', dataIndex: 'phone' }
83242  *         ],
83243  *         width: 400,
83244  *         height: 125,
83245  *         dockedItems: [{
83246  *             xtype: 'pagingtoolbar',
83247  *             store: store,   // same store GridPanel is using
83248  *             dock: 'bottom',
83249  *             displayInfo: true
83250  *         }],
83251  *         renderTo: Ext.getBody()
83252  *     });
83253  *
83254  * To use paging, pass the paging requirements to the server when the store is first loaded.
83255  *
83256  *     store.load({
83257  *         params: {
83258  *             // specify params for the first page load if using paging
83259  *             start: 0,
83260  *             limit: myPageSize,
83261  *             // other params
83262  *             foo:   'bar'
83263  *         }
83264  *     });
83265  *
83266  * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
83267  *
83268  *     var myStore = Ext.create('Ext.data.Store', {
83269  *         {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
83270  *         ...
83271  *     });
83272  *
83273  * The packet sent back from the server would have this form:
83274  *
83275  *     {
83276  *         "success": true,
83277  *         "results": 2000,
83278  *         "rows": [ // ***Note:** this must be an Array
83279  *             { "id":  1, "name": "Bill", "occupation": "Gardener" },
83280  *             { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
83281  *             ...
83282  *             { "id": 25, "name":  "Sue", "occupation": "Botanist" }
83283  *         ]
83284  *     }
83285  *
83286  * ## Paging with Local Data
83287  *
83288  * Paging can also be accomplished with local data using extensions:
83289  *
83290  *   - [Ext.ux.data.PagingStore][1]
83291  *   - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
83292  *
83293  *    [1]: http://sencha.com/forum/showthread.php?t=71532
83294  */
83295 Ext.define('Ext.toolbar.Paging', {
83296     extend: 'Ext.toolbar.Toolbar',
83297     alias: 'widget.pagingtoolbar',
83298     alternateClassName: 'Ext.PagingToolbar',
83299     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
83300     /**
83301      * @cfg {Ext.data.Store} store (required)
83302      * The {@link Ext.data.Store} the paging toolbar should use as its data source.
83303      */
83304
83305     /**
83306      * @cfg {Boolean} displayInfo
83307      * true to display the displayMsg
83308      */
83309     displayInfo: false,
83310
83311     /**
83312      * @cfg {Boolean} prependButtons
83313      * true to insert any configured items _before_ the paging buttons.
83314      */
83315     prependButtons: false,
83316
83317     /**
83318      * @cfg {String} displayMsg
83319      * The paging status message to display. Note that this string is
83320      * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
83321      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
83322      */
83323     displayMsg : 'Displaying {0} - {1} of {2}',
83324
83325     /**
83326      * @cfg {String} emptyMsg
83327      * The message to display when no records are found.
83328      */
83329     emptyMsg : 'No data to display',
83330
83331     /**
83332      * @cfg {String} beforePageText
83333      * The text displayed before the input item.
83334      */
83335     beforePageText : 'Page',
83336
83337     /**
83338      * @cfg {String} afterPageText
83339      * Customizable piece of the default paging text. Note that this string is formatted using
83340      * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
83341      * string if showing the total page count is desired.
83342      */
83343     afterPageText : 'of {0}',
83344
83345     /**
83346      * @cfg {String} firstText
83347      * The quicktip text displayed for the first page button.
83348      * **Note**: quick tips must be initialized for the quicktip to show.
83349      */
83350     firstText : 'First Page',
83351
83352     /**
83353      * @cfg {String} prevText
83354      * The quicktip text displayed for the previous page button.
83355      * **Note**: quick tips must be initialized for the quicktip to show.
83356      */
83357     prevText : 'Previous Page',
83358
83359     /**
83360      * @cfg {String} nextText
83361      * The quicktip text displayed for the next page button.
83362      * **Note**: quick tips must be initialized for the quicktip to show.
83363      */
83364     nextText : 'Next Page',
83365
83366     /**
83367      * @cfg {String} lastText
83368      * The quicktip text displayed for the last page button.
83369      * **Note**: quick tips must be initialized for the quicktip to show.
83370      */
83371     lastText : 'Last Page',
83372
83373     /**
83374      * @cfg {String} refreshText
83375      * The quicktip text displayed for the Refresh button.
83376      * **Note**: quick tips must be initialized for the quicktip to show.
83377      */
83378     refreshText : 'Refresh',
83379
83380     /**
83381      * @cfg {Number} inputItemWidth
83382      * The width in pixels of the input field used to display and change the current page number.
83383      */
83384     inputItemWidth : 30,
83385
83386     /**
83387      * Gets the standard paging items in the toolbar
83388      * @private
83389      */
83390     getPagingItems: function() {
83391         var me = this;
83392
83393         return [{
83394             itemId: 'first',
83395             tooltip: me.firstText,
83396             overflowText: me.firstText,
83397             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
83398             disabled: true,
83399             handler: me.moveFirst,
83400             scope: me
83401         },{
83402             itemId: 'prev',
83403             tooltip: me.prevText,
83404             overflowText: me.prevText,
83405             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
83406             disabled: true,
83407             handler: me.movePrevious,
83408             scope: me
83409         },
83410         '-',
83411         me.beforePageText,
83412         {
83413             xtype: 'numberfield',
83414             itemId: 'inputItem',
83415             name: 'inputItem',
83416             cls: Ext.baseCSSPrefix + 'tbar-page-number',
83417             allowDecimals: false,
83418             minValue: 1,
83419             hideTrigger: true,
83420             enableKeyEvents: true,
83421             selectOnFocus: true,
83422             submitValue: false,
83423             width: me.inputItemWidth,
83424             margins: '-1 2 3 2',
83425             listeners: {
83426                 scope: me,
83427                 keydown: me.onPagingKeyDown,
83428                 blur: me.onPagingBlur
83429             }
83430         },{
83431             xtype: 'tbtext',
83432             itemId: 'afterTextItem',
83433             text: Ext.String.format(me.afterPageText, 1)
83434         },
83435         '-',
83436         {
83437             itemId: 'next',
83438             tooltip: me.nextText,
83439             overflowText: me.nextText,
83440             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
83441             disabled: true,
83442             handler: me.moveNext,
83443             scope: me
83444         },{
83445             itemId: 'last',
83446             tooltip: me.lastText,
83447             overflowText: me.lastText,
83448             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
83449             disabled: true,
83450             handler: me.moveLast,
83451             scope: me
83452         },
83453         '-',
83454         {
83455             itemId: 'refresh',
83456             tooltip: me.refreshText,
83457             overflowText: me.refreshText,
83458             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
83459             handler: me.doRefresh,
83460             scope: me
83461         }];
83462     },
83463
83464     initComponent : function(){
83465         var me = this,
83466             pagingItems = me.getPagingItems(),
83467             userItems   = me.items || me.buttons || [];
83468
83469         if (me.prependButtons) {
83470             me.items = userItems.concat(pagingItems);
83471         } else {
83472             me.items = pagingItems.concat(userItems);
83473         }
83474         delete me.buttons;
83475
83476         if (me.displayInfo) {
83477             me.items.push('->');
83478             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
83479         }
83480
83481         me.callParent();
83482
83483         me.addEvents(
83484             /**
83485              * @event change
83486              * Fires after the active page has been changed.
83487              * @param {Ext.toolbar.Paging} this
83488              * @param {Object} pageData An object that has these properties:
83489              *
83490              * - `total` : Number
83491              *
83492              *   The total number of records in the dataset as returned by the server
83493              *
83494              * - `currentPage` : Number
83495              *
83496              *   The current page number
83497              *
83498              * - `pageCount` : Number
83499              *
83500              *   The total number of pages (calculated from the total number of records in the dataset as returned by the
83501              *   server and the current {@link Ext.data.Store#pageSize pageSize})
83502              *
83503              * - `toRecord` : Number
83504              *
83505              *   The starting record index for the current page
83506              *
83507              * - `fromRecord` : Number
83508              *
83509              *   The ending record index for the current page
83510              */
83511             'change',
83512
83513             /**
83514              * @event beforechange
83515              * Fires just before the active page is changed. Return false to prevent the active page from being changed.
83516              * @param {Ext.toolbar.Paging} this
83517              * @param {Number} page The page number that will be loaded on change
83518              */
83519             'beforechange'
83520         );
83521         me.on('afterlayout', me.onLoad, me, {single: true});
83522
83523         me.bindStore(me.store || 'ext-empty-store', true);
83524     },
83525     // private
83526     updateInfo : function(){
83527         var me = this,
83528             displayItem = me.child('#displayItem'),
83529             store = me.store,
83530             pageData = me.getPageData(),
83531             count, msg;
83532
83533         if (displayItem) {
83534             count = store.getCount();
83535             if (count === 0) {
83536                 msg = me.emptyMsg;
83537             } else {
83538                 msg = Ext.String.format(
83539                     me.displayMsg,
83540                     pageData.fromRecord,
83541                     pageData.toRecord,
83542                     pageData.total
83543                 );
83544             }
83545             displayItem.setText(msg);
83546             me.doComponentLayout();
83547         }
83548     },
83549
83550     // private
83551     onLoad : function(){
83552         var me = this,
83553             pageData,
83554             currPage,
83555             pageCount,
83556             afterText;
83557
83558         if (!me.rendered) {
83559             return;
83560         }
83561
83562         pageData = me.getPageData();
83563         currPage = pageData.currentPage;
83564         pageCount = pageData.pageCount;
83565         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
83566
83567         me.child('#afterTextItem').setText(afterText);
83568         me.child('#inputItem').setValue(currPage);
83569         me.child('#first').setDisabled(currPage === 1);
83570         me.child('#prev').setDisabled(currPage === 1);
83571         me.child('#next').setDisabled(currPage === pageCount);
83572         me.child('#last').setDisabled(currPage === pageCount);
83573         me.child('#refresh').enable();
83574         me.updateInfo();
83575         me.fireEvent('change', me, pageData);
83576     },
83577
83578     // private
83579     getPageData : function(){
83580         var store = this.store,
83581             totalCount = store.getTotalCount();
83582
83583         return {
83584             total : totalCount,
83585             currentPage : store.currentPage,
83586             pageCount: Math.ceil(totalCount / store.pageSize),
83587             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
83588             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
83589
83590         };
83591     },
83592
83593     // private
83594     onLoadError : function(){
83595         if (!this.rendered) {
83596             return;
83597         }
83598         this.child('#refresh').enable();
83599     },
83600
83601     // private
83602     readPageFromInput : function(pageData){
83603         var v = this.child('#inputItem').getValue(),
83604             pageNum = parseInt(v, 10);
83605
83606         if (!v || isNaN(pageNum)) {
83607             this.child('#inputItem').setValue(pageData.currentPage);
83608             return false;
83609         }
83610         return pageNum;
83611     },
83612
83613     onPagingFocus : function(){
83614         this.child('#inputItem').select();
83615     },
83616
83617     //private
83618     onPagingBlur : function(e){
83619         var curPage = this.getPageData().currentPage;
83620         this.child('#inputItem').setValue(curPage);
83621     },
83622
83623     // private
83624     onPagingKeyDown : function(field, e){
83625         var me = this,
83626             k = e.getKey(),
83627             pageData = me.getPageData(),
83628             increment = e.shiftKey ? 10 : 1,
83629             pageNum;
83630
83631         if (k == e.RETURN) {
83632             e.stopEvent();
83633             pageNum = me.readPageFromInput(pageData);
83634             if (pageNum !== false) {
83635                 pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
83636                 if(me.fireEvent('beforechange', me, pageNum) !== false){
83637                     me.store.loadPage(pageNum);
83638                 }
83639             }
83640         } else if (k == e.HOME || k == e.END) {
83641             e.stopEvent();
83642             pageNum = k == e.HOME ? 1 : pageData.pageCount;
83643             field.setValue(pageNum);
83644         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
83645             e.stopEvent();
83646             pageNum = me.readPageFromInput(pageData);
83647             if (pageNum) {
83648                 if (k == e.DOWN || k == e.PAGEDOWN) {
83649                     increment *= -1;
83650                 }
83651                 pageNum += increment;
83652                 if (pageNum >= 1 && pageNum <= pageData.pages) {
83653                     field.setValue(pageNum);
83654                 }
83655             }
83656         }
83657     },
83658
83659     // private
83660     beforeLoad : function(){
83661         if(this.rendered && this.refresh){
83662             this.refresh.disable();
83663         }
83664     },
83665
83666     // private
83667     doLoad : function(start){
83668         if(this.fireEvent('beforechange', this, o) !== false){
83669             this.store.load();
83670         }
83671     },
83672
83673     /**
83674      * Move to the first page, has the same effect as clicking the 'first' button.
83675      */
83676     moveFirst : function(){
83677         if (this.fireEvent('beforechange', this, 1) !== false){
83678             this.store.loadPage(1);
83679         }
83680     },
83681
83682     /**
83683      * Move to the previous page, has the same effect as clicking the 'previous' button.
83684      */
83685     movePrevious : function(){
83686         var me = this,
83687             prev = me.store.currentPage - 1;
83688
83689         if (prev > 0) {
83690             if (me.fireEvent('beforechange', me, prev) !== false) {
83691                 me.store.previousPage();
83692             }
83693         }
83694     },
83695
83696     /**
83697      * Move to the next page, has the same effect as clicking the 'next' button.
83698      */
83699     moveNext : function(){
83700         var me = this,
83701             total = me.getPageData().pageCount,
83702             next = me.store.currentPage + 1;
83703
83704         if (next <= total) {
83705             if (me.fireEvent('beforechange', me, next) !== false) {
83706                 me.store.nextPage();
83707             }
83708         }
83709     },
83710
83711     /**
83712      * Move to the last page, has the same effect as clicking the 'last' button.
83713      */
83714     moveLast : function(){
83715         var me = this,
83716             last = me.getPageData().pageCount;
83717
83718         if (me.fireEvent('beforechange', me, last) !== false) {
83719             me.store.loadPage(last);
83720         }
83721     },
83722
83723     /**
83724      * Refresh the current page, has the same effect as clicking the 'refresh' button.
83725      */
83726     doRefresh : function(){
83727         var me = this,
83728             current = me.store.currentPage;
83729
83730         if (me.fireEvent('beforechange', me, current) !== false) {
83731             me.store.loadPage(current);
83732         }
83733     },
83734
83735     /**
83736      * Binds the paging toolbar to the specified {@link Ext.data.Store}
83737      * @param {Ext.data.Store} store The store to bind to this toolbar
83738      * @param {Boolean} initial (Optional) true to not remove listeners
83739      */
83740     bindStore : function(store, initial){
83741         var me = this;
83742
83743         if (!initial && me.store) {
83744             if(store !== me.store && me.store.autoDestroy){
83745                 me.store.destroyStore();
83746             }else{
83747                 me.store.un('beforeload', me.beforeLoad, me);
83748                 me.store.un('load', me.onLoad, me);
83749                 me.store.un('exception', me.onLoadError, me);
83750             }
83751             if(!store){
83752                 me.store = null;
83753             }
83754         }
83755         if (store) {
83756             store = Ext.data.StoreManager.lookup(store);
83757             store.on({
83758                 scope: me,
83759                 beforeload: me.beforeLoad,
83760                 load: me.onLoad,
83761                 exception: me.onLoadError
83762             });
83763         }
83764         me.store = store;
83765     },
83766
83767     /**
83768      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
83769      * @param {Ext.data.Store} store The data store to unbind
83770      */
83771     unbind : function(store){
83772         this.bindStore(null);
83773     },
83774
83775     /**
83776      * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
83777      * @param {Ext.data.Store} store The data store to bind
83778      */
83779     bind : function(store){
83780         this.bindStore(store);
83781     },
83782
83783     // private
83784     onDestroy : function(){
83785         this.bindStore(null);
83786         this.callParent();
83787     }
83788 });
83789
83790 /**
83791  * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
83792  */
83793 Ext.define('Ext.view.BoundList', {
83794     extend: 'Ext.view.View',
83795     alias: 'widget.boundlist',
83796     alternateClassName: 'Ext.BoundList',
83797     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
83798
83799     /**
83800      * @cfg {Number} pageSize
83801      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
83802      * queries will execute with page {@link Ext.data.Operation#start start} and
83803      * {@link Ext.data.Operation#limit limit} parameters. Defaults to `0`.
83804      */
83805     pageSize: 0,
83806
83807     /**
83808      * @property {Ext.toolbar.Paging} pagingToolbar
83809      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
83810      * than zero and the BoundList has been rendered.
83811      */
83812
83813     // private overrides
83814     autoScroll: true,
83815     baseCls: Ext.baseCSSPrefix + 'boundlist',
83816     itemCls: Ext.baseCSSPrefix + 'boundlist-item',
83817     listItemCls: '',
83818     shadow: false,
83819     trackOver: true,
83820     refreshed: 0,
83821
83822     ariaRole: 'listbox',
83823
83824     componentLayout: 'boundlist',
83825
83826     renderTpl: ['<div id="{id}-listEl" class="list-ct"></div>'],
83827
83828     initComponent: function() {
83829         var me = this,
83830             baseCls = me.baseCls,
83831             itemCls = me.itemCls;
83832             
83833         me.selectedItemCls = baseCls + '-selected';
83834         me.overItemCls = baseCls + '-item-over';
83835         me.itemSelector = "." + itemCls;
83836
83837         if (me.floating) {
83838             me.addCls(baseCls + '-floating');
83839         }
83840
83841         if (!me.tpl) {
83842             // should be setting aria-posinset based on entire set of data
83843             // not filtered set
83844             me.tpl = Ext.create('Ext.XTemplate',
83845                 '<ul><tpl for=".">',
83846                     '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
83847                 '</tpl></ul>'
83848             );
83849         } else if (Ext.isString(me.tpl)) {
83850             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
83851         }
83852
83853         if (me.pageSize) {
83854             me.pagingToolbar = me.createPagingToolbar();
83855         }
83856
83857         me.callParent();
83858
83859         me.addChildEls('listEl');
83860     },
83861
83862     createPagingToolbar: function() {
83863         return Ext.widget('pagingtoolbar', {
83864             pageSize: this.pageSize,
83865             store: this.store,
83866             border: false
83867         });
83868     },
83869
83870     onRender: function() {
83871         var me = this,
83872             toolbar = me.pagingToolbar;
83873         me.callParent(arguments);
83874         if (toolbar) {
83875             toolbar.render(me.el);
83876         }
83877     },
83878
83879     bindStore : function(store, initial) {
83880         var me = this,
83881             toolbar = me.pagingToolbar;
83882         me.callParent(arguments);
83883         if (toolbar) {
83884             toolbar.bindStore(store, initial);
83885         }
83886     },
83887
83888     getTargetEl: function() {
83889         return this.listEl || this.el;
83890     },
83891
83892     getInnerTpl: function(displayField) {
83893         return '{' + displayField + '}';
83894     },
83895
83896     refresh: function() {
83897         var me = this;
83898         me.callParent();
83899         if (me.isVisible()) {
83900             me.refreshed++;
83901             me.doComponentLayout();
83902             me.refreshed--;
83903         }
83904     },
83905
83906     initAria: function() {
83907         this.callParent();
83908
83909         var selModel = this.getSelectionModel(),
83910             mode     = selModel.getSelectionMode(),
83911             actionEl = this.getActionEl();
83912
83913         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
83914         if (mode !== 'SINGLE') {
83915             actionEl.dom.setAttribute('aria-multiselectable', true);
83916         }
83917     },
83918
83919     onDestroy: function() {
83920         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
83921         this.callParent();
83922     }
83923 });
83924
83925 /**
83926  * @class Ext.view.BoundListKeyNav
83927  * @extends Ext.util.KeyNav
83928  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
83929  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
83930  * through the list. The enter key invokes the selection model's select action using the highlighted item.
83931  */
83932 Ext.define('Ext.view.BoundListKeyNav', {
83933     extend: 'Ext.util.KeyNav',
83934     requires: 'Ext.view.BoundList',
83935
83936     /**
83937      * @cfg {Ext.view.BoundList} boundList (required)
83938      * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
83939      */
83940
83941     constructor: function(el, config) {
83942         var me = this;
83943         me.boundList = config.boundList;
83944         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
83945     },
83946
83947     defaultHandlers: {
83948         up: function() {
83949             var me = this,
83950                 boundList = me.boundList,
83951                 allItems = boundList.all,
83952                 oldItem = boundList.highlightedItem,
83953                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
83954                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
83955             me.highlightAt(newItemIdx);
83956         },
83957
83958         down: function() {
83959             var me = this,
83960                 boundList = me.boundList,
83961                 allItems = boundList.all,
83962                 oldItem = boundList.highlightedItem,
83963                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
83964                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
83965             me.highlightAt(newItemIdx);
83966         },
83967
83968         pageup: function() {
83969             //TODO
83970         },
83971
83972         pagedown: function() {
83973             //TODO
83974         },
83975
83976         home: function() {
83977             this.highlightAt(0);
83978         },
83979
83980         end: function() {
83981             var me = this;
83982             me.highlightAt(me.boundList.all.getCount() - 1);
83983         },
83984
83985         enter: function(e) {
83986             this.selectHighlighted(e);
83987         }
83988     },
83989
83990     /**
83991      * Highlights the item at the given index.
83992      * @param {Number} index
83993      */
83994     highlightAt: function(index) {
83995         var boundList = this.boundList,
83996             item = boundList.all.item(index);
83997         if (item) {
83998             item = item.dom;
83999             boundList.highlightItem(item);
84000             boundList.getTargetEl().scrollChildIntoView(item, false);
84001         }
84002     },
84003
84004     /**
84005      * Triggers selection of the currently highlighted item according to the behavior of
84006      * the configured SelectionModel.
84007      */
84008     selectHighlighted: function(e) {
84009         var me = this,
84010             boundList = me.boundList,
84011             highlighted = boundList.highlightedItem,
84012             selModel = boundList.getSelectionModel();
84013         if (highlighted) {
84014             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
84015         }
84016     }
84017
84018 });
84019 /**
84020  * @docauthor Jason Johnston <jason@sencha.com>
84021  *
84022  * A combobox control with support for autocomplete, remote loading, and many other features.
84023  *
84024  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
84025  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
84026  * list. The user can input any value by default, even if it does not appear in the selection list;
84027  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
84028  *
84029  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
84030  * stores. The data items in the store are mapped to each option's displayed text and backing value via
84031  * the {@link #valueField} and {@link #displayField} configurations, respectively.
84032  *
84033  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
84034  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
84035  *
84036  * # Example usage:
84037  *
84038  *     @example
84039  *     // The data store containing the list of states
84040  *     var states = Ext.create('Ext.data.Store', {
84041  *         fields: ['abbr', 'name'],
84042  *         data : [
84043  *             {"abbr":"AL", "name":"Alabama"},
84044  *             {"abbr":"AK", "name":"Alaska"},
84045  *             {"abbr":"AZ", "name":"Arizona"}
84046  *             //...
84047  *         ]
84048  *     });
84049  *
84050  *     // Create the combo box, attached to the states data store
84051  *     Ext.create('Ext.form.ComboBox', {
84052  *         fieldLabel: 'Choose State',
84053  *         store: states,
84054  *         queryMode: 'local',
84055  *         displayField: 'name',
84056  *         valueField: 'abbr',
84057  *         renderTo: Ext.getBody()
84058  *     });
84059  *
84060  * # Events
84061  *
84062  * To do something when something in ComboBox is selected, configure the select event:
84063  *
84064  *     var cb = Ext.create('Ext.form.ComboBox', {
84065  *         // all of your config options
84066  *         listeners:{
84067  *              scope: yourScope,
84068  *              'select': yourFunction
84069  *         }
84070  *     });
84071  *
84072  *     // Alternatively, you can assign events after the object is created:
84073  *     var cb = new Ext.form.field.ComboBox(yourOptions);
84074  *     cb.on('select', yourFunction, yourScope);
84075  *
84076  * # Multiple Selection
84077  *
84078  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
84079  * {@link #multiSelect} config to `true`.
84080  */
84081 Ext.define('Ext.form.field.ComboBox', {
84082     extend:'Ext.form.field.Picker',
84083     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
84084     alternateClassName: 'Ext.form.ComboBox',
84085     alias: ['widget.combobox', 'widget.combo'],
84086
84087     /**
84088      * @cfg {String} [triggerCls='x-form-arrow-trigger']
84089      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
84090      * by default and `triggerCls` will be **appended** if specified.
84091      */
84092     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
84093
84094     /**
84095      * @private
84096      * @cfg {String}
84097      * CSS class used to find the {@link #hiddenDataEl}
84098      */
84099     hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
84100
84101     /**
84102      * @override
84103      */
84104     fieldSubTpl: [
84105         '<div class="{hiddenDataCls}" role="presentation"></div>',
84106         '<input id="{id}" type="{type}" ',
84107             '<tpl if="size">size="{size}" </tpl>',
84108             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
84109             'class="{fieldCls} {typeCls}" autocomplete="off" />',
84110         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
84111             '{triggerEl}',
84112             '<div class="{clearCls}" role="presentation"></div>',
84113         '</div>',
84114         {
84115             compiled: true,
84116             disableFormats: true
84117         }
84118     ],
84119
84120     getSubTplData: function(){
84121         var me = this;
84122         Ext.applyIf(me.subTplData, {
84123             hiddenDataCls: me.hiddenDataCls
84124         });
84125         return me.callParent(arguments);
84126     },
84127
84128     afterRender: function(){
84129         var me = this;
84130         me.callParent(arguments);
84131         me.setHiddenValue(me.value);
84132     },
84133
84134     /**
84135      * @cfg {Ext.data.Store/Array} store
84136      * The data source to which this combo is bound. Acceptable values for this property are:
84137      *
84138      *   - **any {@link Ext.data.Store Store} subclass**
84139      *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
84140      *     {@link Ext.data.Field#name field names} to work with all data components.
84141      *
84142      *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)
84143      *
84144      *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
84145      *       {@link #valueField} and {@link #displayField})
84146      *
84147      *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
84148      *
84149      *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
84150      *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
84151      *
84152      * See also {@link #queryMode}.
84153      */
84154
84155     /**
84156      * @cfg {Boolean} multiSelect
84157      * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
84158      * items from the dropdown list. The combo's text field will show all selected values separated by the
84159      * {@link #delimiter}.
84160      */
84161     multiSelect: false,
84162
84163     /**
84164      * @cfg {String} delimiter
84165      * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
84166      * `{@link #multiSelect} = true`.
84167      */
84168     delimiter: ', ',
84169
84170     /**
84171      * @cfg {String} displayField
84172      * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
84173      *
84174      * See also `{@link #valueField}`.
84175      */
84176     displayField: 'text',
84177
84178     /**
84179      * @cfg {String} valueField (required)
84180      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
84181      * the value of the {@link #displayField} config).
84182      *
84183      * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
84184      * `{@link #displayField}`.
84185      */
84186
84187     /**
84188      * @cfg {String} triggerAction
84189      * The action to execute when the trigger is clicked.
84190      *
84191      *   - **`'all'`** :
84192      *
84193      *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
84194      *
84195      *   - **`'query'`** :
84196      *
84197      *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
84198      *
84199      * See also `{@link #queryParam}`.
84200      */
84201     triggerAction: 'all',
84202
84203     /**
84204      * @cfg {String} allQuery
84205      * The text query to send to the server to return all records for the list with no filtering
84206      */
84207     allQuery: '',
84208
84209     /**
84210      * @cfg {String} queryParam
84211      * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
84212      * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
84213      */
84214     queryParam: 'query',
84215
84216     /**
84217      * @cfg {String} queryMode
84218      * The mode in which the ComboBox uses the configured Store. Acceptable values are:
84219      *
84220      *   - **`'remote'`** :
84221      *
84222      *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
84223      *
84224      *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
84225      *     Ext.data.Store#load load}ed.
84226      *
84227      *     A parameter containing the typed string is sent in the load request. The default parameter name for the input
84228      *     string is `query`, but this can be configured using the {@link #queryParam} config.
84229      *
84230      *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
84231      *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load
84232      *     request which allows the server to further refine the returned dataset.
84233      *
84234      *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
84235      *     autocomplete.
84236      *
84237      *   - **`'local'`** :
84238      *
84239      *     ComboBox loads local data
84240      *
84241      *         var combo = new Ext.form.field.ComboBox({
84242      *             renderTo: document.body,
84243      *             queryMode: 'local',
84244      *             store: new Ext.data.ArrayStore({
84245      *                 id: 0,
84246      *                 fields: [
84247      *                     'myId',  // numeric value is the key
84248      *                     'displayText'
84249      *                 ],
84250      *                 data: [[1, 'item1'], [2, 'item2']]  // data is local
84251      *             }),
84252      *             valueField: 'myId',
84253      *             displayField: 'displayText',
84254      *             triggerAction: 'all'
84255      *         });
84256      */
84257     queryMode: 'remote',
84258
84259     queryCaching: true,
84260
84261     /**
84262      * @cfg {Number} pageSize
84263      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
84264      * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
84265      * parameters. Only applies when `{@link #queryMode} = 'remote'`.
84266      */
84267     pageSize: 0,
84268
84269     /**
84270      * @cfg {Number} queryDelay
84271      * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
84272      * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
84273      */
84274
84275     /**
84276      * @cfg {Number} minChars
84277      * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
84278      * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
84279      * `{@link Ext.form.field.Trigger#editable editable} = false`).
84280      */
84281
84282     /**
84283      * @cfg {Boolean} autoSelect
84284      * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
84285      * opened. A false value would cause nothing in the list to be highlighted automatically, so
84286      * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
84287      * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
84288      */
84289     autoSelect: true,
84290
84291     /**
84292      * @cfg {Boolean} typeAhead
84293      * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
84294      * ({@link #typeAheadDelay}) if it matches a known value.
84295      */
84296     typeAhead: false,
84297
84298     /**
84299      * @cfg {Number} typeAheadDelay
84300      * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
84301      */
84302     typeAheadDelay: 250,
84303
84304     /**
84305      * @cfg {Boolean} selectOnTab
84306      * Whether the Tab key should select the currently highlighted item.
84307      */
84308     selectOnTab: true,
84309
84310     /**
84311      * @cfg {Boolean} forceSelection
84312      * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
84313      * arbitrary text into the field.
84314      */
84315     forceSelection: false,
84316
84317     /**
84318      * @cfg {String} valueNotFoundText
84319      * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
84320      * be displayed as the field text if defined. If this default text is used, it means there
84321      * is no value set and no validation will occur on this field.
84322      */
84323
84324     /**
84325      * @property {String} lastQuery
84326      * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
84327      *
84328      *     var combo = new Ext.form.field.ComboBox({
84329      *         ...
84330      *         queryMode: 'remote',
84331      *         listeners: {
84332      *             // delete the previous query in the beforequery event or set
84333      *             // combo.lastQuery = null (this will reload the store the next time it expands)
84334      *             beforequery: function(qe){
84335      *                 delete qe.combo.lastQuery;
84336      *             }
84337      *         }
84338      *     });
84339      *
84340      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
84341      * combo with `lastQuery=''`. Example use:
84342      *
84343      *     var combo = new Ext.form.field.ComboBox({
84344      *         ...
84345      *         queryMode: 'local',
84346      *         triggerAction: 'all',
84347      *         lastQuery: ''
84348      *     });
84349      */
84350
84351     /**
84352      * @cfg {Object} defaultListConfig
84353      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
84354      */
84355     defaultListConfig: {
84356         emptyText: '',
84357         loadingText: 'Loading...',
84358         loadingHeight: 70,
84359         minWidth: 70,
84360         maxHeight: 300,
84361         shadow: 'sides'
84362     },
84363
84364     /**
84365      * @cfg {String/HTMLElement/Ext.Element} transform
84366      * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
84367      * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
84368      * will take precedence over this.
84369      */
84370
84371     /**
84372      * @cfg {Object} listConfig
84373      * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
84374      * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
84375      *
84376      *   - {@link Ext.view.BoundList#cls} - defaults to empty
84377      *   - {@link Ext.view.BoundList#emptyText} - defaults to empty string
84378      *   - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
84379      *   - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
84380      *   - {@link Ext.view.BoundList#minWidth} - defaults to `70`
84381      *   - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
84382      *   - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
84383      *   - {@link Ext.view.BoundList#resizable} - defaults to `false`
84384      *   - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
84385      *   - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
84386      *     field if {@link #matchFieldWidth} is true)
84387      */
84388
84389     //private
84390     ignoreSelection: 0,
84391
84392     initComponent: function() {
84393         var me = this,
84394             isDefined = Ext.isDefined,
84395             store = me.store,
84396             transform = me.transform,
84397             transformSelect, isLocalMode;
84398
84399         Ext.applyIf(me.renderSelectors, {
84400             hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
84401         });
84402         
84403         //<debug>
84404         if (me.typeAhead && me.multiSelect) {
84405             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
84406         }
84407         if (me.typeAhead && !me.editable) {
84408             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
84409         }
84410         if (me.selectOnFocus && !me.editable) {
84411             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
84412         }
84413         //</debug>
84414
84415         this.addEvents(
84416             /**
84417              * @event beforequery
84418              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
84419              * property to true.
84420              *
84421              * @param {Object} queryEvent An object that has these properties:
84422              *
84423              *   - `combo` : Ext.form.field.ComboBox
84424              *
84425              *     This combo box
84426              *
84427              *   - `query` : String
84428              *
84429              *     The query string
84430              *
84431              *   - `forceAll` : Boolean
84432              *
84433              *     True to force "all" query
84434              *
84435              *   - `cancel` : Boolean
84436              *
84437              *     Set to true to cancel the query
84438              */
84439             'beforequery',
84440
84441             /**
84442              * @event select
84443              * Fires when at least one list item is selected.
84444              * @param {Ext.form.field.ComboBox} combo This combo box
84445              * @param {Array} records The selected records
84446              */
84447             'select',
84448
84449             /**
84450              * @event beforeselect
84451              * Fires before the selected item is added to the collection
84452              * @param {Ext.form.field.ComboBox} combo This combo box
84453              * @param {Ext.data.Record} record The selected record
84454              * @param {Number} index The index of the selected record
84455              */
84456             'beforeselect',
84457
84458             /**
84459              * @event beforedeselect
84460              * Fires before the deselected item is removed from the collection
84461              * @param {Ext.form.field.ComboBox} combo This combo box
84462              * @param {Ext.data.Record} record The deselected record
84463              * @param {Number} index The index of the deselected record
84464              */
84465             'beforedeselect'
84466         );
84467
84468         // Build store from 'transform' HTML select element's options
84469         if (transform) {
84470             transformSelect = Ext.getDom(transform);
84471             if (transformSelect) {
84472                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
84473                     return [option.value, option.text];
84474                 });
84475                 if (!me.name) {
84476                     me.name = transformSelect.name;
84477                 }
84478                 if (!('value' in me)) {
84479                     me.value = transformSelect.value;
84480                 }
84481             }
84482         }
84483
84484         me.bindStore(store || 'ext-empty-store', true);
84485         store = me.store;
84486         if (store.autoCreated) {
84487             me.queryMode = 'local';
84488             me.valueField = me.displayField = 'field1';
84489             if (!store.expanded) {
84490                 me.displayField = 'field2';
84491             }
84492         }
84493
84494
84495         if (!isDefined(me.valueField)) {
84496             me.valueField = me.displayField;
84497         }
84498
84499         isLocalMode = me.queryMode === 'local';
84500         if (!isDefined(me.queryDelay)) {
84501             me.queryDelay = isLocalMode ? 10 : 500;
84502         }
84503         if (!isDefined(me.minChars)) {
84504             me.minChars = isLocalMode ? 0 : 4;
84505         }
84506
84507         if (!me.displayTpl) {
84508             me.displayTpl = Ext.create('Ext.XTemplate',
84509                 '<tpl for=".">' +
84510                     '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
84511                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
84512                 '</tpl>'
84513             );
84514         } else if (Ext.isString(me.displayTpl)) {
84515             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
84516         }
84517
84518         me.callParent();
84519
84520         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
84521
84522         // store has already been loaded, setValue
84523         if (me.store.getCount() > 0) {
84524             me.setValue(me.value);
84525         }
84526
84527         // render in place of 'transform' select
84528         if (transformSelect) {
84529             me.render(transformSelect.parentNode, transformSelect);
84530             Ext.removeNode(transformSelect);
84531             delete me.renderTo;
84532         }
84533     },
84534
84535     /**
84536      * Returns the store associated with this ComboBox.
84537      * @return {Ext.data.Store} The store
84538      */
84539     getStore : function(){
84540         return this.store;
84541     },
84542
84543     beforeBlur: function() {
84544         this.doQueryTask.cancel();
84545         this.assertValue();
84546     },
84547
84548     // private
84549     assertValue: function() {
84550         var me = this,
84551             value = me.getRawValue(),
84552             rec;
84553
84554         if (me.forceSelection) {
84555             if (me.multiSelect) {
84556                 // For multiselect, check that the current displayed value matches the current
84557                 // selection, if it does not then revert to the most recent selection.
84558                 if (value !== me.getDisplayValue()) {
84559                     me.setValue(me.lastSelection);
84560                 }
84561             } else {
84562                 // For single-select, match the displayed value to a record and select it,
84563                 // if it does not match a record then revert to the most recent selection.
84564                 rec = me.findRecordByDisplay(value);
84565                 if (rec) {
84566                     me.select(rec);
84567                 } else {
84568                     me.setValue(me.lastSelection);
84569                 }
84570             }
84571         }
84572         me.collapse();
84573     },
84574
84575     onTypeAhead: function() {
84576         var me = this,
84577             displayField = me.displayField,
84578             record = me.store.findRecord(displayField, me.getRawValue()),
84579             boundList = me.getPicker(),
84580             newValue, len, selStart;
84581
84582         if (record) {
84583             newValue = record.get(displayField);
84584             len = newValue.length;
84585             selStart = me.getRawValue().length;
84586
84587             boundList.highlightItem(boundList.getNode(record));
84588
84589             if (selStart !== 0 && selStart !== len) {
84590                 me.setRawValue(newValue);
84591                 me.selectText(selStart, newValue.length);
84592             }
84593         }
84594     },
84595
84596     // invoked when a different store is bound to this combo
84597     // than the original
84598     resetToDefault: function() {
84599
84600     },
84601
84602     bindStore: function(store, initial) {
84603         var me = this,
84604             oldStore = me.store;
84605
84606         // this code directly accesses this.picker, bc invoking getPicker
84607         // would create it when we may be preping to destroy it
84608         if (oldStore && !initial) {
84609             if (oldStore !== store && oldStore.autoDestroy) {
84610                 oldStore.destroyStore();
84611             } else {
84612                 oldStore.un({
84613                     scope: me,
84614                     load: me.onLoad,
84615                     exception: me.collapse
84616                 });
84617             }
84618             if (!store) {
84619                 me.store = null;
84620                 if (me.picker) {
84621                     me.picker.bindStore(null);
84622                 }
84623             }
84624         }
84625         if (store) {
84626             if (!initial) {
84627                 me.resetToDefault();
84628             }
84629
84630             me.store = Ext.data.StoreManager.lookup(store);
84631             me.store.on({
84632                 scope: me,
84633                 load: me.onLoad,
84634                 exception: me.collapse
84635             });
84636
84637             if (me.picker) {
84638                 me.picker.bindStore(store);
84639             }
84640         }
84641     },
84642
84643     onLoad: function() {
84644         var me = this,
84645             value = me.value;
84646
84647         // If performing a remote query upon the raw value...
84648         if (me.rawQuery) {
84649             me.rawQuery = false;
84650             me.syncSelection();
84651             if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
84652                 me.doAutoSelect();
84653             }
84654         }
84655         // If store initial load or triggerAction: 'all' trigger click.
84656         else {
84657             // Set the value on load
84658             if (me.value) {
84659                 me.setValue(me.value);
84660             } else {
84661                 // There's no value.
84662                 // Highlight the first item in the list if autoSelect: true
84663                 if (me.store.getCount()) {
84664                     me.doAutoSelect();
84665                 } else {
84666                     me.setValue('');
84667                 }
84668             }
84669         }
84670     },
84671
84672     /**
84673      * @private
84674      * Execute the query with the raw contents within the textfield.
84675      */
84676     doRawQuery: function() {
84677         this.doQuery(this.getRawValue(), false, true);
84678     },
84679
84680     /**
84681      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
84682      * allowing the query action to be canceled if needed.
84683      *
84684      * @param {String} queryString The SQL query to execute
84685      * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
84686      * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
84687      * previously saved in the current store.
84688      * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
84689      * resulting store load to leave the raw value undisturbed.
84690      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
84691      * handler.
84692      */
84693     doQuery: function(queryString, forceAll, rawQuery) {
84694         queryString = queryString || '';
84695
84696         // store in object and pass by reference in 'beforequery'
84697         // so that client code can modify values.
84698         var me = this,
84699             qe = {
84700                 query: queryString,
84701                 forceAll: forceAll,
84702                 combo: me,
84703                 cancel: false
84704             },
84705             store = me.store,
84706             isLocalMode = me.queryMode === 'local';
84707
84708         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
84709             return false;
84710         }
84711
84712         // get back out possibly modified values
84713         queryString = qe.query;
84714         forceAll = qe.forceAll;
84715
84716         // query permitted to run
84717         if (forceAll || (queryString.length >= me.minChars)) {
84718             // expand before starting query so LoadMask can position itself correctly
84719             me.expand();
84720
84721             // make sure they aren't querying the same thing
84722             if (!me.queryCaching || me.lastQuery !== queryString) {
84723                 me.lastQuery = queryString;
84724
84725                 if (isLocalMode) {
84726                     // forceAll means no filtering - show whole dataset.
84727                     if (forceAll) {
84728                         store.clearFilter();
84729                     } else {
84730                         // Clear filter, but supress event so that the BoundList is not immediately updated.
84731                         store.clearFilter(true);
84732                         store.filter(me.displayField, queryString);
84733                     }
84734                 } else {
84735                     // Set flag for onLoad handling to know how the Store was loaded
84736                     me.rawQuery = rawQuery;
84737
84738                     // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
84739                     // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
84740                     if (me.pageSize) {
84741                         // if we're paging, we've changed the query so start at page 1.
84742                         me.loadPage(1);
84743                     } else {
84744                         store.load({
84745                             params: me.getParams(queryString)
84746                         });
84747                     }
84748                 }
84749             }
84750
84751             // Clear current selection if it does not match the current value in the field
84752             if (me.getRawValue() !== me.getDisplayValue()) {
84753                 me.ignoreSelection++;
84754                 me.picker.getSelectionModel().deselectAll();
84755                 me.ignoreSelection--;
84756             }
84757
84758             if (isLocalMode) {
84759                 me.doAutoSelect();
84760             }
84761             if (me.typeAhead) {
84762                 me.doTypeAhead();
84763             }
84764         }
84765         return true;
84766     },
84767
84768     loadPage: function(pageNum){
84769         this.store.loadPage(pageNum, {
84770             params: this.getParams(this.lastQuery)
84771         });
84772     },
84773
84774     onPageChange: function(toolbar, newPage){
84775         /*
84776          * Return false here so we can call load ourselves and inject the query param.
84777          * We don't want to do this for every store load since the developer may load
84778          * the store through some other means so we won't add the query param.
84779          */
84780         this.loadPage(newPage);
84781         return false;
84782     },
84783
84784     // private
84785     getParams: function(queryString) {
84786         var params = {},
84787             param = this.queryParam;
84788
84789         if (param) {
84790             params[param] = queryString;
84791         }
84792         return params;
84793     },
84794
84795     /**
84796      * @private
84797      * If the autoSelect config is true, and the picker is open, highlights the first item.
84798      */
84799     doAutoSelect: function() {
84800         var me = this,
84801             picker = me.picker,
84802             lastSelected, itemNode;
84803         if (picker && me.autoSelect && me.store.getCount() > 0) {
84804             // Highlight the last selected item and scroll it into view
84805             lastSelected = picker.getSelectionModel().lastSelected;
84806             itemNode = picker.getNode(lastSelected || 0);
84807             if (itemNode) {
84808                 picker.highlightItem(itemNode);
84809                 picker.listEl.scrollChildIntoView(itemNode, false);
84810             }
84811         }
84812     },
84813
84814     doTypeAhead: function() {
84815         if (!this.typeAheadTask) {
84816             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
84817         }
84818         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
84819             this.typeAheadTask.delay(this.typeAheadDelay);
84820         }
84821     },
84822
84823     onTriggerClick: function() {
84824         var me = this;
84825         if (!me.readOnly && !me.disabled) {
84826             if (me.isExpanded) {
84827                 me.collapse();
84828             } else {
84829                 me.onFocus({});
84830                 if (me.triggerAction === 'all') {
84831                     me.doQuery(me.allQuery, true);
84832                 } else {
84833                     me.doQuery(me.getRawValue(), false, true);
84834                 }
84835             }
84836             me.inputEl.focus();
84837         }
84838     },
84839
84840
84841     // store the last key and doQuery if relevant
84842     onKeyUp: function(e, t) {
84843         var me = this,
84844             key = e.getKey();
84845
84846         if (!me.readOnly && !me.disabled && me.editable) {
84847             me.lastKey = key;
84848             // we put this in a task so that we can cancel it if a user is
84849             // in and out before the queryDelay elapses
84850
84851             // perform query w/ any normal key or backspace or delete
84852             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
84853                 me.doQueryTask.delay(me.queryDelay);
84854             }
84855         }
84856
84857         if (me.enableKeyEvents) {
84858             me.callParent(arguments);
84859         }
84860     },
84861
84862     initEvents: function() {
84863         var me = this;
84864         me.callParent();
84865
84866         /*
84867          * Setup keyboard handling. If enableKeyEvents is true, we already have
84868          * a listener on the inputEl for keyup, so don't create a second.
84869          */
84870         if (!me.enableKeyEvents) {
84871             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
84872         }
84873     },
84874     
84875     onDestroy: function(){
84876         this.bindStore(null);
84877         this.callParent();    
84878     },
84879
84880     createPicker: function() {
84881         var me = this,
84882             picker,
84883             menuCls = Ext.baseCSSPrefix + 'menu',
84884             opts = Ext.apply({
84885                 pickerField: me,
84886                 selModel: {
84887                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
84888                 },
84889                 floating: true,
84890                 hidden: true,
84891                 ownerCt: me.ownerCt,
84892                 cls: me.el.up('.' + menuCls) ? menuCls : '',
84893                 store: me.store,
84894                 displayField: me.displayField,
84895                 focusOnToFront: false,
84896                 pageSize: me.pageSize,
84897                 tpl: me.tpl
84898             }, me.listConfig, me.defaultListConfig);
84899
84900         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
84901         if (me.pageSize) {
84902             picker.pagingToolbar.on('beforechange', me.onPageChange, me);
84903         }
84904
84905         me.mon(picker, {
84906             itemclick: me.onItemClick,
84907             refresh: me.onListRefresh,
84908             scope: me
84909         });
84910
84911         me.mon(picker.getSelectionModel(), {
84912             'beforeselect': me.onBeforeSelect,
84913             'beforedeselect': me.onBeforeDeselect,
84914             'selectionchange': me.onListSelectionChange,
84915             scope: me
84916         });
84917
84918         return picker;
84919     },
84920
84921     alignPicker: function(){
84922         var me = this,
84923             picker = me.picker,
84924             heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
84925             heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
84926             space = Math.max(heightAbove, heightBelow);
84927
84928         me.callParent();
84929         if (picker.getHeight() > space) {
84930             picker.setHeight(space - 5); // have some leeway so we aren't flush against
84931             me.doAlign();
84932         }
84933     },
84934
84935     onListRefresh: function() {
84936         this.alignPicker();
84937         this.syncSelection();
84938     },
84939
84940     onItemClick: function(picker, record){
84941         /*
84942          * If we're doing single selection, the selection change events won't fire when
84943          * clicking on the selected element. Detect it here.
84944          */
84945         var me = this,
84946             lastSelection = me.lastSelection,
84947             valueField = me.valueField,
84948             selected;
84949
84950         if (!me.multiSelect && lastSelection) {
84951             selected = lastSelection[0];
84952             if (selected && (record.get(valueField) === selected.get(valueField))) {
84953                 // Make sure we also update the display value if it's only partial
84954                 me.displayTplData = [record.data];
84955                 me.setRawValue(me.getDisplayValue());
84956                 me.collapse();
84957             }
84958         }
84959     },
84960
84961     onBeforeSelect: function(list, record) {
84962         return this.fireEvent('beforeselect', this, record, record.index);
84963     },
84964
84965     onBeforeDeselect: function(list, record) {
84966         return this.fireEvent('beforedeselect', this, record, record.index);
84967     },
84968
84969     onListSelectionChange: function(list, selectedRecords) {
84970         var me = this,
84971             isMulti = me.multiSelect,
84972             hasRecords = selectedRecords.length > 0;
84973         // Only react to selection if it is not called from setValue, and if our list is
84974         // expanded (ignores changes to the selection model triggered elsewhere)
84975         if (!me.ignoreSelection && me.isExpanded) {
84976             if (!isMulti) {
84977                 Ext.defer(me.collapse, 1, me);
84978             }
84979             /*
84980              * Only set the value here if we're in multi selection mode or we have
84981              * a selection. Otherwise setValue will be called with an empty value
84982              * which will cause the change event to fire twice.
84983              */
84984             if (isMulti || hasRecords) {
84985                 me.setValue(selectedRecords, false);
84986             }
84987             if (hasRecords) {
84988                 me.fireEvent('select', me, selectedRecords);
84989             }
84990             me.inputEl.focus();
84991         }
84992     },
84993
84994     /**
84995      * @private
84996      * Enables the key nav for the BoundList when it is expanded.
84997      */
84998     onExpand: function() {
84999         var me = this,
85000             keyNav = me.listKeyNav,
85001             selectOnTab = me.selectOnTab,
85002             picker = me.getPicker();
85003
85004         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
85005         if (keyNav) {
85006             keyNav.enable();
85007         } else {
85008             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
85009                 boundList: picker,
85010                 forceKeyDown: true,
85011                 tab: function(e) {
85012                     if (selectOnTab) {
85013                         this.selectHighlighted(e);
85014                         me.triggerBlur();
85015                     }
85016                     // Tab key event is allowed to propagate to field
85017                     return true;
85018                 }
85019             });
85020         }
85021
85022         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
85023         if (selectOnTab) {
85024             me.ignoreMonitorTab = true;
85025         }
85026
85027         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
85028         me.inputEl.focus();
85029     },
85030
85031     /**
85032      * @private
85033      * Disables the key nav for the BoundList when it is collapsed.
85034      */
85035     onCollapse: function() {
85036         var me = this,
85037             keyNav = me.listKeyNav;
85038         if (keyNav) {
85039             keyNav.disable();
85040             me.ignoreMonitorTab = false;
85041         }
85042     },
85043
85044     /**
85045      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
85046      * @param {Object} r
85047      */
85048     select: function(r) {
85049         this.setValue(r, true);
85050     },
85051
85052     /**
85053      * Finds the record by searching for a specific field/value combination.
85054      * @param {String} field The name of the field to test.
85055      * @param {Object} value The value to match the field against.
85056      * @return {Ext.data.Model} The matched record or false.
85057      */
85058     findRecord: function(field, value) {
85059         var ds = this.store,
85060             idx = ds.findExact(field, value);
85061         return idx !== -1 ? ds.getAt(idx) : false;
85062     },
85063
85064     /**
85065      * Finds the record by searching values in the {@link #valueField}.
85066      * @param {Object} value The value to match the field against.
85067      * @return {Ext.data.Model} The matched record or false.
85068      */
85069     findRecordByValue: function(value) {
85070         return this.findRecord(this.valueField, value);
85071     },
85072
85073     /**
85074      * Finds the record by searching values in the {@link #displayField}.
85075      * @param {Object} value The value to match the field against.
85076      * @return {Ext.data.Model} The matched record or false.
85077      */
85078     findRecordByDisplay: function(value) {
85079         return this.findRecord(this.displayField, value);
85080     },
85081
85082     /**
85083      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
85084      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
85085      * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
85086      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
85087      * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
85088      * or an Array of Strings or Models.
85089      * @return {Ext.form.field.Field} this
85090      */
85091     setValue: function(value, doSelect) {
85092         var me = this,
85093             valueNotFoundText = me.valueNotFoundText,
85094             inputEl = me.inputEl,
85095             i, len, record,
85096             models = [],
85097             displayTplData = [],
85098             processedValue = [];
85099
85100         if (me.store.loading) {
85101             // Called while the Store is loading. Ensure it is processed by the onLoad method.
85102             me.value = value;
85103             me.setHiddenValue(me.value);
85104             return me;
85105         }
85106
85107         // This method processes multi-values, so ensure value is an array.
85108         value = Ext.Array.from(value);
85109
85110         // Loop through values
85111         for (i = 0, len = value.length; i < len; i++) {
85112             record = value[i];
85113             if (!record || !record.isModel) {
85114                 record = me.findRecordByValue(record);
85115             }
85116             // record found, select it.
85117             if (record) {
85118                 models.push(record);
85119                 displayTplData.push(record.data);
85120                 processedValue.push(record.get(me.valueField));
85121             }
85122             // record was not found, this could happen because
85123             // store is not loaded or they set a value not in the store
85124             else {
85125                 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
85126                 if (!me.forceSelection) {
85127                     displayTplData.push(value[i]);
85128                     processedValue.push(value[i]);
85129                 }
85130                 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
85131                 else if (Ext.isDefined(valueNotFoundText)) {
85132                     displayTplData.push(valueNotFoundText);
85133                 }
85134             }
85135         }
85136
85137         // Set the value of this field. If we are multiselecting, then that is an array.
85138         me.setHiddenValue(processedValue);
85139         me.value = me.multiSelect ? processedValue : processedValue[0];
85140         if (!Ext.isDefined(me.value)) {
85141             me.value = null;
85142         }
85143         me.displayTplData = displayTplData; //store for getDisplayValue method
85144         me.lastSelection = me.valueModels = models;
85145
85146         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
85147             inputEl.removeCls(me.emptyCls);
85148         }
85149
85150         // Calculate raw value from the collection of Model data
85151         me.setRawValue(me.getDisplayValue());
85152         me.checkChange();
85153
85154         if (doSelect !== false) {
85155             me.syncSelection();
85156         }
85157         me.applyEmptyText();
85158
85159         return me;
85160     },
85161
85162     /**
85163      * @private
85164      * Set the value of {@link #hiddenDataEl}
85165      * Dynamically adds and removes input[type=hidden] elements
85166      */
85167     setHiddenValue: function(values){
85168         var me = this, i;
85169         if (!me.hiddenDataEl) {
85170             return;
85171         }
85172         values = Ext.Array.from(values);
85173         var dom = me.hiddenDataEl.dom,
85174             childNodes = dom.childNodes,
85175             input = childNodes[0],
85176             valueCount = values.length,
85177             childrenCount = childNodes.length;
85178         
85179         if (!input && valueCount > 0) {
85180             me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
85181             childrenCount = 1;
85182             input = dom.firstChild;
85183         }
85184         while (childrenCount > valueCount) {
85185             dom.removeChild(childNodes[0]);
85186             -- childrenCount;
85187         }
85188         while (childrenCount < valueCount) {
85189             dom.appendChild(input.cloneNode(true));
85190             ++ childrenCount;
85191         }
85192         for (i = 0; i < valueCount; i++) {
85193             childNodes[i].value = values[i];
85194         }
85195     },
85196
85197     /**
85198      * @private Generates the string value to be displayed in the text field for the currently stored value
85199      */
85200     getDisplayValue: function() {
85201         return this.displayTpl.apply(this.displayTplData);
85202     },
85203
85204     getValue: function() {
85205         // If the user has not changed the raw field value since a value was selected from the list,
85206         // then return the structured value from the selection. If the raw field value is different
85207         // than what would be displayed due to selection, return that raw value.
85208         var me = this,
85209             picker = me.picker,
85210             rawValue = me.getRawValue(), //current value of text field
85211             value = me.value; //stored value from last selection or setValue() call
85212
85213         if (me.getDisplayValue() !== rawValue) {
85214             value = rawValue;
85215             me.value = me.displayTplData = me.valueModels = null;
85216             if (picker) {
85217                 me.ignoreSelection++;
85218                 picker.getSelectionModel().deselectAll();
85219                 me.ignoreSelection--;
85220             }
85221         }
85222
85223         return value;
85224     },
85225
85226     getSubmitValue: function() {
85227         return this.getValue();
85228     },
85229
85230     isEqual: function(v1, v2) {
85231         var fromArray = Ext.Array.from,
85232             i, len;
85233
85234         v1 = fromArray(v1);
85235         v2 = fromArray(v2);
85236         len = v1.length;
85237
85238         if (len !== v2.length) {
85239             return false;
85240         }
85241
85242         for(i = 0; i < len; i++) {
85243             if (v2[i] !== v1[i]) {
85244                 return false;
85245             }
85246         }
85247
85248         return true;
85249     },
85250
85251     /**
85252      * Clears any value currently set in the ComboBox.
85253      */
85254     clearValue: function() {
85255         this.setValue([]);
85256     },
85257
85258     /**
85259      * @private Synchronizes the selection in the picker to match the current value of the combobox.
85260      */
85261     syncSelection: function() {
85262         var me = this,
85263             ExtArray = Ext.Array,
85264             picker = me.picker,
85265             selection, selModel;
85266         if (picker) {
85267             // From the value, find the Models that are in the store's current data
85268             selection = [];
85269             ExtArray.forEach(me.valueModels || [], function(value) {
85270                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
85271                     selection.push(value);
85272                 }
85273             });
85274
85275             // Update the selection to match
85276             me.ignoreSelection++;
85277             selModel = picker.getSelectionModel();
85278             selModel.deselectAll();
85279             if (selection.length) {
85280                 selModel.select(selection);
85281             }
85282             me.ignoreSelection--;
85283         }
85284     }
85285 });
85286
85287 /**
85288  * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
85289  * to allow browsing and selection of year/months combinations.
85290  */
85291 Ext.define('Ext.picker.Month', {
85292     extend: 'Ext.Component',
85293     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
85294     alias: 'widget.monthpicker',
85295     alternateClassName: 'Ext.MonthPicker',
85296
85297     renderTpl: [
85298         '<div id="{id}-bodyEl" class="{baseCls}-body">',
85299           '<div class="{baseCls}-months">',
85300               '<tpl for="months">',
85301                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
85302               '</tpl>',
85303           '</div>',
85304           '<div class="{baseCls}-years">',
85305               '<div class="{baseCls}-yearnav">',
85306                   '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
85307                   '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
85308               '</div>',
85309               '<tpl for="years">',
85310                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
85311               '</tpl>',
85312           '</div>',
85313           '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
85314         '</div>',
85315         '<tpl if="showButtons">',
85316           '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>',
85317         '</tpl>'
85318     ],
85319
85320     /**
85321      * @cfg {String} okText The text to display on the ok button.
85322      */
85323     okText: 'OK',
85324
85325     /**
85326      * @cfg {String} cancelText The text to display on the cancel button.
85327      */
85328     cancelText: 'Cancel',
85329
85330     /**
85331      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
85332      */
85333     baseCls: Ext.baseCSSPrefix + 'monthpicker',
85334
85335     /**
85336      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
85337      */
85338     showButtons: true,
85339
85340     /**
85341      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
85342      * <tt>'x-monthpicker-selected'</tt>
85343      */
85344
85345     /**
85346      * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
85347      */
85348     width: 178,
85349
85350     // used when attached to date picker which isnt showing buttons
85351     smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
85352
85353     // private
85354     totalYears: 10,
85355     yearOffset: 5, // 10 years in total, 2 per row
85356     monthOffset: 6, // 12 months, 2 per row
85357
85358     // private, inherit docs
85359     initComponent: function(){
85360         var me = this;
85361
85362         me.selectedCls = me.baseCls + '-selected';
85363         me.addEvents(
85364             /**
85365              * @event cancelclick
85366              * Fires when the cancel button is pressed.
85367              * @param {Ext.picker.Month} this
85368              */
85369             'cancelclick',
85370
85371             /**
85372              * @event monthclick
85373              * Fires when a month is clicked.
85374              * @param {Ext.picker.Month} this
85375              * @param {Array} value The current value
85376              */
85377             'monthclick',
85378
85379             /**
85380              * @event monthdblclick
85381              * Fires when a month is clicked.
85382              * @param {Ext.picker.Month} this
85383              * @param {Array} value The current value
85384              */
85385             'monthdblclick',
85386
85387             /**
85388              * @event okclick
85389              * Fires when the ok button is pressed.
85390              * @param {Ext.picker.Month} this
85391              * @param {Array} value The current value
85392              */
85393             'okclick',
85394
85395             /**
85396              * @event select
85397              * Fires when a month/year is selected.
85398              * @param {Ext.picker.Month} this
85399              * @param {Array} value The current value
85400              */
85401             'select',
85402
85403             /**
85404              * @event yearclick
85405              * Fires when a year is clicked.
85406              * @param {Ext.picker.Month} this
85407              * @param {Array} value The current value
85408              */
85409             'yearclick',
85410
85411             /**
85412              * @event yeardblclick
85413              * Fires when a year is clicked.
85414              * @param {Ext.picker.Month} this
85415              * @param {Array} value The current value
85416              */
85417             'yeardblclick'
85418         );
85419         if (me.small) {
85420             me.addCls(me.smallCls);
85421         }
85422         me.setValue(me.value);
85423         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
85424         this.callParent();
85425     },
85426
85427     // private, inherit docs
85428     onRender: function(ct, position){
85429         var me = this,
85430             i = 0,
85431             months = [],
85432             shortName = Ext.Date.getShortMonthName,
85433             monthLen = me.monthOffset;
85434
85435         for (; i < monthLen; ++i) {
85436             months.push(shortName(i), shortName(i + monthLen));
85437         }
85438
85439         Ext.apply(me.renderData, {
85440             months: months,
85441             years: me.getYears(),
85442             showButtons: me.showButtons
85443         });
85444
85445         me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl');
85446
85447         me.callParent(arguments);
85448     },
85449
85450     // private, inherit docs
85451     afterRender: function(){
85452         var me = this,
85453             body = me.bodyEl,
85454             buttonsEl = me.buttonsEl;
85455
85456         me.callParent();
85457
85458         me.mon(body, 'click', me.onBodyClick, me);
85459         me.mon(body, 'dblclick', me.onBodyClick, me);
85460
85461         // keep a reference to the year/month elements since we'll be re-using them
85462         me.years = body.select('.' + me.baseCls + '-year a');
85463         me.months = body.select('.' + me.baseCls + '-month a');
85464
85465         if (me.showButtons) {
85466             me.okBtn = Ext.create('Ext.button.Button', {
85467                 text: me.okText,
85468                 renderTo: buttonsEl,
85469                 handler: me.onOkClick,
85470                 scope: me
85471             });
85472             me.cancelBtn = Ext.create('Ext.button.Button', {
85473                 text: me.cancelText,
85474                 renderTo: buttonsEl,
85475                 handler: me.onCancelClick,
85476                 scope: me
85477             });
85478         }
85479
85480         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
85481             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
85482         });
85483
85484         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
85485         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
85486             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
85487         });
85488         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
85489         me.updateBody();
85490     },
85491
85492     /**
85493      * Set the value for the picker.
85494      * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
85495      * it can be an array, with the month as the first index and the year as the second.
85496      * @return {Ext.picker.Month} this
85497      */
85498     setValue: function(value){
85499         var me = this,
85500             active = me.activeYear,
85501             offset = me.monthOffset,
85502             year,
85503             index;
85504
85505         if (!value) {
85506             me.value = [null, null];
85507         } else if (Ext.isDate(value)) {
85508             me.value = [value.getMonth(), value.getFullYear()];
85509         } else {
85510             me.value = [value[0], value[1]];
85511         }
85512
85513         if (me.rendered) {
85514             year = me.value[1];
85515             if (year !== null) {
85516                 if ((year < active || year > active + me.yearOffset)) {
85517                     me.activeYear = year - me.yearOffset + 1;
85518                 }
85519             }
85520             me.updateBody();
85521         }
85522
85523         return me;
85524     },
85525
85526     /**
85527      * Gets the selected value. It is returned as an array [month, year]. It may
85528      * be a partial value, for example [null, 2010]. The month is returned as
85529      * 0 based.
85530      * @return {Number[]} The selected value
85531      */
85532     getValue: function(){
85533         return this.value;
85534     },
85535
85536     /**
85537      * Checks whether the picker has a selection
85538      * @return {Boolean} Returns true if both a month and year have been selected
85539      */
85540     hasSelection: function(){
85541         var value = this.value;
85542         return value[0] !== null && value[1] !== null;
85543     },
85544
85545     /**
85546      * Get an array of years to be pushed in the template. It is not in strict
85547      * numerical order because we want to show them in columns.
85548      * @private
85549      * @return {Number[]} An array of years
85550      */
85551     getYears: function(){
85552         var me = this,
85553             offset = me.yearOffset,
85554             start = me.activeYear, // put the "active" year on the left
85555             end = start + offset,
85556             i = start,
85557             years = [];
85558
85559         for (; i < end; ++i) {
85560             years.push(i, i + offset);
85561         }
85562
85563         return years;
85564     },
85565
85566     /**
85567      * Update the years in the body based on any change
85568      * @private
85569      */
85570     updateBody: function(){
85571         var me = this,
85572             years = me.years,
85573             months = me.months,
85574             yearNumbers = me.getYears(),
85575             cls = me.selectedCls,
85576             value = me.getYear(null),
85577             month = me.value[0],
85578             monthOffset = me.monthOffset,
85579             year;
85580
85581         if (me.rendered) {
85582             years.removeCls(cls);
85583             months.removeCls(cls);
85584             years.each(function(el, all, index){
85585                 year = yearNumbers[index];
85586                 el.dom.innerHTML = year;
85587                 if (year == value) {
85588                     el.dom.className = cls;
85589                 }
85590             });
85591             if (month !== null) {
85592                 if (month < monthOffset) {
85593                     month = month * 2;
85594                 } else {
85595                     month = (month - monthOffset) * 2 + 1;
85596                 }
85597                 months.item(month).addCls(cls);
85598             }
85599         }
85600     },
85601
85602     /**
85603      * Gets the current year value, or the default.
85604      * @private
85605      * @param {Number} defaultValue The default value to use if the year is not defined.
85606      * @param {Number} offset A number to offset the value by
85607      * @return {Number} The year value
85608      */
85609     getYear: function(defaultValue, offset) {
85610         var year = this.value[1];
85611         offset = offset || 0;
85612         return year === null ? defaultValue : year + offset;
85613     },
85614
85615     /**
85616      * React to clicks on the body
85617      * @private
85618      */
85619     onBodyClick: function(e, t) {
85620         var me = this,
85621             isDouble = e.type == 'dblclick';
85622
85623         if (e.getTarget('.' + me.baseCls + '-month')) {
85624             e.stopEvent();
85625             me.onMonthClick(t, isDouble);
85626         } else if (e.getTarget('.' + me.baseCls + '-year')) {
85627             e.stopEvent();
85628             me.onYearClick(t, isDouble);
85629         }
85630     },
85631
85632     /**
85633      * Modify the year display by passing an offset.
85634      * @param {Number} [offset=10] The offset to move by.
85635      */
85636     adjustYear: function(offset){
85637         if (typeof offset != 'number') {
85638             offset = this.totalYears;
85639         }
85640         this.activeYear += offset;
85641         this.updateBody();
85642     },
85643
85644     /**
85645      * React to the ok button being pressed
85646      * @private
85647      */
85648     onOkClick: function(){
85649         this.fireEvent('okclick', this, this.value);
85650     },
85651
85652     /**
85653      * React to the cancel button being pressed
85654      * @private
85655      */
85656     onCancelClick: function(){
85657         this.fireEvent('cancelclick', this);
85658     },
85659
85660     /**
85661      * React to a month being clicked
85662      * @private
85663      * @param {HTMLElement} target The element that was clicked
85664      * @param {Boolean} isDouble True if the event was a doubleclick
85665      */
85666     onMonthClick: function(target, isDouble){
85667         var me = this;
85668         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
85669         me.updateBody();
85670         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
85671         me.fireEvent('select', me, me.value);
85672     },
85673
85674     /**
85675      * React to a year being clicked
85676      * @private
85677      * @param {HTMLElement} target The element that was clicked
85678      * @param {Boolean} isDouble True if the event was a doubleclick
85679      */
85680     onYearClick: function(target, isDouble){
85681         var me = this;
85682         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
85683         me.updateBody();
85684         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
85685         me.fireEvent('select', me, me.value);
85686
85687     },
85688
85689     /**
85690      * Returns an offsetted number based on the position in the collection. Since our collections aren't
85691      * numerically ordered, this function helps to normalize those differences.
85692      * @private
85693      * @param {Object} index
85694      * @param {Object} offset
85695      * @return {Number} The correctly offsetted number
85696      */
85697     resolveOffset: function(index, offset){
85698         if (index % 2 === 0) {
85699             return (index / 2);
85700         } else {
85701             return offset + Math.floor(index / 2);
85702         }
85703     },
85704
85705     // private, inherit docs
85706     beforeDestroy: function(){
85707         var me = this;
85708         me.years = me.months = null;
85709         Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
85710         me.callParent();
85711     }
85712 });
85713
85714 /**
85715  * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
85716  * dates in a popup next to the field, but may also be used with other components.
85717  *
85718  * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
85719  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
85720  *
85721  * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
85722  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
85723  *
85724  * All the string values documented below may be overridden by including an Ext locale file in your page.
85725  *
85726  *     @example
85727  *     Ext.create('Ext.panel.Panel', {
85728  *         title: 'Choose a future date:',
85729  *         width: 200,
85730  *         bodyPadding: 10,
85731  *         renderTo: Ext.getBody(),
85732  *         items: [{
85733  *             xtype: 'datepicker',
85734  *             minDate: new Date(),
85735  *             handler: function(picker, date) {
85736  *                 // do something with the selected date
85737  *             }
85738  *         }]
85739  *     });
85740  */
85741 Ext.define('Ext.picker.Date', {
85742     extend: 'Ext.Component',
85743     requires: [
85744         'Ext.XTemplate',
85745         'Ext.button.Button',
85746         'Ext.button.Split',
85747         'Ext.util.ClickRepeater',
85748         'Ext.util.KeyNav',
85749         'Ext.EventObject',
85750         'Ext.fx.Manager',
85751         'Ext.picker.Month'
85752     ],
85753     alias: 'widget.datepicker',
85754     alternateClassName: 'Ext.DatePicker',
85755
85756     renderTpl: [
85757         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
85758             '<div role="presentation" class="{baseCls}-header">',
85759                 '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
85760                 '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
85761                 '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
85762             '</div>',
85763             '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
85764                 '<thead role="presentation"><tr role="presentation">',
85765                     '<tpl for="dayNames">',
85766                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
85767                     '</tpl>',
85768                 '</tr></thead>',
85769                 '<tbody role="presentation"><tr role="presentation">',
85770                     '<tpl for="days">',
85771                         '{#:this.isEndOfWeek}',
85772                         '<td role="gridcell" id="{[Ext.id()]}">',
85773                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
85774                                 '<em role="presentation"><span role="presentation"></span></em>',
85775                             '</a>',
85776                         '</td>',
85777                     '</tpl>',
85778                 '</tr></tbody>',
85779             '</table>',
85780             '<tpl if="showToday">',
85781                 '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
85782             '</tpl>',
85783         '</div>',
85784         {
85785             firstInitial: function(value) {
85786                 return value.substr(0,1);
85787             },
85788             isEndOfWeek: function(value) {
85789                 // convert from 1 based index to 0 based
85790                 // by decrementing value once.
85791                 value--;
85792                 var end = value % 7 === 0 && value !== 0;
85793                 return end ? '</tr><tr role="row">' : '';
85794             },
85795             longDay: function(value){
85796                 return Ext.Date.format(value, this.longDayFormat);
85797             }
85798         }
85799     ],
85800
85801     ariaTitle: 'Date Picker',
85802
85803     /**
85804      * @cfg {String} todayText
85805      * The text to display on the button that selects the current date
85806      */
85807     todayText : 'Today',
85808
85809     /**
85810      * @cfg {Function} handler
85811      * Optional. A function that will handle the select event of this picker. The handler is passed the following
85812      * parameters:
85813      *
85814      *   - `picker` : Ext.picker.Date
85815      *
85816      * This Date picker.
85817      *
85818      *   - `date` : Date
85819      *
85820      * The selected date.
85821      */
85822
85823     /**
85824      * @cfg {Object} scope
85825      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
85826      * DatePicker instance.
85827      */
85828
85829     /**
85830      * @cfg {String} todayTip
85831      * A string used to format the message for displaying in a tooltip over the button that selects the current date.
85832      * The `{0}` token in string is replaced by today's date.
85833      */
85834     todayTip : '{0} (Spacebar)',
85835
85836     /**
85837      * @cfg {String} minText
85838      * The error text to display if the minDate validation fails.
85839      */
85840     minText : 'This date is before the minimum date',
85841
85842     /**
85843      * @cfg {String} maxText
85844      * The error text to display if the maxDate validation fails.
85845      */
85846     maxText : 'This date is after the maximum date',
85847
85848     /**
85849      * @cfg {String} format
85850      * The default date format string which can be overriden for localization support. The format must be valid
85851      * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
85852      */
85853
85854     /**
85855      * @cfg {String} disabledDaysText
85856      * The tooltip to display when the date falls on a disabled day.
85857      */
85858     disabledDaysText : 'Disabled',
85859
85860     /**
85861      * @cfg {String} disabledDatesText
85862      * The tooltip text to display when the date falls on a disabled date.
85863      */
85864     disabledDatesText : 'Disabled',
85865
85866     /**
85867      * @cfg {String[]} monthNames
85868      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
85869      */
85870
85871     /**
85872      * @cfg {String[]} dayNames
85873      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
85874      */
85875
85876     /**
85877      * @cfg {String} nextText
85878      * The next month navigation button tooltip
85879      */
85880     nextText : 'Next Month (Control+Right)',
85881
85882     /**
85883      * @cfg {String} prevText
85884      * The previous month navigation button tooltip
85885      */
85886     prevText : 'Previous Month (Control+Left)',
85887
85888     /**
85889      * @cfg {String} monthYearText
85890      * The header month selector tooltip
85891      */
85892     monthYearText : 'Choose a month (Control+Up/Down to move years)',
85893
85894     /**
85895      * @cfg {Number} startDay
85896      * Day index at which the week should begin, 0-based (defaults to Sunday)
85897      */
85898     startDay : 0,
85899
85900     /**
85901      * @cfg {Boolean} showToday
85902      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
85903      * selects the current date.
85904      */
85905     showToday : true,
85906
85907     /**
85908      * @cfg {Date} [minDate=null]
85909      * Minimum allowable date (JavaScript date object)
85910      */
85911
85912     /**
85913      * @cfg {Date} [maxDate=null]
85914      * Maximum allowable date (JavaScript date object)
85915      */
85916
85917     /**
85918      * @cfg {Number[]} [disabledDays=null]
85919      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
85920      */
85921
85922     /**
85923      * @cfg {RegExp} [disabledDatesRE=null]
85924      * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
85925      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
85926      * disabledDates value.
85927      */
85928
85929     /**
85930      * @cfg {String[]} disabledDates
85931      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
85932      * they are very powerful. Some examples:
85933      *
85934      *   - ['03/08/2003', '09/16/2003'] would disable those exact dates
85935      *   - ['03/08', '09/16'] would disable those days for every year
85936      *   - ['^03/08'] would only match the beginning (useful if you are using short years)
85937      *   - ['03/../2006'] would disable every day in March 2006
85938      *   - ['^03'] would disable every day in every March
85939      *
85940      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
85941      * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
85942      * dot when restricting dates. For example: ['03\\.08\\.03'].
85943      */
85944
85945     /**
85946      * @cfg {Boolean} disableAnim
85947      * True to disable animations when showing the month picker.
85948      */
85949     disableAnim: false,
85950
85951     /**
85952      * @cfg {String} [baseCls='x-datepicker']
85953      * The base CSS class to apply to this components element.
85954      */
85955     baseCls: Ext.baseCSSPrefix + 'datepicker',
85956
85957     /**
85958      * @cfg {String} [selectedCls='x-datepicker-selected']
85959      * The class to apply to the selected cell.
85960      */
85961
85962     /**
85963      * @cfg {String} [disabledCellCls='x-datepicker-disabled']
85964      * The class to apply to disabled cells.
85965      */
85966
85967     /**
85968      * @cfg {String} longDayFormat
85969      * The format for displaying a date in a longer format.
85970      */
85971     longDayFormat: 'F d, Y',
85972
85973     /**
85974      * @cfg {Object} keyNavConfig
85975      * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
85976      * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
85977      * object will replace default handlers of the same name.
85978      */
85979
85980     /**
85981      * @cfg {Boolean} focusOnShow
85982      * True to automatically focus the picker on show.
85983      */
85984     focusOnShow: false,
85985
85986     // private
85987     // Set by other components to stop the picker focus being updated when the value changes.
85988     focusOnSelect: true,
85989
85990     width: 178,
85991
85992     // default value used to initialise each date in the DatePicker
85993     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
85994     initHour: 12, // 24-hour format
85995
85996     numDays: 42,
85997
85998     // private, inherit docs
85999     initComponent : function() {
86000         var me = this,
86001             clearTime = Ext.Date.clearTime;
86002
86003         me.selectedCls = me.baseCls + '-selected';
86004         me.disabledCellCls = me.baseCls + '-disabled';
86005         me.prevCls = me.baseCls + '-prevday';
86006         me.activeCls = me.baseCls + '-active';
86007         me.nextCls = me.baseCls + '-prevday';
86008         me.todayCls = me.baseCls + '-today';
86009         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
86010         this.callParent();
86011
86012         me.value = me.value ?
86013                  clearTime(me.value, true) : clearTime(new Date());
86014
86015         me.addEvents(
86016             /**
86017              * @event select
86018              * Fires when a date is selected
86019              * @param {Ext.picker.Date} this DatePicker
86020              * @param {Date} date The selected date
86021              */
86022             'select'
86023         );
86024
86025         me.initDisabledDays();
86026     },
86027
86028     // private, inherit docs
86029     onRender : function(container, position){
86030         /*
86031          * days array for looping through 6 full weeks (6 weeks * 7 days)
86032          * Note that we explicitly force the size here so the template creates
86033          * all the appropriate cells.
86034          */
86035
86036         var me = this,
86037             days = new Array(me.numDays),
86038             today = Ext.Date.format(new Date(), me.format);
86039
86040         Ext.applyIf(me, {
86041             renderData: {}
86042         });
86043
86044         Ext.apply(me.renderData, {
86045             dayNames: me.dayNames,
86046             ariaTitle: me.ariaTitle,
86047             value: me.value,
86048             showToday: me.showToday,
86049             prevText: me.prevText,
86050             nextText: me.nextText,
86051             days: days
86052         });
86053         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
86054
86055         me.addChildEls('eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
86056
86057         this.callParent(arguments);
86058         me.el.unselectable();
86059
86060         me.cells = me.eventEl.select('tbody td');
86061         me.textNodes = me.eventEl.query('tbody td span');
86062
86063         me.monthBtn = Ext.create('Ext.button.Split', {
86064             text: '',
86065             tooltip: me.monthYearText,
86066             renderTo: me.middleBtnEl
86067         });
86068         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
86069
86070
86071         me.todayBtn = Ext.create('Ext.button.Button', {
86072             renderTo: me.footerEl,
86073             text: Ext.String.format(me.todayText, today),
86074             tooltip: Ext.String.format(me.todayTip, today),
86075             handler: me.selectToday,
86076             scope: me
86077         });
86078     },
86079
86080     // private, inherit docs
86081     initEvents: function(){
86082         var me = this,
86083             eDate = Ext.Date,
86084             day = eDate.DAY;
86085
86086         this.callParent();
86087
86088         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
86089             handler: me.showPrevMonth,
86090             scope: me,
86091             preventDefault: true,
86092             stopDefault: true
86093         });
86094
86095         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
86096             handler: me.showNextMonth,
86097             scope: me,
86098             preventDefault:true,
86099             stopDefault:true
86100         });
86101
86102         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
86103             scope: me,
86104             'left' : function(e){
86105                 if(e.ctrlKey){
86106                     me.showPrevMonth();
86107                 }else{
86108                     me.update(eDate.add(me.activeDate, day, -1));
86109                 }
86110             },
86111
86112             'right' : function(e){
86113                 if(e.ctrlKey){
86114                     me.showNextMonth();
86115                 }else{
86116                     me.update(eDate.add(me.activeDate, day, 1));
86117                 }
86118             },
86119
86120             'up' : function(e){
86121                 if(e.ctrlKey){
86122                     me.showNextYear();
86123                 }else{
86124                     me.update(eDate.add(me.activeDate, day, -7));
86125                 }
86126             },
86127
86128             'down' : function(e){
86129                 if(e.ctrlKey){
86130                     me.showPrevYear();
86131                 }else{
86132                     me.update(eDate.add(me.activeDate, day, 7));
86133                 }
86134             },
86135             'pageUp' : me.showNextMonth,
86136             'pageDown' : me.showPrevMonth,
86137             'enter' : function(e){
86138                 e.stopPropagation();
86139                 return true;
86140             }
86141         }, me.keyNavConfig));
86142
86143         if(me.showToday){
86144             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
86145         }
86146         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
86147         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
86148         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
86149         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
86150         me.update(me.value);
86151     },
86152
86153     /**
86154      * Setup the disabled dates regex based on config options
86155      * @private
86156      */
86157     initDisabledDays : function(){
86158         var me = this,
86159             dd = me.disabledDates,
86160             re = '(?:',
86161             len;
86162
86163         if(!me.disabledDatesRE && dd){
86164                 len = dd.length - 1;
86165
86166             Ext.each(dd, function(d, i){
86167                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
86168                 if(i != len){
86169                     re += '|';
86170                 }
86171             }, me);
86172             me.disabledDatesRE = new RegExp(re + ')');
86173         }
86174     },
86175
86176     /**
86177      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
86178      * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
86179      * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
86180      * @return {Ext.picker.Date} this
86181      */
86182     setDisabledDates : function(dd){
86183         var me = this;
86184
86185         if(Ext.isArray(dd)){
86186             me.disabledDates = dd;
86187             me.disabledDatesRE = null;
86188         }else{
86189             me.disabledDatesRE = dd;
86190         }
86191         me.initDisabledDays();
86192         me.update(me.value, true);
86193         return me;
86194     },
86195
86196     /**
86197      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
86198      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
86199      * on supported values.
86200      * @return {Ext.picker.Date} this
86201      */
86202     setDisabledDays : function(dd){
86203         this.disabledDays = dd;
86204         return this.update(this.value, true);
86205     },
86206
86207     /**
86208      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
86209      * @param {Date} value The minimum date that can be selected
86210      * @return {Ext.picker.Date} this
86211      */
86212     setMinDate : function(dt){
86213         this.minDate = dt;
86214         return this.update(this.value, true);
86215     },
86216
86217     /**
86218      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
86219      * @param {Date} value The maximum date that can be selected
86220      * @return {Ext.picker.Date} this
86221      */
86222     setMaxDate : function(dt){
86223         this.maxDate = dt;
86224         return this.update(this.value, true);
86225     },
86226
86227     /**
86228      * Sets the value of the date field
86229      * @param {Date} value The date to set
86230      * @return {Ext.picker.Date} this
86231      */
86232     setValue : function(value){
86233         this.value = Ext.Date.clearTime(value, true);
86234         return this.update(this.value);
86235     },
86236
86237     /**
86238      * Gets the current selected value of the date field
86239      * @return {Date} The selected date
86240      */
86241     getValue : function(){
86242         return this.value;
86243     },
86244
86245     // private
86246     focus : function(){
86247         this.update(this.activeDate);
86248     },
86249
86250     // private, inherit docs
86251     onEnable: function(){
86252         this.callParent();
86253         this.setDisabledStatus(false);
86254         this.update(this.activeDate);
86255
86256     },
86257
86258     // private, inherit docs
86259     onDisable : function(){
86260         this.callParent();
86261         this.setDisabledStatus(true);
86262     },
86263
86264     /**
86265      * Set the disabled state of various internal components
86266      * @private
86267      * @param {Boolean} disabled
86268      */
86269     setDisabledStatus : function(disabled){
86270         var me = this;
86271
86272         me.keyNav.setDisabled(disabled);
86273         me.prevRepeater.setDisabled(disabled);
86274         me.nextRepeater.setDisabled(disabled);
86275         if (me.showToday) {
86276             me.todayKeyListener.setDisabled(disabled);
86277             me.todayBtn.setDisabled(disabled);
86278         }
86279     },
86280
86281     /**
86282      * Get the current active date.
86283      * @private
86284      * @return {Date} The active date
86285      */
86286     getActive: function(){
86287         return this.activeDate || this.value;
86288     },
86289
86290     /**
86291      * Run any animation required to hide/show the month picker.
86292      * @private
86293      * @param {Boolean} isHide True if it's a hide operation
86294      */
86295     runAnimation: function(isHide){
86296         var picker = this.monthPicker,
86297             options = {
86298                 duration: 200,
86299                 callback: function(){
86300                     if (isHide) {
86301                         picker.hide();
86302                     } else {
86303                         picker.show();
86304                     }
86305                 }
86306             };
86307
86308         if (isHide) {
86309             picker.el.slideOut('t', options);
86310         } else {
86311             picker.el.slideIn('t', options);
86312         }
86313     },
86314
86315     /**
86316      * Hides the month picker, if it's visible.
86317      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
86318      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
86319      * whether to animate or not.
86320      * @return {Ext.picker.Date} this
86321      */
86322     hideMonthPicker : function(animate){
86323         var me = this,
86324             picker = me.monthPicker;
86325
86326         if (picker) {
86327             if (me.shouldAnimate(animate)) {
86328                 me.runAnimation(true);
86329             } else {
86330                 picker.hide();
86331             }
86332         }
86333         return me;
86334     },
86335
86336     /**
86337      * Show the month picker
86338      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
86339      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
86340      * whether to animate or not.
86341      * @return {Ext.picker.Date} this
86342      */
86343     showMonthPicker : function(animate){
86344         var me = this,
86345             picker;
86346         
86347         if (me.rendered && !me.disabled) {
86348             picker = me.createMonthPicker();
86349             picker.setValue(me.getActive());
86350             picker.setSize(me.getSize());
86351             picker.setPosition(-1, -1);
86352             if (me.shouldAnimate(animate)) {
86353                 me.runAnimation(false);
86354             } else {
86355                 picker.show();
86356             }
86357         }
86358         return me;
86359     },
86360     
86361     /**
86362      * Checks whether a hide/show action should animate
86363      * @private
86364      * @param {Boolean} [animate] A possible animation value
86365      * @return {Boolean} Whether to animate the action
86366      */
86367     shouldAnimate: function(animate){
86368         return Ext.isDefined(animate) ? animate : !this.disableAnim;
86369     },
86370
86371     /**
86372      * Create the month picker instance
86373      * @private
86374      * @return {Ext.picker.Month} picker
86375      */
86376     createMonthPicker: function(){
86377         var me = this,
86378             picker = me.monthPicker;
86379
86380         if (!picker) {
86381             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
86382                 renderTo: me.el,
86383                 floating: true,
86384                 shadow: false,
86385                 small: me.showToday === false,
86386                 listeners: {
86387                     scope: me,
86388                     cancelclick: me.onCancelClick,
86389                     okclick: me.onOkClick,
86390                     yeardblclick: me.onOkClick,
86391                     monthdblclick: me.onOkClick
86392                 }
86393             });
86394             if (!me.disableAnim) {
86395                 // hide the element if we're animating to prevent an initial flicker
86396                 picker.el.setStyle('display', 'none');
86397             }
86398             me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
86399         }
86400         return picker;
86401     },
86402
86403     /**
86404      * Respond to an ok click on the month picker
86405      * @private
86406      */
86407     onOkClick: function(picker, value){
86408         var me = this,
86409             month = value[0],
86410             year = value[1],
86411             date = new Date(year, month, me.getActive().getDate());
86412
86413         if (date.getMonth() !== month) {
86414             // 'fix' the JS rolling date conversion if needed
86415             date = new Date(year, month, 1).getLastDateOfMonth();
86416         }
86417         me.update(date);
86418         me.hideMonthPicker();
86419     },
86420
86421     /**
86422      * Respond to a cancel click on the month picker
86423      * @private
86424      */
86425     onCancelClick: function(){
86426         this.hideMonthPicker();
86427     },
86428
86429     /**
86430      * Show the previous month.
86431      * @param {Object} e
86432      * @return {Ext.picker.Date} this
86433      */
86434     showPrevMonth : function(e){
86435         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
86436     },
86437
86438     /**
86439      * Show the next month.
86440      * @param {Object} e
86441      * @return {Ext.picker.Date} this
86442      */
86443     showNextMonth : function(e){
86444         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
86445     },
86446
86447     /**
86448      * Show the previous year.
86449      * @return {Ext.picker.Date} this
86450      */
86451     showPrevYear : function(){
86452         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
86453     },
86454
86455     /**
86456      * Show the next year.
86457      * @return {Ext.picker.Date} this
86458      */
86459     showNextYear : function(){
86460         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
86461     },
86462
86463     /**
86464      * Respond to the mouse wheel event
86465      * @private
86466      * @param {Ext.EventObject} e
86467      */
86468     handleMouseWheel : function(e){
86469         e.stopEvent();
86470         if(!this.disabled){
86471             var delta = e.getWheelDelta();
86472             if(delta > 0){
86473                 this.showPrevMonth();
86474             } else if(delta < 0){
86475                 this.showNextMonth();
86476             }
86477         }
86478     },
86479
86480     /**
86481      * Respond to a date being clicked in the picker
86482      * @private
86483      * @param {Ext.EventObject} e
86484      * @param {HTMLElement} t
86485      */
86486     handleDateClick : function(e, t){
86487         var me = this,
86488             handler = me.handler;
86489
86490         e.stopEvent();
86491         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
86492             me.cancelFocus = me.focusOnSelect === false;
86493             me.setValue(new Date(t.dateValue));
86494             delete me.cancelFocus;
86495             me.fireEvent('select', me, me.value);
86496             if (handler) {
86497                 handler.call(me.scope || me, me, me.value);
86498             }
86499             // event handling is turned off on hide
86500             // when we are using the picker in a field
86501             // therefore onSelect comes AFTER the select
86502             // event.
86503             me.onSelect();
86504         }
86505     },
86506
86507     /**
86508      * Perform any post-select actions
86509      * @private
86510      */
86511     onSelect: function() {
86512         if (this.hideOnSelect) {
86513              this.hide();
86514          }
86515     },
86516
86517     /**
86518      * Sets the current value to today.
86519      * @return {Ext.picker.Date} this
86520      */
86521     selectToday : function(){
86522         var me = this,
86523             btn = me.todayBtn,
86524             handler = me.handler;
86525
86526         if(btn && !btn.disabled){
86527             me.setValue(Ext.Date.clearTime(new Date()));
86528             me.fireEvent('select', me, me.value);
86529             if (handler) {
86530                 handler.call(me.scope || me, me, me.value);
86531             }
86532             me.onSelect();
86533         }
86534         return me;
86535     },
86536
86537     /**
86538      * Update the selected cell
86539      * @private
86540      * @param {Date} date The new date
86541      * @param {Date} active The active date
86542      */
86543     selectedUpdate: function(date, active){
86544         var me = this,
86545             t = date.getTime(),
86546             cells = me.cells,
86547             cls = me.selectedCls;
86548
86549         cells.removeCls(cls);
86550         cells.each(function(c){
86551             if (c.dom.firstChild.dateValue == t) {
86552                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
86553                 c.addCls(cls);
86554                 if(me.isVisible() && !me.cancelFocus){
86555                     Ext.fly(c.dom.firstChild).focus(50);
86556                 }
86557                 return false;
86558             }
86559         }, this);
86560     },
86561
86562     /**
86563      * Update the contents of the picker for a new month
86564      * @private
86565      * @param {Date} date The new date
86566      * @param {Date} active The active date
86567      */
86568     fullUpdate: function(date, active){
86569         var me = this,
86570             cells = me.cells.elements,
86571             textNodes = me.textNodes,
86572             disabledCls = me.disabledCellCls,
86573             eDate = Ext.Date,
86574             i = 0,
86575             extraDays = 0,
86576             visible = me.isVisible(),
86577             sel = +eDate.clearTime(date, true),
86578             today = +eDate.clearTime(new Date()),
86579             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
86580             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
86581             ddMatch = me.disabledDatesRE,
86582             ddText = me.disabledDatesText,
86583             ddays = me.disabledDays ? me.disabledDays.join('') : false,
86584             ddaysText = me.disabledDaysText,
86585             format = me.format,
86586             days = eDate.getDaysInMonth(date),
86587             firstOfMonth = eDate.getFirstDateOfMonth(date),
86588             startingPos = firstOfMonth.getDay() - me.startDay,
86589             previousMonth = eDate.add(date, eDate.MONTH, -1),
86590             longDayFormat = me.longDayFormat,
86591             prevStart,
86592             current,
86593             disableToday,
86594             tempDate,
86595             setCellClass,
86596             html,
86597             cls,
86598             formatValue,
86599             value;
86600
86601         if (startingPos < 0) {
86602             startingPos += 7;
86603         }
86604
86605         days += startingPos;
86606         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
86607         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
86608
86609         if (me.showToday) {
86610             tempDate = eDate.clearTime(new Date());
86611             disableToday = (tempDate < min || tempDate > max ||
86612                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
86613                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
86614
86615             if (!me.disabled) {
86616                 me.todayBtn.setDisabled(disableToday);
86617                 me.todayKeyListener.setDisabled(disableToday);
86618             }
86619         }
86620
86621         setCellClass = function(cell){
86622             value = +eDate.clearTime(current, true);
86623             cell.title = eDate.format(current, longDayFormat);
86624             // store dateValue number as an expando
86625             cell.firstChild.dateValue = value;
86626             if(value == today){
86627                 cell.className += ' ' + me.todayCls;
86628                 cell.title = me.todayText;
86629             }
86630             if(value == sel){
86631                 cell.className += ' ' + me.selectedCls;
86632                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
86633                 if (visible && me.floating) {
86634                     Ext.fly(cell.firstChild).focus(50);
86635                 }
86636             }
86637             // disabling
86638             if(value < min) {
86639                 cell.className = disabledCls;
86640                 cell.title = me.minText;
86641                 return;
86642             }
86643             if(value > max) {
86644                 cell.className = disabledCls;
86645                 cell.title = me.maxText;
86646                 return;
86647             }
86648             if(ddays){
86649                 if(ddays.indexOf(current.getDay()) != -1){
86650                     cell.title = ddaysText;
86651                     cell.className = disabledCls;
86652                 }
86653             }
86654             if(ddMatch && format){
86655                 formatValue = eDate.dateFormat(current, format);
86656                 if(ddMatch.test(formatValue)){
86657                     cell.title = ddText.replace('%0', formatValue);
86658                     cell.className = disabledCls;
86659                 }
86660             }
86661         };
86662
86663         for(; i < me.numDays; ++i) {
86664             if (i < startingPos) {
86665                 html = (++prevStart);
86666                 cls = me.prevCls;
86667             } else if (i >= days) {
86668                 html = (++extraDays);
86669                 cls = me.nextCls;
86670             } else {
86671                 html = i - startingPos + 1;
86672                 cls = me.activeCls;
86673             }
86674             textNodes[i].innerHTML = html;
86675             cells[i].className = cls;
86676             current.setDate(current.getDate() + 1);
86677             setCellClass(cells[i]);
86678         }
86679
86680         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
86681     },
86682
86683     /**
86684      * Update the contents of the picker
86685      * @private
86686      * @param {Date} date The new date
86687      * @param {Boolean} forceRefresh True to force a full refresh
86688      */
86689     update : function(date, forceRefresh){
86690         var me = this,
86691             active = me.activeDate;
86692
86693         if (me.rendered) {
86694             me.activeDate = date;
86695             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
86696                 me.selectedUpdate(date, active);
86697             } else {
86698                 me.fullUpdate(date, active);
86699             }
86700         }
86701         return me;
86702     },
86703
86704     // private, inherit docs
86705     beforeDestroy : function() {
86706         var me = this;
86707
86708         if (me.rendered) {
86709             Ext.destroy(
86710                 me.todayKeyListener,
86711                 me.keyNav,
86712                 me.monthPicker,
86713                 me.monthBtn,
86714                 me.nextRepeater,
86715                 me.prevRepeater,
86716                 me.todayBtn
86717             );
86718             delete me.textNodes;
86719             delete me.cells.elements;
86720         }
86721         me.callParent();
86722     },
86723
86724     // private, inherit docs
86725     onShow: function() {
86726         this.callParent(arguments);
86727         if (this.focusOnShow) {
86728             this.focus();
86729         }
86730     }
86731 },
86732
86733 // After dependencies have loaded:
86734 function() {
86735     var proto = this.prototype;
86736
86737     proto.monthNames = Ext.Date.monthNames;
86738
86739     proto.dayNames = Ext.Date.dayNames;
86740
86741     proto.format = Ext.Date.defaultFormat;
86742 });
86743
86744 /**
86745  * @docauthor Jason Johnston <jason@sencha.com>
86746  *
86747  * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
86748  * validation.
86749  *
86750  * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
86751  * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
86752  * configs. These may be reconfigured to use date formats appropriate for the user's locale.
86753  *
86754  * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
86755  * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
86756  * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
86757  *
86758  * # Example usage
86759  *
86760  *     @example
86761  *     Ext.create('Ext.form.Panel', {
86762  *         renderTo: Ext.getBody(),
86763  *         width: 300,
86764  *         bodyPadding: 10,
86765  *         title: 'Dates',
86766  *         items: [{
86767  *             xtype: 'datefield',
86768  *             anchor: '100%',
86769  *             fieldLabel: 'From',
86770  *             name: 'from_date',
86771  *             maxValue: new Date()  // limited to the current date or prior
86772  *         }, {
86773  *             xtype: 'datefield',
86774  *             anchor: '100%',
86775  *             fieldLabel: 'To',
86776  *             name: 'to_date',
86777  *             value: new Date()  // defaults to today
86778  *         }]
86779  *     });
86780  *
86781  * # Date Formats Examples
86782  *
86783  * This example shows a couple of different date format parsing scenarios. Both use custom date format
86784  * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
86785  *
86786  *     @example
86787  *     Ext.create('Ext.form.Panel', {
86788  *         renderTo: Ext.getBody(),
86789  *         width: 300,
86790  *         bodyPadding: 10,
86791  *         title: 'Dates',
86792  *         items: [{
86793  *             xtype: 'datefield',
86794  *             anchor: '100%',
86795  *             fieldLabel: 'Date',
86796  *             name: 'date',
86797  *             // The value matches the format; will be parsed and displayed using that format.
86798  *             format: 'm d Y',
86799  *             value: '2 4 1978'
86800  *         }, {
86801  *             xtype: 'datefield',
86802  *             anchor: '100%',
86803  *             fieldLabel: 'Date',
86804  *             name: 'date',
86805  *             // The value does not match the format, but does match an altFormat; will be parsed
86806  *             // using the altFormat and displayed using the format.
86807  *             format: 'm d Y',
86808  *             altFormats: 'm,d,Y|m.d.Y',
86809  *             value: '2.4.1978'
86810  *         }]
86811  *     });
86812  */
86813 Ext.define('Ext.form.field.Date', {
86814     extend:'Ext.form.field.Picker',
86815     alias: 'widget.datefield',
86816     requires: ['Ext.picker.Date'],
86817     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
86818
86819     /**
86820      * @cfg {String} format
86821      * The default date format string which can be overriden for localization support. The format must be valid
86822      * according to {@link Ext.Date#parse}.
86823      */
86824     format : "m/d/Y",
86825     /**
86826      * @cfg {String} altFormats
86827      * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
86828      * format.
86829      */
86830     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",
86831     /**
86832      * @cfg {String} disabledDaysText
86833      * The tooltip to display when the date falls on a disabled day.
86834      */
86835     disabledDaysText : "Disabled",
86836     /**
86837      * @cfg {String} disabledDatesText
86838      * The tooltip text to display when the date falls on a disabled date.
86839      */
86840     disabledDatesText : "Disabled",
86841     /**
86842      * @cfg {String} minText
86843      * The error text to display when the date in the cell is before {@link #minValue}.
86844      */
86845     minText : "The date in this field must be equal to or after {0}",
86846     /**
86847      * @cfg {String} maxText
86848      * The error text to display when the date in the cell is after {@link #maxValue}.
86849      */
86850     maxText : "The date in this field must be equal to or before {0}",
86851     /**
86852      * @cfg {String} invalidText
86853      * The error text to display when the date in the field is invalid.
86854      */
86855     invalidText : "{0} is not a valid date - it must be in the format {1}",
86856     /**
86857      * @cfg {String} [triggerCls='x-form-date-trigger']
86858      * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
86859      * and triggerCls will be **appended** if specified (default class displays a calendar icon).
86860      */
86861     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
86862     /**
86863      * @cfg {Boolean} showToday
86864      * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
86865      * spacebar that selects the current date.
86866      */
86867     showToday : true,
86868     /**
86869      * @cfg {Date/String} minValue
86870      * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
86871      */
86872     /**
86873      * @cfg {Date/String} maxValue
86874      * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
86875      */
86876     /**
86877      * @cfg {Number[]} disabledDays
86878      * An array of days to disable, 0 based. Some examples:
86879      *
86880      *     // disable Sunday and Saturday:
86881      *     disabledDays:  [0, 6]
86882      *     // disable weekdays:
86883      *     disabledDays: [1,2,3,4,5]
86884      */
86885     /**
86886      * @cfg {String[]} disabledDates
86887      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
86888      * they are very powerful. Some examples:
86889      *
86890      *     // disable these exact dates:
86891      *     disabledDates: ["03/08/2003", "09/16/2003"]
86892      *     // disable these days for every year:
86893      *     disabledDates: ["03/08", "09/16"]
86894      *     // only match the beginning (useful if you are using short years):
86895      *     disabledDates: ["^03/08"]
86896      *     // disable every day in March 2006:
86897      *     disabledDates: ["03/../2006"]
86898      *     // disable every day in every March:
86899      *     disabledDates: ["^03"]
86900      *
86901      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
86902      * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
86903      * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
86904      */
86905
86906     /**
86907      * @cfg {String} submitFormat
86908      * The date format string which will be submitted to the server. The format must be valid according to {@link
86909      * Ext.Date#parse} (defaults to {@link #format}).
86910      */
86911
86912     // in the absence of a time value, a default value of 12 noon will be used
86913     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
86914     initTime: '12', // 24 hour format
86915
86916     initTimeFormat: 'H',
86917
86918     matchFieldWidth: false,
86919     /**
86920      * @cfg {Number} startDay
86921      * Day index at which the week should begin, 0-based (defaults to Sunday)
86922      */
86923     startDay: 0,
86924
86925     initComponent : function(){
86926         var me = this,
86927             isString = Ext.isString,
86928             min, max;
86929
86930         min = me.minValue;
86931         max = me.maxValue;
86932         if(isString(min)){
86933             me.minValue = me.parseDate(min);
86934         }
86935         if(isString(max)){
86936             me.maxValue = me.parseDate(max);
86937         }
86938         me.disabledDatesRE = null;
86939         me.initDisabledDays();
86940
86941         me.callParent();
86942     },
86943
86944     initValue: function() {
86945         var me = this,
86946             value = me.value;
86947
86948         // If a String value was supplied, try to convert it to a proper Date
86949         if (Ext.isString(value)) {
86950             me.value = me.rawToValue(value);
86951         }
86952
86953         me.callParent();
86954     },
86955
86956     // private
86957     initDisabledDays : function(){
86958         if(this.disabledDates){
86959             var dd = this.disabledDates,
86960                 len = dd.length - 1,
86961                 re = "(?:";
86962
86963             Ext.each(dd, function(d, i){
86964                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
86965                 if (i !== len) {
86966                     re += '|';
86967                 }
86968             }, this);
86969             this.disabledDatesRE = new RegExp(re + ')');
86970         }
86971     },
86972
86973     /**
86974      * Replaces any existing disabled dates with new values and refreshes the Date picker.
86975      * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
86976      * supported values) used to disable a pattern of dates.
86977      */
86978     setDisabledDates : function(dd){
86979         var me = this,
86980             picker = me.picker;
86981
86982         me.disabledDates = dd;
86983         me.initDisabledDays();
86984         if (picker) {
86985             picker.setDisabledDates(me.disabledDatesRE);
86986         }
86987     },
86988
86989     /**
86990      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
86991      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
86992      * supported values.
86993      */
86994     setDisabledDays : function(dd){
86995         var picker = this.picker;
86996
86997         this.disabledDays = dd;
86998         if (picker) {
86999             picker.setDisabledDays(dd);
87000         }
87001     },
87002
87003     /**
87004      * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
87005      * @param {Date} value The minimum date that can be selected
87006      */
87007     setMinValue : function(dt){
87008         var me = this,
87009             picker = me.picker,
87010             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
87011
87012         me.minValue = minValue;
87013         if (picker) {
87014             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
87015             picker.setMinDate(minValue);
87016         }
87017     },
87018
87019     /**
87020      * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
87021      * @param {Date} value The maximum date that can be selected
87022      */
87023     setMaxValue : function(dt){
87024         var me = this,
87025             picker = me.picker,
87026             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
87027
87028         me.maxValue = maxValue;
87029         if (picker) {
87030             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
87031             picker.setMaxDate(maxValue);
87032         }
87033     },
87034
87035     /**
87036      * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
87037      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
87038      * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
87039      * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
87040      * @param {Object} [value] The value to get errors for (defaults to the current field value)
87041      * @return {String[]} All validation errors for this field
87042      */
87043     getErrors: function(value) {
87044         var me = this,
87045             format = Ext.String.format,
87046             clearTime = Ext.Date.clearTime,
87047             errors = me.callParent(arguments),
87048             disabledDays = me.disabledDays,
87049             disabledDatesRE = me.disabledDatesRE,
87050             minValue = me.minValue,
87051             maxValue = me.maxValue,
87052             len = disabledDays ? disabledDays.length : 0,
87053             i = 0,
87054             svalue,
87055             fvalue,
87056             day,
87057             time;
87058
87059         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
87060
87061         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
87062              return errors;
87063         }
87064
87065         svalue = value;
87066         value = me.parseDate(value);
87067         if (!value) {
87068             errors.push(format(me.invalidText, svalue, me.format));
87069             return errors;
87070         }
87071
87072         time = value.getTime();
87073         if (minValue && time < clearTime(minValue).getTime()) {
87074             errors.push(format(me.minText, me.formatDate(minValue)));
87075         }
87076
87077         if (maxValue && time > clearTime(maxValue).getTime()) {
87078             errors.push(format(me.maxText, me.formatDate(maxValue)));
87079         }
87080
87081         if (disabledDays) {
87082             day = value.getDay();
87083
87084             for(; i < len; i++) {
87085                 if (day === disabledDays[i]) {
87086                     errors.push(me.disabledDaysText);
87087                     break;
87088                 }
87089             }
87090         }
87091
87092         fvalue = me.formatDate(value);
87093         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
87094             errors.push(format(me.disabledDatesText, fvalue));
87095         }
87096
87097         return errors;
87098     },
87099
87100     rawToValue: function(rawValue) {
87101         return this.parseDate(rawValue) || rawValue || null;
87102     },
87103
87104     valueToRaw: function(value) {
87105         return this.formatDate(this.parseDate(value));
87106     },
87107
87108     /**
87109      * @method setValue
87110      * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
87111      * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
87112      * format used is "m/d/Y").
87113      *
87114      * Usage:
87115      *
87116      *     //All of these calls set the same date value (May 4, 2006)
87117      *
87118      *     //Pass a date object:
87119      *     var dt = new Date('5/4/2006');
87120      *     dateField.setValue(dt);
87121      *
87122      *     //Pass a date string (default format):
87123      *     dateField.setValue('05/04/2006');
87124      *
87125      *     //Pass a date string (custom format):
87126      *     dateField.format = 'Y-m-d';
87127      *     dateField.setValue('2006-05-04');
87128      *
87129      * @param {String/Date} date The date or valid date string
87130      * @return {Ext.form.field.Date} this
87131      */
87132
87133     /**
87134      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
87135      * @param {String} value The value to attempt to parse
87136      * @param {String} format A valid date format (see {@link Ext.Date#parse})
87137      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
87138      */
87139     safeParse : function(value, format) {
87140         var me = this,
87141             utilDate = Ext.Date,
87142             parsedDate,
87143             result = null;
87144
87145         if (utilDate.formatContainsHourInfo(format)) {
87146             // if parse format contains hour information, no DST adjustment is necessary
87147             result = utilDate.parse(value, format);
87148         } else {
87149             // set time to 12 noon, then clear the time
87150             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
87151             if (parsedDate) {
87152                 result = utilDate.clearTime(parsedDate);
87153             }
87154         }
87155         return result;
87156     },
87157
87158     // @private
87159     getSubmitValue: function() {
87160         var format = this.submitFormat || this.format,
87161             value = this.getValue();
87162
87163         return value ? Ext.Date.format(value, format) : '';
87164     },
87165
87166     /**
87167      * @private
87168      */
87169     parseDate : function(value) {
87170         if(!value || Ext.isDate(value)){
87171             return value;
87172         }
87173
87174         var me = this,
87175             val = me.safeParse(value, me.format),
87176             altFormats = me.altFormats,
87177             altFormatsArray = me.altFormatsArray,
87178             i = 0,
87179             len;
87180
87181         if (!val && altFormats) {
87182             altFormatsArray = altFormatsArray || altFormats.split('|');
87183             len = altFormatsArray.length;
87184             for (; i < len && !val; ++i) {
87185                 val = me.safeParse(value, altFormatsArray[i]);
87186             }
87187         }
87188         return val;
87189     },
87190
87191     // private
87192     formatDate : function(date){
87193         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
87194     },
87195
87196     createPicker: function() {
87197         var me = this,
87198             format = Ext.String.format;
87199
87200         return Ext.create('Ext.picker.Date', {
87201             pickerField: me,
87202             ownerCt: me.ownerCt,
87203             renderTo: document.body,
87204             floating: true,
87205             hidden: true,
87206             focusOnShow: true,
87207             minDate: me.minValue,
87208             maxDate: me.maxValue,
87209             disabledDatesRE: me.disabledDatesRE,
87210             disabledDatesText: me.disabledDatesText,
87211             disabledDays: me.disabledDays,
87212             disabledDaysText: me.disabledDaysText,
87213             format: me.format,
87214             showToday: me.showToday,
87215             startDay: me.startDay,
87216             minText: format(me.minText, me.formatDate(me.minValue)),
87217             maxText: format(me.maxText, me.formatDate(me.maxValue)),
87218             listeners: {
87219                 scope: me,
87220                 select: me.onSelect
87221             },
87222             keyNavConfig: {
87223                 esc: function() {
87224                     me.collapse();
87225                 }
87226             }
87227         });
87228     },
87229
87230     onSelect: function(m, d) {
87231         var me = this;
87232
87233         me.setValue(d);
87234         me.fireEvent('select', me, d);
87235         me.collapse();
87236     },
87237
87238     /**
87239      * @private
87240      * Sets the Date picker's value to match the current field value when expanding.
87241      */
87242     onExpand: function() {
87243         var value = this.getValue();
87244         this.picker.setValue(Ext.isDate(value) ? value : new Date());
87245     },
87246
87247     /**
87248      * @private
87249      * Focuses the field when collapsing the Date picker.
87250      */
87251     onCollapse: function() {
87252         this.focus(false, 60);
87253     },
87254
87255     // private
87256     beforeBlur : function(){
87257         var me = this,
87258             v = me.parseDate(me.getRawValue()),
87259             focusTask = me.focusTask;
87260
87261         if (focusTask) {
87262             focusTask.cancel();
87263         }
87264
87265         if (v) {
87266             me.setValue(v);
87267         }
87268     }
87269
87270     /**
87271      * @hide
87272      * @cfg {Boolean} grow
87273      */
87274     /**
87275      * @hide
87276      * @cfg {Number} growMin
87277      */
87278     /**
87279      * @hide
87280      * @cfg {Number} growMax
87281      */
87282     /**
87283      * @hide
87284      * @method autoSize
87285      */
87286 });
87287
87288 /**
87289  * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
87290  * 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
87291  * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
87292  * to be rendered.
87293  *
87294  * If you have more complex content, or need to include components within the displayed content, also consider using a
87295  * {@link Ext.form.FieldContainer} instead.
87296  *
87297  * Example:
87298  *
87299  *     @example
87300  *     Ext.create('Ext.form.Panel', {
87301  *         renderTo: Ext.getBody(),
87302  *         width: 175,
87303  *         height: 120,
87304  *         bodyPadding: 10,
87305  *         title: 'Final Score',
87306  *         items: [{
87307  *             xtype: 'displayfield',
87308  *             fieldLabel: 'Home',
87309  *             name: 'home_score',
87310  *             value: '10'
87311  *         }, {
87312  *             xtype: 'displayfield',
87313  *             fieldLabel: 'Visitor',
87314  *             name: 'visitor_score',
87315  *             value: '11'
87316  *         }],
87317  *         buttons: [{
87318  *             text: 'Update',
87319  *         }]
87320  *     });
87321  */
87322 Ext.define('Ext.form.field.Display', {
87323     extend:'Ext.form.field.Base',
87324     alias: 'widget.displayfield',
87325     requires: ['Ext.util.Format', 'Ext.XTemplate'],
87326     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
87327     fieldSubTpl: [
87328         '<div id="{id}" class="{fieldCls}"></div>',
87329         {
87330             compiled: true,
87331             disableFormats: true
87332         }
87333     ],
87334
87335     /**
87336      * @cfg {String} [fieldCls="x-form-display-field"]
87337      * The default CSS class for the field.
87338      */
87339     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
87340
87341     /**
87342      * @cfg {Boolean} htmlEncode
87343      * false to skip HTML-encoding the text when rendering it. This might be useful if you want to
87344      * include tags in the field's innerHTML rather than rendering them as string literals per the default logic.
87345      */
87346     htmlEncode: false,
87347
87348     validateOnChange: false,
87349
87350     initEvents: Ext.emptyFn,
87351
87352     submitValue: false,
87353
87354     isValid: function() {
87355         return true;
87356     },
87357
87358     validate: function() {
87359         return true;
87360     },
87361
87362     getRawValue: function() {
87363         return this.rawValue;
87364     },
87365
87366     setRawValue: function(value) {
87367         var me = this;
87368         value = Ext.value(value, '');
87369         me.rawValue = value;
87370         if (me.rendered) {
87371             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
87372         }
87373         return value;
87374     },
87375
87376     // private
87377     getContentTarget: function() {
87378         return this.inputEl;
87379     }
87380
87381     /**
87382      * @cfg {String} inputType
87383      * @hide
87384      */
87385     /**
87386      * @cfg {Boolean} disabled
87387      * @hide
87388      */
87389     /**
87390      * @cfg {Boolean} readOnly
87391      * @hide
87392      */
87393     /**
87394      * @cfg {Boolean} validateOnChange
87395      * @hide
87396      */
87397     /**
87398      * @cfg {Number} checkChangeEvents
87399      * @hide
87400      */
87401     /**
87402      * @cfg {Number} checkChangeBuffer
87403      * @hide
87404      */
87405 });
87406
87407 /**
87408  * @docauthor Jason Johnston <jason@sencha.com>
87409  *
87410  * A file upload field which has custom styling and allows control over the button text and other
87411  * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
87412  * It uses a hidden file input element behind the scenes to allow user selection of a file and to
87413  * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
87414  *
87415  * Because there is no secure cross-browser way to programmatically set the value of a file input,
87416  * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
87417  * a value that is browser-dependent; some have just the file name, some have a full path, some use
87418  * a fake path.
87419  *
87420  * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
87421  * {@link Ext.form.Basic#hasUpload} for details.
87422  *
87423  * # Example Usage
87424  *
87425  *     @example
87426  *     Ext.create('Ext.form.Panel', {
87427  *         title: 'Upload a Photo',
87428  *         width: 400,
87429  *         bodyPadding: 10,
87430  *         frame: true,
87431  *         renderTo: Ext.getBody(),
87432  *         items: [{
87433  *             xtype: 'filefield',
87434  *             name: 'photo',
87435  *             fieldLabel: 'Photo',
87436  *             labelWidth: 50,
87437  *             msgTarget: 'side',
87438  *             allowBlank: false,
87439  *             anchor: '100%',
87440  *             buttonText: 'Select Photo...'
87441  *         }],
87442  *
87443  *         buttons: [{
87444  *             text: 'Upload',
87445  *             handler: function() {
87446  *                 var form = this.up('form').getForm();
87447  *                 if(form.isValid()){
87448  *                     form.submit({
87449  *                         url: 'photo-upload.php',
87450  *                         waitMsg: 'Uploading your photo...',
87451  *                         success: function(fp, o) {
87452  *                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
87453  *                         }
87454  *                     });
87455  *                 }
87456  *             }
87457  *         }]
87458  *     });
87459  */
87460 Ext.define("Ext.form.field.File", {
87461     extend: 'Ext.form.field.Text',
87462     alias: ['widget.filefield', 'widget.fileuploadfield'],
87463     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
87464     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
87465
87466     /**
87467      * @cfg {String} buttonText
87468      * The button text to display on the upload button. Note that if you supply a value for
87469      * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
87470      */
87471     buttonText: 'Browse...',
87472
87473     /**
87474      * @cfg {Boolean} buttonOnly
87475      * True to display the file upload field as a button with no visible text field. If true, all
87476      * inherited Text members will still be available.
87477      */
87478     buttonOnly: false,
87479
87480     /**
87481      * @cfg {Number} buttonMargin
87482      * The number of pixels of space reserved between the button and the text field. Note that this only
87483      * applies if {@link #buttonOnly} = false.
87484      */
87485     buttonMargin: 3,
87486
87487     /**
87488      * @cfg {Object} buttonConfig
87489      * A standard {@link Ext.button.Button} config object.
87490      */
87491
87492     /**
87493      * @event change
87494      * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
87495      * file selection dialog.
87496      * @param {Ext.ux.form.FileUploadField} this
87497      * @param {String} value The file value returned by the underlying file input field
87498      */
87499
87500     /**
87501      * @property {Ext.Element} fileInputEl
87502      * A reference to the invisible file input element created for this upload field. Only populated after this
87503      * component is rendered.
87504      */
87505
87506     /**
87507      * @property {Ext.button.Button} button
87508      * A reference to the trigger Button component created for this upload field. Only populated after this component is
87509      * rendered.
87510      */
87511
87512     /**
87513      * @cfg {String} [fieldBodyCls='x-form-file-wrap']
87514      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
87515      */
87516     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
87517
87518     /**
87519      * @cfg {Boolean} readOnly
87520      * Unlike with other form fields, the readOnly config defaults to true in File field.
87521      */
87522     readOnly: true,
87523
87524     // private
87525     componentLayout: 'filefield',
87526
87527     // private
87528     onRender: function() {
87529         var me = this,
87530             inputEl;
87531
87532         me.callParent(arguments);
87533
87534         me.createButton();
87535         me.createFileInput();
87536         
87537         // we don't create the file/button til after onRender, the initial disable() is
87538         // called in the onRender of the component.
87539         if (me.disabled) {
87540             me.disableItems();
87541         }
87542
87543         inputEl = me.inputEl;
87544         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
87545         if (me.buttonOnly) {
87546             inputEl.setDisplayed(false);
87547         }
87548     },
87549
87550     /**
87551      * @private
87552      * Creates the custom trigger Button component. The fileInput will be inserted into this.
87553      */
87554     createButton: function() {
87555         var me = this;
87556         me.button = Ext.widget('button', Ext.apply({
87557             ui: me.ui,
87558             renderTo: me.bodyEl,
87559             text: me.buttonText,
87560             cls: Ext.baseCSSPrefix + 'form-file-btn',
87561             preventDefault: false,
87562             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
87563         }, me.buttonConfig));
87564     },
87565
87566     /**
87567      * @private
87568      * Creates the file input element. It is inserted into the trigger button component, made
87569      * invisible, and floated on top of the button's other content so that it will receive the
87570      * button's clicks.
87571      */
87572     createFileInput : function() {
87573         var me = this;
87574         me.fileInputEl = me.button.el.createChild({
87575             name: me.getName(),
87576             cls: Ext.baseCSSPrefix + 'form-file-input',
87577             tag: 'input',
87578             type: 'file',
87579             size: 1
87580         }).on('change', me.onFileChange, me);
87581     },
87582
87583     /**
87584      * @private Event handler fired when the user selects a file.
87585      */
87586     onFileChange: function() {
87587         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
87588         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
87589     },
87590
87591     /**
87592      * Overridden to do nothing
87593      * @hide
87594      */
87595     setValue: Ext.emptyFn,
87596
87597     reset : function(){
87598         var me = this;
87599         if (me.rendered) {
87600             me.fileInputEl.remove();
87601             me.createFileInput();
87602             me.inputEl.dom.value = '';
87603         }
87604         me.callParent();
87605     },
87606
87607     onDisable: function(){
87608         this.callParent();
87609         this.disableItems();
87610     },
87611     
87612     disableItems: function(){
87613         var file = this.fileInputEl,
87614             button = this.button;
87615              
87616         if (file) {
87617             file.dom.disabled = true;
87618         }
87619         if (button) {
87620             button.disable();
87621         }    
87622     },
87623
87624     onEnable: function(){
87625         var me = this;
87626         me.callParent();
87627         me.fileInputEl.dom.disabled = false;
87628         me.button.enable();
87629     },
87630
87631     isFileUpload: function() {
87632         return true;
87633     },
87634
87635     extractFileInput: function() {
87636         var fileInput = this.fileInputEl.dom;
87637         this.reset();
87638         return fileInput;
87639     },
87640
87641     onDestroy: function(){
87642         Ext.destroyMembers(this, 'fileInputEl', 'button');
87643         this.callParent();
87644     }
87645
87646
87647 });
87648
87649 /**
87650  * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
87651  *
87652  * This creates an actual input element with type="submit" in the DOM. While its label is
87653  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
87654  * to its owner container's layout.
87655  *
87656  * Because of this, in most cases it is more convenient and less problematic to simply
87657  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
87658  * {@link Ext.form.Basic#submit submitting the form}.
87659  *
87660  * Example:
87661  *
87662  *     new Ext.form.Panel({
87663  *         title: 'My Form',
87664  *         items: [{
87665  *             xtype: 'textfield',
87666  *             fieldLabel: 'Text Field',
87667  *             name: 'text_field',
87668  *             value: 'value from text field'
87669  *         }, {
87670  *             xtype: 'hiddenfield',
87671  *             name: 'hidden_field_1',
87672  *             value: 'value from hidden field'
87673  *         }],
87674  *
87675  *         buttons: [{
87676  *             text: 'Submit',
87677  *             handler: function() {
87678  *                 this.up('form').getForm().submit({
87679  *                     params: {
87680  *                         hidden_field_2: 'value from submit call'
87681  *                     }
87682  *                 });
87683  *             }
87684  *         }]
87685  *     });
87686  *
87687  * Submitting the above form will result in three values sent to the server:
87688  *
87689  *     text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
87690  *
87691  */
87692 Ext.define('Ext.form.field.Hidden', {
87693     extend:'Ext.form.field.Base',
87694     alias: ['widget.hiddenfield', 'widget.hidden'],
87695     alternateClassName: 'Ext.form.Hidden',
87696
87697     // private
87698     inputType : 'hidden',
87699     hideLabel: true,
87700     
87701     initComponent: function(){
87702         this.formItemCls += '-hidden';
87703         this.callParent();    
87704     },
87705     
87706     /**
87707      * @private
87708      * Override. Treat undefined and null values as equal to an empty string value.
87709      */
87710     isEqual: function(value1, value2) {
87711         return this.isEqualAsString(value1, value2);
87712     },
87713
87714     // These are all private overrides
87715     initEvents: Ext.emptyFn,
87716     setSize : Ext.emptyFn,
87717     setWidth : Ext.emptyFn,
87718     setHeight : Ext.emptyFn,
87719     setPosition : Ext.emptyFn,
87720     setPagePosition : Ext.emptyFn,
87721     markInvalid : Ext.emptyFn,
87722     clearInvalid : Ext.emptyFn
87723 });
87724
87725 /**
87726  * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
87727  * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
87728  *
87729  * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
87730  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
87731  *
87732  *     @example
87733  *     Ext.create('Ext.picker.Color', {
87734  *         value: '993300',  // initial selected color
87735  *         renderTo: Ext.getBody(),
87736  *         listeners: {
87737  *             select: function(picker, selColor) {
87738  *                 alert(selColor);
87739  *             }
87740  *         }
87741  *     });
87742  */
87743 Ext.define('Ext.picker.Color', {
87744     extend: 'Ext.Component',
87745     requires: 'Ext.XTemplate',
87746     alias: 'widget.colorpicker',
87747     alternateClassName: 'Ext.ColorPalette',
87748
87749     /**
87750      * @cfg {String} [componentCls='x-color-picker']
87751      * The CSS class to apply to the containing element.
87752      */
87753     componentCls : Ext.baseCSSPrefix + 'color-picker',
87754
87755     /**
87756      * @cfg {String} [selectedCls='x-color-picker-selected']
87757      * The CSS class to apply to the selected element
87758      */
87759     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
87760
87761     /**
87762      * @cfg {String} value
87763      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
87764      * codes are case-sensitive.
87765      */
87766     value : null,
87767
87768     /**
87769      * @cfg {String} clickEvent
87770      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
87771      */
87772     clickEvent :'click',
87773
87774     /**
87775      * @cfg {Boolean} allowReselect
87776      * If set to true then reselecting a color that is already selected fires the {@link #select} event
87777      */
87778     allowReselect : false,
87779
87780     /**
87781      * @property {String[]} colors
87782      * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
87783      * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
87784      * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
87785      * width setting until the box is symmetrical.
87786      *
87787      * You can override individual colors if needed:
87788      *
87789      *     var cp = new Ext.picker.Color();
87790      *     cp.colors[0] = 'FF0000';  // change the first box to red
87791      *
87792      * Or you can provide a custom array of your own for complete control:
87793      *
87794      *     var cp = new Ext.picker.Color();
87795      *     cp.colors = ['000000', '993300', '333300'];
87796      */
87797     colors : [
87798         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
87799         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
87800         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
87801         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
87802         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
87803     ],
87804
87805     /**
87806      * @cfg {Function} handler
87807      * A function that will handle the select event of this picker. The handler is passed the following parameters:
87808      *
87809      * - `picker` : ColorPicker
87810      *
87811      *   The {@link Ext.picker.Color picker}.
87812      *
87813      * - `color` : String
87814      *
87815      *   The 6-digit color hex code (without the # symbol).
87816      */
87817
87818     /**
87819      * @cfg {Object} scope
87820      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
87821      * Color picker instance.
87822      */
87823
87824     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
87825     
87826     renderTpl: [
87827         '<tpl for="colors">',
87828             '<a href="#" class="color-{.}" hidefocus="on">',
87829                 '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
87830             '</a>',
87831         '</tpl>'
87832     ],
87833
87834     // private
87835     initComponent : function(){
87836         var me = this;
87837
87838         me.callParent(arguments);
87839         me.addEvents(
87840             /**
87841              * @event select
87842              * Fires when a color is selected
87843              * @param {Ext.picker.Color} this
87844              * @param {String} color The 6-digit color hex code (without the # symbol)
87845              */
87846             'select'
87847         );
87848
87849         if (me.handler) {
87850             me.on('select', me.handler, me.scope, true);
87851         }
87852     },
87853
87854
87855     // private
87856     onRender : function(container, position){
87857         var me = this,
87858             clickEvent = me.clickEvent;
87859
87860         Ext.apply(me.renderData, {
87861             itemCls: me.itemCls,
87862             colors: me.colors
87863         });
87864         me.callParent(arguments);
87865
87866         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
87867         // always stop following the anchors
87868         if(clickEvent != 'click'){
87869             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
87870         }
87871     },
87872
87873     // private
87874     afterRender : function(){
87875         var me = this,
87876             value;
87877
87878         me.callParent(arguments);
87879         if (me.value) {
87880             value = me.value;
87881             me.value = null;
87882             me.select(value, true);
87883         }
87884     },
87885
87886     // private
87887     handleClick : function(event, target){
87888         var me = this,
87889             color;
87890
87891         event.stopEvent();
87892         if (!me.disabled) {
87893             color = target.className.match(me.colorRe)[1];
87894             me.select(color.toUpperCase());
87895         }
87896     },
87897
87898     /**
87899      * Selects the specified color in the picker (fires the {@link #select} event)
87900      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
87901      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
87902      */
87903     select : function(color, suppressEvent){
87904
87905         var me = this,
87906             selectedCls = me.selectedCls,
87907             value = me.value,
87908             el;
87909
87910         color = color.replace('#', '');
87911         if (!me.rendered) {
87912             me.value = color;
87913             return;
87914         }
87915
87916
87917         if (color != value || me.allowReselect) {
87918             el = me.el;
87919
87920             if (me.value) {
87921                 el.down('a.color-' + value).removeCls(selectedCls);
87922             }
87923             el.down('a.color-' + color).addCls(selectedCls);
87924             me.value = color;
87925             if (suppressEvent !== true) {
87926                 me.fireEvent('select', me, color);
87927             }
87928         }
87929     },
87930
87931     /**
87932      * Get the currently selected color value.
87933      * @return {String} value The selected value. Null if nothing is selected.
87934      */
87935     getValue: function(){
87936         return this.value || null;
87937     }
87938 });
87939
87940 /**
87941  * @private
87942  * @class Ext.layout.component.field.HtmlEditor
87943  * @extends Ext.layout.component.field.Field
87944  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
87945  * @private
87946  */
87947
87948 Ext.define('Ext.layout.component.field.HtmlEditor', {
87949     extend: 'Ext.layout.component.field.Field',
87950     alias: ['layout.htmleditor'],
87951
87952     type: 'htmleditor',
87953
87954     sizeBodyContents: function(width, height) {
87955         var me = this,
87956             owner = me.owner,
87957             bodyEl = owner.bodyEl,
87958             toolbar = owner.getToolbar(),
87959             textarea = owner.textareaEl,
87960             iframe = owner.iframeEl,
87961             editorHeight;
87962
87963         if (Ext.isNumber(width)) {
87964             width -= bodyEl.getFrameWidth('lr');
87965         }
87966         toolbar.setWidth(width);
87967         textarea.setWidth(width);
87968         iframe.setWidth(width);
87969
87970         // If fixed height, subtract toolbar height from the input area height
87971         if (Ext.isNumber(height)) {
87972             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
87973             textarea.setHeight(editorHeight);
87974             iframe.setHeight(editorHeight);
87975         }
87976     }
87977 });
87978 /**
87979  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
87980  * automatically hidden when needed. These are noted in the config options where appropriate.
87981  *
87982  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
87983  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
87984  * {@link Ext.tip.QuickTipManager#init initialized}.
87985  *
87986  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
87987  * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
87988  * default iframe reloading bugs.
87989  *
87990  * # Example usage
87991  *
87992  * Simple example rendered with default options:
87993  *
87994  *     @example
87995  *     Ext.tip.QuickTipManager.init();  // enable tooltips
87996  *     Ext.create('Ext.form.HtmlEditor', {
87997  *         width: 580,
87998  *         height: 250,
87999  *         renderTo: Ext.getBody()
88000  *     });
88001  *
88002  * Passed via xtype into a container and with custom options:
88003  *
88004  *     @example
88005  *     Ext.tip.QuickTipManager.init();  // enable tooltips
88006  *     new Ext.panel.Panel({
88007  *         title: 'HTML Editor',
88008  *         renderTo: Ext.getBody(),
88009  *         width: 550,
88010  *         height: 250,
88011  *         frame: true,
88012  *         layout: 'fit',
88013  *         items: {
88014  *             xtype: 'htmleditor',
88015  *             enableColors: false,
88016  *             enableAlignments: false
88017  *         }
88018  *     });
88019  */
88020 Ext.define('Ext.form.field.HtmlEditor', {
88021     extend:'Ext.Component',
88022     mixins: {
88023         labelable: 'Ext.form.Labelable',
88024         field: 'Ext.form.field.Field'
88025     },
88026     alias: 'widget.htmleditor',
88027     alternateClassName: 'Ext.form.HtmlEditor',
88028     requires: [
88029         'Ext.tip.QuickTipManager',
88030         'Ext.picker.Color',
88031         'Ext.toolbar.Item',
88032         'Ext.toolbar.Toolbar',
88033         'Ext.util.Format',
88034         'Ext.layout.component.field.HtmlEditor'
88035     ],
88036
88037     fieldSubTpl: [
88038         '<div id="{cmpId}-toolbarWrap" class="{toolbarWrapCls}"></div>',
88039         '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" class="{textareaCls}" ',
88040             'style="{size}" autocomplete="off"></textarea>',
88041         '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
88042         {
88043             compiled: true,
88044             disableFormats: true
88045         }
88046     ],
88047
88048     /**
88049      * @cfg {Boolean} enableFormat
88050      * Enable the bold, italic and underline buttons
88051      */
88052     enableFormat : true,
88053     /**
88054      * @cfg {Boolean} enableFontSize
88055      * Enable the increase/decrease font size buttons
88056      */
88057     enableFontSize : true,
88058     /**
88059      * @cfg {Boolean} enableColors
88060      * Enable the fore/highlight color buttons
88061      */
88062     enableColors : true,
88063     /**
88064      * @cfg {Boolean} enableAlignments
88065      * Enable the left, center, right alignment buttons
88066      */
88067     enableAlignments : true,
88068     /**
88069      * @cfg {Boolean} enableLists
88070      * Enable the bullet and numbered list buttons. Not available in Safari.
88071      */
88072     enableLists : true,
88073     /**
88074      * @cfg {Boolean} enableSourceEdit
88075      * Enable the switch to source edit button. Not available in Safari.
88076      */
88077     enableSourceEdit : true,
88078     /**
88079      * @cfg {Boolean} enableLinks
88080      * Enable the create link button. Not available in Safari.
88081      */
88082     enableLinks : true,
88083     /**
88084      * @cfg {Boolean} enableFont
88085      * Enable font selection. Not available in Safari.
88086      */
88087     enableFont : true,
88088     /**
88089      * @cfg {String} createLinkText
88090      * The default text for the create link prompt
88091      */
88092     createLinkText : 'Please enter the URL for the link:',
88093     /**
88094      * @cfg {String} [defaultLinkValue='http://']
88095      * The default value for the create link prompt
88096      */
88097     defaultLinkValue : 'http:/'+'/',
88098     /**
88099      * @cfg {String[]} fontFamilies
88100      * An array of available font families
88101      */
88102     fontFamilies : [
88103         'Arial',
88104         'Courier New',
88105         'Tahoma',
88106         'Times New Roman',
88107         'Verdana'
88108     ],
88109     defaultFont: 'tahoma',
88110     /**
88111      * @cfg {String} defaultValue
88112      * A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera
88113      * and IE6, â€‹(Zero-width space) in all other browsers).
88114      */
88115     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
88116
88117     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
88118
88119     componentLayout: 'htmleditor',
88120
88121     // private properties
88122     initialized : false,
88123     activated : false,
88124     sourceEditMode : false,
88125     iframePad:3,
88126     hideMode:'offsets',
88127
88128     maskOnDisable: true,
88129
88130     // private
88131     initComponent : function(){
88132         var me = this;
88133
88134         me.addEvents(
88135             /**
88136              * @event initialize
88137              * Fires when the editor is fully initialized (including the iframe)
88138              * @param {Ext.form.field.HtmlEditor} this
88139              */
88140             'initialize',
88141             /**
88142              * @event activate
88143              * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
88144              * @param {Ext.form.field.HtmlEditor} this
88145              */
88146             'activate',
88147              /**
88148              * @event beforesync
88149              * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
88150              * sync.
88151              * @param {Ext.form.field.HtmlEditor} this
88152              * @param {String} html
88153              */
88154             'beforesync',
88155              /**
88156              * @event beforepush
88157              * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
88158              * push.
88159              * @param {Ext.form.field.HtmlEditor} this
88160              * @param {String} html
88161              */
88162             'beforepush',
88163              /**
88164              * @event sync
88165              * Fires when the textarea is updated with content from the editor iframe.
88166              * @param {Ext.form.field.HtmlEditor} this
88167              * @param {String} html
88168              */
88169             'sync',
88170              /**
88171              * @event push
88172              * Fires when the iframe editor is updated with content from the textarea.
88173              * @param {Ext.form.field.HtmlEditor} this
88174              * @param {String} html
88175              */
88176             'push',
88177              /**
88178              * @event editmodechange
88179              * Fires when the editor switches edit modes
88180              * @param {Ext.form.field.HtmlEditor} this
88181              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
88182              */
88183             'editmodechange'
88184         );
88185
88186         me.callParent(arguments);
88187
88188         // Init mixins
88189         me.initLabelable();
88190         me.initField();
88191     },
88192
88193     /**
88194      * Called when the editor creates its toolbar. Override this method if you need to
88195      * add custom toolbar buttons.
88196      * @param {Ext.form.field.HtmlEditor} editor
88197      * @protected
88198      */
88199     createToolbar : function(editor){
88200         var me = this,
88201             items = [],
88202             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
88203             baseCSSPrefix = Ext.baseCSSPrefix,
88204             fontSelectItem, toolbar, undef;
88205
88206         function btn(id, toggle, handler){
88207             return {
88208                 itemId : id,
88209                 cls : baseCSSPrefix + 'btn-icon',
88210                 iconCls: baseCSSPrefix + 'edit-'+id,
88211                 enableToggle:toggle !== false,
88212                 scope: editor,
88213                 handler:handler||editor.relayBtnCmd,
88214                 clickEvent:'mousedown',
88215                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
88216                 overflowText: editor.buttonTips[id].title || undef,
88217                 tabIndex:-1
88218             };
88219         }
88220
88221
88222         if (me.enableFont && !Ext.isSafari2) {
88223             fontSelectItem = Ext.widget('component', {
88224                 renderTpl: [
88225                     '<select id="{id}-selectEl" class="{cls}">',
88226                         '<tpl for="fonts">',
88227                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
88228                         '</tpl>',
88229                     '</select>'
88230                 ],
88231                 renderData: {
88232                     cls: baseCSSPrefix + 'font-select',
88233                     fonts: me.fontFamilies,
88234                     defaultFont: me.defaultFont
88235                 },
88236                 childEls: ['selectEl'],
88237                 onDisable: function() {
88238                     var selectEl = this.selectEl;
88239                     if (selectEl) {
88240                         selectEl.dom.disabled = true;
88241                     }
88242                     Ext.Component.superclass.onDisable.apply(this, arguments);
88243                 },
88244                 onEnable: function() {
88245                     var selectEl = this.selectEl;
88246                     if (selectEl) {
88247                         selectEl.dom.disabled = false;
88248                     }
88249                     Ext.Component.superclass.onEnable.apply(this, arguments);
88250                 }
88251             });
88252
88253             items.push(
88254                 fontSelectItem,
88255                 '-'
88256             );
88257         }
88258
88259         if (me.enableFormat) {
88260             items.push(
88261                 btn('bold'),
88262                 btn('italic'),
88263                 btn('underline')
88264             );
88265         }
88266
88267         if (me.enableFontSize) {
88268             items.push(
88269                 '-',
88270                 btn('increasefontsize', false, me.adjustFont),
88271                 btn('decreasefontsize', false, me.adjustFont)
88272             );
88273         }
88274
88275         if (me.enableColors) {
88276             items.push(
88277                 '-', {
88278                     itemId: 'forecolor',
88279                     cls: baseCSSPrefix + 'btn-icon',
88280                     iconCls: baseCSSPrefix + 'edit-forecolor',
88281                     overflowText: editor.buttonTips.forecolor.title,
88282                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
88283                     tabIndex:-1,
88284                     menu : Ext.widget('menu', {
88285                         plain: true,
88286                         items: [{
88287                             xtype: 'colorpicker',
88288                             allowReselect: true,
88289                             focus: Ext.emptyFn,
88290                             value: '000000',
88291                             plain: true,
88292                             clickEvent: 'mousedown',
88293                             handler: function(cp, color) {
88294                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
88295                                 me.deferFocus();
88296                                 this.up('menu').hide();
88297                             }
88298                         }]
88299                     })
88300                 }, {
88301                     itemId: 'backcolor',
88302                     cls: baseCSSPrefix + 'btn-icon',
88303                     iconCls: baseCSSPrefix + 'edit-backcolor',
88304                     overflowText: editor.buttonTips.backcolor.title,
88305                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
88306                     tabIndex:-1,
88307                     menu : Ext.widget('menu', {
88308                         plain: true,
88309                         items: [{
88310                             xtype: 'colorpicker',
88311                             focus: Ext.emptyFn,
88312                             value: 'FFFFFF',
88313                             plain: true,
88314                             allowReselect: true,
88315                             clickEvent: 'mousedown',
88316                             handler: function(cp, color) {
88317                                 if (Ext.isGecko) {
88318                                     me.execCmd('useCSS', false);
88319                                     me.execCmd('hilitecolor', color);
88320                                     me.execCmd('useCSS', true);
88321                                     me.deferFocus();
88322                                 } else {
88323                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
88324                                     me.deferFocus();
88325                                 }
88326                                 this.up('menu').hide();
88327                             }
88328                         }]
88329                     })
88330                 }
88331             );
88332         }
88333
88334         if (me.enableAlignments) {
88335             items.push(
88336                 '-',
88337                 btn('justifyleft'),
88338                 btn('justifycenter'),
88339                 btn('justifyright')
88340             );
88341         }
88342
88343         if (!Ext.isSafari2) {
88344             if (me.enableLinks) {
88345                 items.push(
88346                     '-',
88347                     btn('createlink', false, me.createLink)
88348                 );
88349             }
88350
88351             if (me.enableLists) {
88352                 items.push(
88353                     '-',
88354                     btn('insertorderedlist'),
88355                     btn('insertunorderedlist')
88356                 );
88357             }
88358             if (me.enableSourceEdit) {
88359                 items.push(
88360                     '-',
88361                     btn('sourceedit', true, function(btn){
88362                         me.toggleSourceEdit(!me.sourceEditMode);
88363                     })
88364                 );
88365             }
88366         }
88367
88368         // build the toolbar
88369         toolbar = Ext.widget('toolbar', {
88370             renderTo: me.toolbarWrap,
88371             enableOverflow: true,
88372             items: items
88373         });
88374
88375         if (fontSelectItem) {
88376             me.fontSelect = fontSelectItem.selectEl;
88377
88378             me.mon(me.fontSelect, 'change', function(){
88379                 me.relayCmd('fontname', me.fontSelect.dom.value);
88380                 me.deferFocus();
88381             });
88382         }
88383
88384         // stop form submits
88385         me.mon(toolbar.el, 'click', function(e){
88386             e.preventDefault();
88387         });
88388
88389         me.toolbar = toolbar;
88390     },
88391
88392     onDisable: function() {
88393         this.bodyEl.mask();
88394         this.callParent(arguments);
88395     },
88396
88397     onEnable: function() {
88398         this.bodyEl.unmask();
88399         this.callParent(arguments);
88400     },
88401
88402     /**
88403      * Sets the read only state of this field.
88404      * @param {Boolean} readOnly Whether the field should be read only.
88405      */
88406     setReadOnly: function(readOnly) {
88407         var me = this,
88408             textareaEl = me.textareaEl,
88409             iframeEl = me.iframeEl,
88410             body;
88411
88412         me.readOnly = readOnly;
88413
88414         if (textareaEl) {
88415             textareaEl.dom.readOnly = readOnly;
88416         }
88417
88418         if (me.initialized) {
88419             body = me.getEditorBody();
88420             if (Ext.isIE) {
88421                 // Hide the iframe while setting contentEditable so it doesn't grab focus
88422                 iframeEl.setDisplayed(false);
88423                 body.contentEditable = !readOnly;
88424                 iframeEl.setDisplayed(true);
88425             } else {
88426                 me.setDesignMode(!readOnly);
88427             }
88428             if (body) {
88429                 body.style.cursor = readOnly ? 'default' : 'text';
88430             }
88431             me.disableItems(readOnly);
88432         }
88433     },
88434
88435     /**
88436      * Called when the editor initializes the iframe with HTML contents. Override this method if you
88437      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
88438      *
88439      * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
88440      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
88441      * Developer Tools to manually set the document mode, that will take precedence and override what this
88442      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
88443      * @protected
88444      */
88445     getDocMarkup: function() {
88446         var me = this,
88447             h = me.iframeEl.getHeight() - me.iframePad * 2;
88448         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);
88449     },
88450
88451     // private
88452     getEditorBody: function() {
88453         var doc = this.getDoc();
88454         return doc.body || doc.documentElement;
88455     },
88456
88457     // private
88458     getDoc: function() {
88459         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
88460     },
88461
88462     // private
88463     getWin: function() {
88464         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
88465     },
88466
88467     // private
88468     onRender: function() {
88469         var me = this;
88470
88471         me.onLabelableRender();
88472
88473         me.addChildEls('toolbarWrap', 'iframeEl', 'textareaEl');
88474
88475         me.callParent(arguments);
88476
88477         me.textareaEl.dom.value = me.value || '';
88478
88479         // Start polling for when the iframe document is ready to be manipulated
88480         me.monitorTask = Ext.TaskManager.start({
88481             run: me.checkDesignMode,
88482             scope: me,
88483             interval:100
88484         });
88485
88486         me.createToolbar(me);
88487         me.disableItems(true);
88488     },
88489
88490     initRenderTpl: function() {
88491         var me = this;
88492         if (!me.hasOwnProperty('renderTpl')) {
88493             me.renderTpl = me.getTpl('labelableRenderTpl');
88494         }
88495         return me.callParent();
88496     },
88497
88498     initRenderData: function() {
88499         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
88500     },
88501
88502     getSubTplData: function() {
88503         var cssPrefix = Ext.baseCSSPrefix;
88504         return {
88505             cmpId: this.id,
88506             id: this.getInputId(),
88507             toolbarWrapCls: cssPrefix + 'html-editor-tb',
88508             textareaCls: cssPrefix + 'hidden',
88509             iframeName: Ext.id(),
88510             iframeSrc: Ext.SSL_SECURE_URL,
88511             size: 'height:100px;'
88512         };
88513     },
88514
88515     getSubTplMarkup: function() {
88516         var data = this.getSubTplData();
88517         return this.getTpl('fieldSubTpl').apply(data);
88518     },
88519
88520     getBodyNaturalWidth: function() {
88521         return 565;
88522     },
88523
88524     initFrameDoc: function() {
88525         var me = this,
88526             doc, task;
88527
88528         Ext.TaskManager.stop(me.monitorTask);
88529
88530         doc = me.getDoc();
88531         me.win = me.getWin();
88532
88533         doc.open();
88534         doc.write(me.getDocMarkup());
88535         doc.close();
88536
88537         task = { // must defer to wait for browser to be ready
88538             run: function() {
88539                 var doc = me.getDoc();
88540                 if (doc.body || doc.readyState === 'complete') {
88541                     Ext.TaskManager.stop(task);
88542                     me.setDesignMode(true);
88543                     Ext.defer(me.initEditor, 10, me);
88544                 }
88545             },
88546             interval : 10,
88547             duration:10000,
88548             scope: me
88549         };
88550         Ext.TaskManager.start(task);
88551     },
88552
88553     checkDesignMode: function() {
88554         var me = this,
88555             doc = me.getDoc();
88556         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
88557             me.initFrameDoc();
88558         }
88559     },
88560
88561     /**
88562      * @private
88563      * Sets current design mode. To enable, mode can be true or 'on', off otherwise
88564      */
88565     setDesignMode: function(mode) {
88566         var me = this,
88567             doc = me.getDoc();
88568         if (doc) {
88569             if (me.readOnly) {
88570                 mode = false;
88571             }
88572             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
88573         }
88574     },
88575
88576     // private
88577     getDesignMode: function() {
88578         var doc = this.getDoc();
88579         return !doc ? '' : String(doc.designMode).toLowerCase();
88580     },
88581
88582     disableItems: function(disabled) {
88583         this.getToolbar().items.each(function(item){
88584             if(item.getItemId() !== 'sourceedit'){
88585                 item.setDisabled(disabled);
88586             }
88587         });
88588     },
88589
88590     /**
88591      * Toggles the editor between standard and source edit mode.
88592      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
88593      */
88594     toggleSourceEdit: function(sourceEditMode) {
88595         var me = this,
88596             iframe = me.iframeEl,
88597             textarea = me.textareaEl,
88598             hiddenCls = Ext.baseCSSPrefix + 'hidden',
88599             btn = me.getToolbar().getComponent('sourceedit');
88600
88601         if (!Ext.isBoolean(sourceEditMode)) {
88602             sourceEditMode = !me.sourceEditMode;
88603         }
88604         me.sourceEditMode = sourceEditMode;
88605
88606         if (btn.pressed !== sourceEditMode) {
88607             btn.toggle(sourceEditMode);
88608         }
88609         if (sourceEditMode) {
88610             me.disableItems(true);
88611             me.syncValue();
88612             iframe.addCls(hiddenCls);
88613             textarea.removeCls(hiddenCls);
88614             textarea.dom.removeAttribute('tabIndex');
88615             textarea.focus();
88616         }
88617         else {
88618             if (me.initialized) {
88619                 me.disableItems(me.readOnly);
88620             }
88621             me.pushValue();
88622             iframe.removeCls(hiddenCls);
88623             textarea.addCls(hiddenCls);
88624             textarea.dom.setAttribute('tabIndex', -1);
88625             me.deferFocus();
88626         }
88627         me.fireEvent('editmodechange', me, sourceEditMode);
88628         me.doComponentLayout();
88629     },
88630
88631     // private used internally
88632     createLink : function() {
88633         var url = prompt(this.createLinkText, this.defaultLinkValue);
88634         if (url && url !== 'http:/'+'/') {
88635             this.relayCmd('createlink', url);
88636         }
88637     },
88638
88639     clearInvalid: Ext.emptyFn,
88640
88641     // docs inherit from Field
88642     setValue: function(value) {
88643         var me = this,
88644             textarea = me.textareaEl;
88645         me.mixins.field.setValue.call(me, value);
88646         if (value === null || value === undefined) {
88647             value = '';
88648         }
88649         if (textarea) {
88650             textarea.dom.value = value;
88651         }
88652         me.pushValue();
88653         return this;
88654     },
88655
88656     /**
88657      * If you need/want custom HTML cleanup, this is the method you should override.
88658      * @param {String} html The HTML to be cleaned
88659      * @return {String} The cleaned HTML
88660      * @protected
88661      */
88662     cleanHtml: function(html) {
88663         html = String(html);
88664         if (Ext.isWebKit) { // strip safari nonsense
88665             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
88666         }
88667
88668         /*
88669          * Neat little hack. Strips out all the non-digit characters from the default
88670          * value and compares it to the character code of the first character in the string
88671          * because it can cause encoding issues when posted to the server.
88672          */
88673         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
88674             html = html.substring(1);
88675         }
88676         return html;
88677     },
88678
88679     /**
88680      * Syncs the contents of the editor iframe with the textarea.
88681      * @protected
88682      */
88683     syncValue : function(){
88684         var me = this,
88685             body, html, bodyStyle, match;
88686         if (me.initialized) {
88687             body = me.getEditorBody();
88688             html = body.innerHTML;
88689             if (Ext.isWebKit) {
88690                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
88691                 match = bodyStyle.match(/text-align:(.*?);/i);
88692                 if (match && match[1]) {
88693                     html = '<div style="' + match[0] + '">' + html + '</div>';
88694                 }
88695             }
88696             html = me.cleanHtml(html);
88697             if (me.fireEvent('beforesync', me, html) !== false) {
88698                 me.textareaEl.dom.value = html;
88699                 me.fireEvent('sync', me, html);
88700             }
88701         }
88702     },
88703
88704     //docs inherit from Field
88705     getValue : function() {
88706         var me = this,
88707             value;
88708         if (!me.sourceEditMode) {
88709             me.syncValue();
88710         }
88711         value = me.rendered ? me.textareaEl.dom.value : me.value;
88712         me.value = value;
88713         return value;
88714     },
88715
88716     /**
88717      * Pushes the value of the textarea into the iframe editor.
88718      * @protected
88719      */
88720     pushValue: function() {
88721         var me = this,
88722             v;
88723         if(me.initialized){
88724             v = me.textareaEl.dom.value || '';
88725             if (!me.activated && v.length < 1) {
88726                 v = me.defaultValue;
88727             }
88728             if (me.fireEvent('beforepush', me, v) !== false) {
88729                 me.getEditorBody().innerHTML = v;
88730                 if (Ext.isGecko) {
88731                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
88732                     me.setDesignMode(false);  //toggle off first
88733                     me.setDesignMode(true);
88734                 }
88735                 me.fireEvent('push', me, v);
88736             }
88737         }
88738     },
88739
88740     // private
88741     deferFocus : function(){
88742          this.focus(false, true);
88743     },
88744
88745     getFocusEl: function() {
88746         var me = this,
88747             win = me.win;
88748         return win && !me.sourceEditMode ? win : me.textareaEl;
88749     },
88750
88751     // private
88752     initEditor : function(){
88753         //Destroying the component during/before initEditor can cause issues.
88754         try {
88755             var me = this,
88756                 dbody = me.getEditorBody(),
88757                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
88758                 doc,
88759                 fn;
88760
88761             ss['background-attachment'] = 'fixed'; // w3c
88762             dbody.bgProperties = 'fixed'; // ie
88763
88764             Ext.DomHelper.applyStyles(dbody, ss);
88765
88766             doc = me.getDoc();
88767
88768             if (doc) {
88769                 try {
88770                     Ext.EventManager.removeAll(doc);
88771                 } catch(e) {}
88772             }
88773
88774             /*
88775              * We need to use createDelegate here, because when using buffer, the delayed task is added
88776              * as a property to the function. When the listener is removed, the task is deleted from the function.
88777              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
88778              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
88779              */
88780             fn = Ext.Function.bind(me.onEditorEvent, me);
88781             Ext.EventManager.on(doc, {
88782                 mousedown: fn,
88783                 dblclick: fn,
88784                 click: fn,
88785                 keyup: fn,
88786                 buffer:100
88787             });
88788
88789             // These events need to be relayed from the inner document (where they stop
88790             // bubbling) up to the outer document. This has to be done at the DOM level so
88791             // the event reaches listeners on elements like the document body. The effected
88792             // mechanisms that depend on this bubbling behavior are listed to the right
88793             // of the event.
88794             fn = me.onRelayedEvent;
88795             Ext.EventManager.on(doc, {
88796                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
88797                 mousemove: fn, // window resize drag detection
88798                 mouseup: fn,   // window resize termination
88799                 click: fn,     // not sure, but just to be safe
88800                 dblclick: fn,  // not sure again
88801                 scope: me
88802             });
88803
88804             if (Ext.isGecko) {
88805                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
88806             }
88807             if (me.fixKeys) {
88808                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
88809             }
88810
88811             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
88812             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
88813             doc.editorInitialized = true;
88814
88815             me.initialized = true;
88816             me.pushValue();
88817             me.setReadOnly(me.readOnly);
88818             me.fireEvent('initialize', me);
88819         } catch(ex) {
88820             // ignore (why?)
88821         }
88822     },
88823
88824     // private
88825     beforeDestroy : function(){
88826         var me = this,
88827             monitorTask = me.monitorTask,
88828             doc, prop;
88829
88830         if (monitorTask) {
88831             Ext.TaskManager.stop(monitorTask);
88832         }
88833         if (me.rendered) {
88834             try {
88835                 doc = me.getDoc();
88836                 if (doc) {
88837                     Ext.EventManager.removeAll(doc);
88838                     for (prop in doc) {
88839                         if (doc.hasOwnProperty(prop)) {
88840                             delete doc[prop];
88841                         }
88842                     }
88843                 }
88844             } catch(e) {
88845                 // ignore (why?)
88846             }
88847             Ext.destroyMembers(me, 'tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
88848         }
88849         me.callParent();
88850     },
88851
88852     // private
88853     onRelayedEvent: function (event) {
88854         // relay event from the iframe's document to the document that owns the iframe...
88855
88856         var iframeEl = this.iframeEl,
88857             iframeXY = iframeEl.getXY(),
88858             eventXY = event.getXY();
88859
88860         // the event from the inner document has XY relative to that document's origin,
88861         // so adjust it to use the origin of the iframe in the outer document:
88862         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
88863
88864         event.injectEvent(iframeEl); // blame the iframe for the event...
88865
88866         event.xy = eventXY; // restore the original XY (just for safety)
88867     },
88868
88869     // private
88870     onFirstFocus : function(){
88871         var me = this,
88872             selection, range;
88873         me.activated = true;
88874         me.disableItems(me.readOnly);
88875         if (Ext.isGecko) { // prevent silly gecko errors
88876             me.win.focus();
88877             selection = me.win.getSelection();
88878             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
88879                 range = selection.getRangeAt(0);
88880                 range.selectNodeContents(me.getEditorBody());
88881                 range.collapse(true);
88882                 me.deferFocus();
88883             }
88884             try {
88885                 me.execCmd('useCSS', true);
88886                 me.execCmd('styleWithCSS', false);
88887             } catch(e) {
88888                 // ignore (why?)
88889             }
88890         }
88891         me.fireEvent('activate', me);
88892     },
88893
88894     // private
88895     adjustFont: function(btn) {
88896         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
88897             size = this.getDoc().queryCommandValue('FontSize') || '2',
88898             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
88899             isSafari;
88900         size = parseInt(size, 10);
88901         if (isPxSize) {
88902             // Safari 3 values
88903             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
88904             if (size <= 10) {
88905                 size = 1 + adjust;
88906             }
88907             else if (size <= 13) {
88908                 size = 2 + adjust;
88909             }
88910             else if (size <= 16) {
88911                 size = 3 + adjust;
88912             }
88913             else if (size <= 18) {
88914                 size = 4 + adjust;
88915             }
88916             else if (size <= 24) {
88917                 size = 5 + adjust;
88918             }
88919             else {
88920                 size = 6 + adjust;
88921             }
88922             size = Ext.Number.constrain(size, 1, 6);
88923         } else {
88924             isSafari = Ext.isSafari;
88925             if (isSafari) { // safari
88926                 adjust *= 2;
88927             }
88928             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
88929         }
88930         this.execCmd('FontSize', size);
88931     },
88932
88933     // private
88934     onEditorEvent: function(e) {
88935         this.updateToolbar();
88936     },
88937
88938     /**
88939      * Triggers a toolbar update by reading the markup state of the current selection in the editor.
88940      * @protected
88941      */
88942     updateToolbar: function() {
88943         var me = this,
88944             btns, doc, name, fontSelect;
88945
88946         if (me.readOnly) {
88947             return;
88948         }
88949
88950         if (!me.activated) {
88951             me.onFirstFocus();
88952             return;
88953         }
88954
88955         btns = me.getToolbar().items.map;
88956         doc = me.getDoc();
88957
88958         if (me.enableFont && !Ext.isSafari2) {
88959             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
88960             fontSelect = me.fontSelect.dom;
88961             if (name !== fontSelect.value) {
88962                 fontSelect.value = name;
88963             }
88964         }
88965
88966         function updateButtons() {
88967             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
88968                 btns[name].toggle(doc.queryCommandState(name));
88969             });
88970         }
88971         if(me.enableFormat){
88972             updateButtons('bold', 'italic', 'underline');
88973         }
88974         if(me.enableAlignments){
88975             updateButtons('justifyleft', 'justifycenter', 'justifyright');
88976         }
88977         if(!Ext.isSafari2 && me.enableLists){
88978             updateButtons('insertorderedlist', 'insertunorderedlist');
88979         }
88980
88981         Ext.menu.Manager.hideAll();
88982
88983         me.syncValue();
88984     },
88985
88986     // private
88987     relayBtnCmd: function(btn) {
88988         this.relayCmd(btn.getItemId());
88989     },
88990
88991     /**
88992      * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
88993      * **This should only be called after the editor is initialized.**
88994      * @param {String} cmd The Midas command
88995      * @param {String/Boolean} [value=null] The value to pass to the command
88996      */
88997     relayCmd: function(cmd, value) {
88998         Ext.defer(function() {
88999             var me = this;
89000             me.focus();
89001             me.execCmd(cmd, value);
89002             me.updateToolbar();
89003         }, 10, this);
89004     },
89005
89006     /**
89007      * Executes a Midas editor command directly on the editor document. For visual commands, you should use
89008      * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
89009      * @param {String} cmd The Midas command
89010      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
89011      */
89012     execCmd : function(cmd, value){
89013         var me = this,
89014             doc = me.getDoc(),
89015             undef;
89016         doc.execCommand(cmd, false, value === undef ? null : value);
89017         me.syncValue();
89018     },
89019
89020     // private
89021     applyCommand : function(e){
89022         if (e.ctrlKey) {
89023             var me = this,
89024                 c = e.getCharCode(), cmd;
89025             if (c > 0) {
89026                 c = String.fromCharCode(c);
89027                 switch (c) {
89028                     case 'b':
89029                         cmd = 'bold';
89030                     break;
89031                     case 'i':
89032                         cmd = 'italic';
89033                     break;
89034                     case 'u':
89035                         cmd = 'underline';
89036                     break;
89037                 }
89038                 if (cmd) {
89039                     me.win.focus();
89040                     me.execCmd(cmd);
89041                     me.deferFocus();
89042                     e.preventDefault();
89043                 }
89044             }
89045         }
89046     },
89047
89048     /**
89049      * Inserts the passed text at the current cursor position.
89050      * Note: the editor must be initialized and activated to insert text.
89051      * @param {String} text
89052      */
89053     insertAtCursor : function(text){
89054         var me = this,
89055             range;
89056
89057         if (me.activated) {
89058             me.win.focus();
89059             if (Ext.isIE) {
89060                 range = me.getDoc().selection.createRange();
89061                 if (range) {
89062                     range.pasteHTML(text);
89063                     me.syncValue();
89064                     me.deferFocus();
89065                 }
89066             }else{
89067                 me.execCmd('InsertHTML', text);
89068                 me.deferFocus();
89069             }
89070         }
89071     },
89072
89073     // private
89074     fixKeys: function() { // load time branching for fastest keydown performance
89075         if (Ext.isIE) {
89076             return function(e){
89077                 var me = this,
89078                     k = e.getKey(),
89079                     doc = me.getDoc(),
89080                     range, target;
89081                 if (k === e.TAB) {
89082                     e.stopEvent();
89083                     range = doc.selection.createRange();
89084                     if(range){
89085                         range.collapse(true);
89086                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
89087                         me.deferFocus();
89088                     }
89089                 }
89090                 else if (k === e.ENTER) {
89091                     range = doc.selection.createRange();
89092                     if (range) {
89093                         target = range.parentElement();
89094                         if(!target || target.tagName.toLowerCase() !== 'li'){
89095                             e.stopEvent();
89096                             range.pasteHTML('<br />');
89097                             range.collapse(false);
89098                             range.select();
89099                         }
89100                     }
89101                 }
89102             };
89103         }
89104
89105         if (Ext.isOpera) {
89106             return function(e){
89107                 var me = this;
89108                 if (e.getKey() === e.TAB) {
89109                     e.stopEvent();
89110                     me.win.focus();
89111                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
89112                     me.deferFocus();
89113                 }
89114             };
89115         }
89116
89117         if (Ext.isWebKit) {
89118             return function(e){
89119                 var me = this,
89120                     k = e.getKey();
89121                 if (k === e.TAB) {
89122                     e.stopEvent();
89123                     me.execCmd('InsertText','\t');
89124                     me.deferFocus();
89125                 }
89126                 else if (k === e.ENTER) {
89127                     e.stopEvent();
89128                     me.execCmd('InsertHtml','<br /><br />');
89129                     me.deferFocus();
89130                 }
89131             };
89132         }
89133
89134         return null; // not needed, so null
89135     }(),
89136
89137     /**
89138      * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
89139      * @return {Ext.toolbar.Toolbar}
89140      */
89141     getToolbar : function(){
89142         return this.toolbar;
89143     },
89144
89145     /**
89146      * @property {Object} buttonTips
89147      * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
89148      * that button and the value is a valid QuickTips object. For example:
89149      *
89150      *     {
89151      *         bold : {
89152      *             title: 'Bold (Ctrl+B)',
89153      *             text: 'Make the selected text bold.',
89154      *             cls: 'x-html-editor-tip'
89155      *         },
89156      *         italic : {
89157      *             title: 'Italic (Ctrl+I)',
89158      *             text: 'Make the selected text italic.',
89159      *             cls: 'x-html-editor-tip'
89160      *         },
89161      *         ...
89162      */
89163     buttonTips : {
89164         bold : {
89165             title: 'Bold (Ctrl+B)',
89166             text: 'Make the selected text bold.',
89167             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89168         },
89169         italic : {
89170             title: 'Italic (Ctrl+I)',
89171             text: 'Make the selected text italic.',
89172             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89173         },
89174         underline : {
89175             title: 'Underline (Ctrl+U)',
89176             text: 'Underline the selected text.',
89177             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89178         },
89179         increasefontsize : {
89180             title: 'Grow Text',
89181             text: 'Increase the font size.',
89182             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89183         },
89184         decreasefontsize : {
89185             title: 'Shrink Text',
89186             text: 'Decrease the font size.',
89187             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89188         },
89189         backcolor : {
89190             title: 'Text Highlight Color',
89191             text: 'Change the background color of the selected text.',
89192             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89193         },
89194         forecolor : {
89195             title: 'Font Color',
89196             text: 'Change the color of the selected text.',
89197             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89198         },
89199         justifyleft : {
89200             title: 'Align Text Left',
89201             text: 'Align text to the left.',
89202             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89203         },
89204         justifycenter : {
89205             title: 'Center Text',
89206             text: 'Center text in the editor.',
89207             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89208         },
89209         justifyright : {
89210             title: 'Align Text Right',
89211             text: 'Align text to the right.',
89212             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89213         },
89214         insertunorderedlist : {
89215             title: 'Bullet List',
89216             text: 'Start a bulleted list.',
89217             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89218         },
89219         insertorderedlist : {
89220             title: 'Numbered List',
89221             text: 'Start a numbered list.',
89222             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89223         },
89224         createlink : {
89225             title: 'Hyperlink',
89226             text: 'Make the selected text a hyperlink.',
89227             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89228         },
89229         sourceedit : {
89230             title: 'Source Edit',
89231             text: 'Switch to source editing mode.',
89232             cls: Ext.baseCSSPrefix + 'html-editor-tip'
89233         }
89234     }
89235
89236     // hide stuff that is not compatible
89237     /**
89238      * @event blur
89239      * @hide
89240      */
89241     /**
89242      * @event change
89243      * @hide
89244      */
89245     /**
89246      * @event focus
89247      * @hide
89248      */
89249     /**
89250      * @event specialkey
89251      * @hide
89252      */
89253     /**
89254      * @cfg {String} fieldCls @hide
89255      */
89256     /**
89257      * @cfg {String} focusCls @hide
89258      */
89259     /**
89260      * @cfg {String} autoCreate @hide
89261      */
89262     /**
89263      * @cfg {String} inputType @hide
89264      */
89265     /**
89266      * @cfg {String} invalidCls @hide
89267      */
89268     /**
89269      * @cfg {String} invalidText @hide
89270      */
89271     /**
89272      * @cfg {String} msgFx @hide
89273      */
89274     /**
89275      * @cfg {Boolean} allowDomMove @hide
89276      */
89277     /**
89278      * @cfg {String} applyTo @hide
89279      */
89280     /**
89281      * @cfg {String} readOnly  @hide
89282      */
89283     /**
89284      * @cfg {String} tabIndex  @hide
89285      */
89286     /**
89287      * @method validate
89288      * @hide
89289      */
89290 });
89291
89292 /**
89293  * @docauthor Robert Dougan <rob@sencha.com>
89294  *
89295  * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
89296  * at a time within a group of radios with the same name.
89297  *
89298  * # Labeling
89299  *
89300  * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
89301  * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
89302  * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
89303  *
89304  * # Values
89305  *
89306  * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
89307  *
89308  * The following values will check the radio:
89309  *
89310  * - `true`
89311  * - `'true'`
89312  * - `'1'`
89313  * - `'on'`
89314  *
89315  * Any other value will uncheck it.
89316  *
89317  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
89318  * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
89319  * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
89320  *
89321  * # Example usage
89322  *
89323  *     @example
89324  *     Ext.create('Ext.form.Panel', {
89325  *         title      : 'Order Form',
89326  *         width      : 300,
89327  *         bodyPadding: 10,
89328  *         renderTo   : Ext.getBody(),
89329  *         items: [
89330  *             {
89331  *                 xtype      : 'fieldcontainer',
89332  *                 fieldLabel : 'Size',
89333  *                 defaultType: 'radiofield',
89334  *                 defaults: {
89335  *                     flex: 1
89336  *                 },
89337  *                 layout: 'hbox',
89338  *                 items: [
89339  *                     {
89340  *                         boxLabel  : 'M',
89341  *                         name      : 'size',
89342  *                         inputValue: 'm',
89343  *                         id        : 'radio1'
89344  *                     }, {
89345  *                         boxLabel  : 'L',
89346  *                         name      : 'size',
89347  *                         inputValue: 'l',
89348  *                         id        : 'radio2'
89349  *                     }, {
89350  *                         boxLabel  : 'XL',
89351  *                         name      : 'size',
89352  *                         inputValue: 'xl',
89353  *                         id        : 'radio3'
89354  *                     }
89355  *                 ]
89356  *             },
89357  *             {
89358  *                 xtype      : 'fieldcontainer',
89359  *                 fieldLabel : 'Color',
89360  *                 defaultType: 'radiofield',
89361  *                 defaults: {
89362  *                     flex: 1
89363  *                 },
89364  *                 layout: 'hbox',
89365  *                 items: [
89366  *                     {
89367  *                         boxLabel  : 'Blue',
89368  *                         name      : 'color',
89369  *                         inputValue: 'blue',
89370  *                         id        : 'radio4'
89371  *                     }, {
89372  *                         boxLabel  : 'Grey',
89373  *                         name      : 'color',
89374  *                         inputValue: 'grey',
89375  *                         id        : 'radio5'
89376  *                     }, {
89377  *                         boxLabel  : 'Black',
89378  *                         name      : 'color',
89379  *                         inputValue: 'black',
89380  *                         id        : 'radio6'
89381  *                     }
89382  *                 ]
89383  *             }
89384  *         ],
89385  *         bbar: [
89386  *             {
89387  *                 text: 'Smaller Size',
89388  *                 handler: function() {
89389  *                     var radio1 = Ext.getCmp('radio1'),
89390  *                         radio2 = Ext.getCmp('radio2'),
89391  *                         radio3 = Ext.getCmp('radio3');
89392  *
89393  *                     //if L is selected, change to M
89394  *                     if (radio2.getValue()) {
89395  *                         radio1.setValue(true);
89396  *                         return;
89397  *                     }
89398  *
89399  *                     //if XL is selected, change to L
89400  *                     if (radio3.getValue()) {
89401  *                         radio2.setValue(true);
89402  *                         return;
89403  *                     }
89404  *
89405  *                     //if nothing is set, set size to S
89406  *                     radio1.setValue(true);
89407  *                 }
89408  *             },
89409  *             {
89410  *                 text: 'Larger Size',
89411  *                 handler: function() {
89412  *                     var radio1 = Ext.getCmp('radio1'),
89413  *                         radio2 = Ext.getCmp('radio2'),
89414  *                         radio3 = Ext.getCmp('radio3');
89415  *
89416  *                     //if M is selected, change to L
89417  *                     if (radio1.getValue()) {
89418  *                         radio2.setValue(true);
89419  *                         return;
89420  *                     }
89421  *
89422  *                     //if L is selected, change to XL
89423  *                     if (radio2.getValue()) {
89424  *                         radio3.setValue(true);
89425  *                         return;
89426  *                     }
89427  *
89428  *                     //if nothing is set, set size to XL
89429  *                     radio3.setValue(true);
89430  *                 }
89431  *             },
89432  *             '-',
89433  *             {
89434  *                 text: 'Select color',
89435  *                 menu: {
89436  *                     indent: false,
89437  *                     items: [
89438  *                         {
89439  *                             text: 'Blue',
89440  *                             handler: function() {
89441  *                                 var radio = Ext.getCmp('radio4');
89442  *                                 radio.setValue(true);
89443  *                             }
89444  *                         },
89445  *                         {
89446  *                             text: 'Grey',
89447  *                             handler: function() {
89448  *                                 var radio = Ext.getCmp('radio5');
89449  *                                 radio.setValue(true);
89450  *                             }
89451  *                         },
89452  *                         {
89453  *                             text: 'Black',
89454  *                             handler: function() {
89455  *                                 var radio = Ext.getCmp('radio6');
89456  *                                 radio.setValue(true);
89457  *                             }
89458  *                         }
89459  *                     ]
89460  *                 }
89461  *             }
89462  *         ]
89463  *     });
89464  */
89465 Ext.define('Ext.form.field.Radio', {
89466     extend:'Ext.form.field.Checkbox',
89467     alias: ['widget.radiofield', 'widget.radio'],
89468     alternateClassName: 'Ext.form.Radio',
89469     requires: ['Ext.form.RadioManager'],
89470
89471     isRadio: true,
89472
89473     /**
89474      * @cfg {String} uncheckedValue @hide
89475      */
89476
89477     // private
89478     inputType: 'radio',
89479     ariaRole: 'radio',
89480
89481     /**
89482      * If this radio is part of a group, it will return the selected value
89483      * @return {String}
89484      */
89485     getGroupValue: function() {
89486         var selected = this.getManager().getChecked(this.name);
89487         return selected ? selected.inputValue : null;
89488     },
89489
89490     /**
89491      * @private Handle click on the radio button
89492      */
89493     onBoxClick: function(e) {
89494         var me = this;
89495         if (!me.disabled && !me.readOnly) {
89496             this.setValue(true);
89497         }
89498     },
89499
89500     /**
89501      * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
89502      * of the same name whose value is the value specified.
89503      * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
89504      * @return {Ext.form.field.Radio} this
89505      */
89506     setValue: function(v) {
89507         var me = this,
89508             active;
89509
89510         if (Ext.isBoolean(v)) {
89511             me.callParent(arguments);
89512         } else {
89513             active = me.getManager().getWithValue(me.name, v).getAt(0);
89514             if (active) {
89515                 active.setValue(true);
89516             }
89517         }
89518         return me;
89519     },
89520
89521     /**
89522      * Returns the submit value for the checkbox which can be used when submitting forms.
89523      * @return {Boolean/Object} True if checked, null if not.
89524      */
89525     getSubmitValue: function() {
89526         return this.checked ? this.inputValue : null;
89527     },
89528
89529     getModelData: function() {
89530         return this.getSubmitData();
89531     },
89532
89533     // inherit docs
89534     onChange: function(newVal, oldVal) {
89535         var me = this;
89536         me.callParent(arguments);
89537
89538         if (newVal) {
89539             this.getManager().getByName(me.name).each(function(item){
89540                 if (item !== me) {
89541                     item.setValue(false);
89542                 }
89543             }, me);
89544         }
89545     },
89546
89547     // inherit docs
89548     getManager: function() {
89549         return Ext.form.RadioManager;
89550     }
89551 });
89552
89553 /**
89554  * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
89555  * class to allow browsing and selection of valid times, but could also be used with other components.
89556  *
89557  * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
89558  * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
89559  * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
89560  * config.
89561  *
89562  * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
89563  *
89564  *     @example
89565  *     Ext.create('Ext.picker.Time', {
89566  *        width: 60,
89567  *        minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
89568  *        maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
89569  *        renderTo: Ext.getBody()
89570  *     });
89571  */
89572 Ext.define('Ext.picker.Time', {
89573     extend: 'Ext.view.BoundList',
89574     alias: 'widget.timepicker',
89575     requires: ['Ext.data.Store', 'Ext.Date'],
89576
89577     /**
89578      * @cfg {Date} minValue
89579      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
89580      * used); no parsing of String values will be done.
89581      */
89582
89583     /**
89584      * @cfg {Date} maxValue
89585      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
89586      * used); no parsing of String values will be done.
89587      */
89588
89589     /**
89590      * @cfg {Number} increment
89591      * The number of minutes between each time value in the list.
89592      */
89593     increment: 15,
89594
89595     /**
89596      * @cfg {String} format
89597      * The default time format string which can be overriden for localization support. The format must be valid
89598      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
89599      * instead.
89600      */
89601     format : "g:i A",
89602
89603     /**
89604      * @hide
89605      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
89606      * an internal field name only and is not useful to change via config.
89607      */
89608     displayField: 'disp',
89609
89610     /**
89611      * @private
89612      * Year, month, and day that all times will be normalized into internally.
89613      */
89614     initDate: [2008,0,1],
89615
89616     componentCls: Ext.baseCSSPrefix + 'timepicker',
89617
89618     /**
89619      * @hide
89620      */
89621     loadMask: false,
89622
89623     initComponent: function() {
89624         var me = this,
89625             dateUtil = Ext.Date,
89626             clearTime = dateUtil.clearTime,
89627             initDate = me.initDate;
89628
89629         // Set up absolute min and max for the entire day
89630         me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
89631         me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
89632
89633         me.store = me.createStore();
89634         me.updateList();
89635
89636         me.callParent();
89637     },
89638
89639     /**
89640      * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
89641      * fields will be used); no parsing of String values will be done.
89642      * @param {Date} value
89643      */
89644     setMinValue: function(value) {
89645         this.minValue = value;
89646         this.updateList();
89647     },
89648
89649     /**
89650      * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
89651      * fields will be used); no parsing of String values will be done.
89652      * @param {Date} value
89653      */
89654     setMaxValue: function(value) {
89655         this.maxValue = value;
89656         this.updateList();
89657     },
89658
89659     /**
89660      * @private
89661      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
89662      * the time fields are significant. This makes values suitable for time comparison.
89663      * @param {Date} date
89664      */
89665     normalizeDate: function(date) {
89666         var initDate = this.initDate;
89667         date.setFullYear(initDate[0], initDate[1], initDate[2]);
89668         return date;
89669     },
89670
89671     /**
89672      * Update the list of available times in the list to be constrained within the {@link #minValue}
89673      * and {@link #maxValue}.
89674      */
89675     updateList: function() {
89676         var me = this,
89677             min = me.normalizeDate(me.minValue || me.absMin),
89678             max = me.normalizeDate(me.maxValue || me.absMax);
89679
89680         me.store.filterBy(function(record) {
89681             var date = record.get('date');
89682             return date >= min && date <= max;
89683         });
89684     },
89685
89686     /**
89687      * @private
89688      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
89689      * is loaded with all possible times, and it is later filtered to hide those times outside
89690      * the minValue/maxValue.
89691      */
89692     createStore: function() {
89693         var me = this,
89694             utilDate = Ext.Date,
89695             times = [],
89696             min = me.absMin,
89697             max = me.absMax;
89698
89699         while(min <= max){
89700             times.push({
89701                 disp: utilDate.dateFormat(min, me.format),
89702                 date: min
89703             });
89704             min = utilDate.add(min, 'mi', me.increment);
89705         }
89706
89707         return Ext.create('Ext.data.Store', {
89708             fields: ['disp', 'date'],
89709             data: times
89710         });
89711     }
89712
89713 });
89714
89715 /**
89716  * Provides a time input field with a time dropdown and automatic time validation.
89717  *
89718  * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
89719  * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
89720  * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
89721  * the user's locale.
89722  *
89723  * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
89724  * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
89725  *
89726  * Example usage:
89727  *
89728  *     @example
89729  *     Ext.create('Ext.form.Panel', {
89730  *         title: 'Time Card',
89731  *         width: 300,
89732  *         bodyPadding: 10,
89733  *         renderTo: Ext.getBody(),
89734  *         items: [{
89735  *             xtype: 'timefield',
89736  *             name: 'in',
89737  *             fieldLabel: 'Time In',
89738  *             minValue: '6:00 AM',
89739  *             maxValue: '8:00 PM',
89740  *             increment: 30,
89741  *             anchor: '100%'
89742  *         }, {
89743  *             xtype: 'timefield',
89744  *             name: 'out',
89745  *             fieldLabel: 'Time Out',
89746  *             minValue: '6:00 AM',
89747  *             maxValue: '8:00 PM',
89748  *             increment: 30,
89749  *             anchor: '100%'
89750  *        }]
89751  *     });
89752  */
89753 Ext.define('Ext.form.field.Time', {
89754     extend:'Ext.form.field.Picker',
89755     alias: 'widget.timefield',
89756     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
89757     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
89758
89759     /**
89760      * @cfg {String} triggerCls
89761      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
89762      * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field
89763      * trigger.
89764      */
89765     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
89766
89767     /**
89768      * @cfg {Date/String} minValue
89769      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
89770      * valid format -- see {@link #format} and {@link #altFormats}.
89771      */
89772
89773     /**
89774      * @cfg {Date/String} maxValue
89775      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
89776      * valid format -- see {@link #format} and {@link #altFormats}.
89777      */
89778
89779     /**
89780      * @cfg {String} minText
89781      * The error text to display when the entered time is before {@link #minValue}.
89782      */
89783     minText : "The time in this field must be equal to or after {0}",
89784
89785     /**
89786      * @cfg {String} maxText
89787      * The error text to display when the entered time is after {@link #maxValue}.
89788      */
89789     maxText : "The time in this field must be equal to or before {0}",
89790
89791     /**
89792      * @cfg {String} invalidText
89793      * The error text to display when the time in the field is invalid.
89794      */
89795     invalidText : "{0} is not a valid time",
89796
89797     /**
89798      * @cfg {String} format
89799      * The default time format string which can be overriden for localization support. The format must be valid
89800      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
89801      * instead.
89802      */
89803     format : "g:i A",
89804
89805     /**
89806      * @cfg {String} submitFormat
89807      * The date format string which will be submitted to the server. The format must be valid according to {@link
89808      * Ext.Date#parse} (defaults to {@link #format}).
89809      */
89810
89811     /**
89812      * @cfg {String} altFormats
89813      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
89814      * format.
89815      */
89816     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",
89817
89818     /**
89819      * @cfg {Number} increment
89820      * The number of minutes between each time value in the list.
89821      */
89822     increment: 15,
89823
89824     /**
89825      * @cfg {Number} pickerMaxHeight
89826      * The maximum height of the {@link Ext.picker.Time} dropdown.
89827      */
89828     pickerMaxHeight: 300,
89829
89830     /**
89831      * @cfg {Boolean} selectOnTab
89832      * Whether the Tab key should select the currently highlighted item.
89833      */
89834     selectOnTab: true,
89835
89836     /**
89837      * @private
89838      * This is the date to use when generating time values in the absence of either minValue
89839      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
89840      * arbitrary "safe" date that can be any date aside from DST boundary dates.
89841      */
89842     initDate: '1/1/2008',
89843     initDateFormat: 'j/n/Y',
89844
89845
89846     initComponent: function() {
89847         var me = this,
89848             min = me.minValue,
89849             max = me.maxValue;
89850         if (min) {
89851             me.setMinValue(min);
89852         }
89853         if (max) {
89854             me.setMaxValue(max);
89855         }
89856         this.callParent();
89857     },
89858
89859     initValue: function() {
89860         var me = this,
89861             value = me.value;
89862
89863         // If a String value was supplied, try to convert it to a proper Date object
89864         if (Ext.isString(value)) {
89865             me.value = me.rawToValue(value);
89866         }
89867
89868         me.callParent();
89869     },
89870
89871     /**
89872      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
89873      * @param {Date/String} value The minimum time that can be selected
89874      */
89875     setMinValue: function(value) {
89876         var me = this,
89877             picker = me.picker;
89878         me.setLimit(value, true);
89879         if (picker) {
89880             picker.setMinValue(me.minValue);
89881         }
89882     },
89883
89884     /**
89885      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
89886      * @param {Date/String} value The maximum time that can be selected
89887      */
89888     setMaxValue: function(value) {
89889         var me = this,
89890             picker = me.picker;
89891         me.setLimit(value, false);
89892         if (picker) {
89893             picker.setMaxValue(me.maxValue);
89894         }
89895     },
89896
89897     /**
89898      * @private
89899      * Updates either the min or max value. Converts the user's value into a Date object whose
89900      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
89901      */
89902     setLimit: function(value, isMin) {
89903         var me = this,
89904             d, val;
89905         if (Ext.isString(value)) {
89906             d = me.parseDate(value);
89907         }
89908         else if (Ext.isDate(value)) {
89909             d = value;
89910         }
89911         if (d) {
89912             val = Ext.Date.clearTime(new Date(me.initDate));
89913             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
89914             me[isMin ? 'minValue' : 'maxValue'] = val;
89915         }
89916     },
89917
89918     rawToValue: function(rawValue) {
89919         return this.parseDate(rawValue) || rawValue || null;
89920     },
89921
89922     valueToRaw: function(value) {
89923         return this.formatDate(this.parseDate(value));
89924     },
89925
89926     /**
89927      * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
89928      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
89929      * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
89930      * set.
89931      * @param {Object} [value] The value to get errors for (defaults to the current field value)
89932      * @return {String[]} All validation errors for this field
89933      */
89934     getErrors: function(value) {
89935         var me = this,
89936             format = Ext.String.format,
89937             errors = me.callParent(arguments),
89938             minValue = me.minValue,
89939             maxValue = me.maxValue,
89940             date;
89941
89942         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
89943
89944         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
89945              return errors;
89946         }
89947
89948         date = me.parseDate(value);
89949         if (!date) {
89950             errors.push(format(me.invalidText, value, me.format));
89951             return errors;
89952         }
89953
89954         if (minValue && date < minValue) {
89955             errors.push(format(me.minText, me.formatDate(minValue)));
89956         }
89957
89958         if (maxValue && date > maxValue) {
89959             errors.push(format(me.maxText, me.formatDate(maxValue)));
89960         }
89961
89962         return errors;
89963     },
89964
89965     formatDate: function() {
89966         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
89967     },
89968
89969     /**
89970      * @private
89971      * Parses an input value into a valid Date object.
89972      * @param {String/Date} value
89973      */
89974     parseDate: function(value) {
89975         if (!value || Ext.isDate(value)) {
89976             return value;
89977         }
89978
89979         var me = this,
89980             val = me.safeParse(value, me.format),
89981             altFormats = me.altFormats,
89982             altFormatsArray = me.altFormatsArray,
89983             i = 0,
89984             len;
89985
89986         if (!val && altFormats) {
89987             altFormatsArray = altFormatsArray || altFormats.split('|');
89988             len = altFormatsArray.length;
89989             for (; i < len && !val; ++i) {
89990                 val = me.safeParse(value, altFormatsArray[i]);
89991             }
89992         }
89993         return val;
89994     },
89995
89996     safeParse: function(value, format){
89997         var me = this,
89998             utilDate = Ext.Date,
89999             parsedDate,
90000             result = null;
90001
90002         if (utilDate.formatContainsDateInfo(format)) {
90003             // assume we've been given a full date
90004             result = utilDate.parse(value, format);
90005         } else {
90006             // Use our initial safe date
90007             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
90008             if (parsedDate) {
90009                 result = parsedDate;
90010             }
90011         }
90012         return result;
90013     },
90014
90015     // @private
90016     getSubmitValue: function() {
90017         var me = this,
90018             format = me.submitFormat || me.format,
90019             value = me.getValue();
90020
90021         return value ? Ext.Date.format(value, format) : null;
90022     },
90023
90024     /**
90025      * @private
90026      * Creates the {@link Ext.picker.Time}
90027      */
90028     createPicker: function() {
90029         var me = this,
90030             picker = Ext.create('Ext.picker.Time', {
90031                 pickerField: me,
90032                 selModel: {
90033                     mode: 'SINGLE'
90034                 },
90035                 floating: true,
90036                 hidden: true,
90037                 minValue: me.minValue,
90038                 maxValue: me.maxValue,
90039                 increment: me.increment,
90040                 format: me.format,
90041                 ownerCt: this.ownerCt,
90042                 renderTo: document.body,
90043                 maxHeight: me.pickerMaxHeight,
90044                 focusOnToFront: false
90045             });
90046
90047         me.mon(picker.getSelectionModel(), {
90048             selectionchange: me.onListSelect,
90049             scope: me
90050         });
90051
90052         return picker;
90053     },
90054
90055     /**
90056      * @private
90057      * Enables the key nav for the Time picker when it is expanded.
90058      * TODO this is largely the same logic as ComboBox, should factor out.
90059      */
90060     onExpand: function() {
90061         var me = this,
90062             keyNav = me.pickerKeyNav,
90063             selectOnTab = me.selectOnTab,
90064             picker = me.getPicker(),
90065             lastSelected = picker.getSelectionModel().lastSelected,
90066             itemNode;
90067
90068         if (!keyNav) {
90069             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
90070                 boundList: picker,
90071                 forceKeyDown: true,
90072                 tab: function(e) {
90073                     if (selectOnTab) {
90074                         if(me.picker.highlightedItem) {
90075                             this.selectHighlighted(e);
90076                         } else {
90077                             me.collapse();
90078                         }
90079                         me.triggerBlur();
90080                     }
90081                     // Tab key event is allowed to propagate to field
90082                     return true;
90083                 }
90084             });
90085             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
90086             if (selectOnTab) {
90087                 me.ignoreMonitorTab = true;
90088             }
90089         }
90090         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
90091
90092         // Highlight the last selected item and scroll it into view
90093         if (lastSelected) {
90094             itemNode = picker.getNode(lastSelected);
90095             if (itemNode) {
90096                 picker.highlightItem(itemNode);
90097                 picker.el.scrollChildIntoView(itemNode, false);
90098             }
90099         }
90100     },
90101
90102     /**
90103      * @private
90104      * Disables the key nav for the Time picker when it is collapsed.
90105      */
90106     onCollapse: function() {
90107         var me = this,
90108             keyNav = me.pickerKeyNav;
90109         if (keyNav) {
90110             keyNav.disable();
90111             me.ignoreMonitorTab = false;
90112         }
90113     },
90114
90115     /**
90116      * @private
90117      * Clears the highlighted item in the picker on change.
90118      * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed.
90119      */
90120     onChange: function() {
90121         var me = this,
90122             picker = me.picker;
90123
90124         me.callParent(arguments);
90125         if(picker) {
90126             picker.clearHighlight();
90127         }
90128     },
90129
90130     /**
90131      * @private
90132      * Handles a time being selected from the Time picker.
90133      */
90134     onListSelect: function(list, recordArray) {
90135         var me = this,
90136             record = recordArray[0],
90137             val = record ? record.get('date') : null;
90138         me.setValue(val);
90139         me.fireEvent('select', me, val);
90140         me.picker.clearHighlight();
90141         me.collapse();
90142         me.inputEl.focus();
90143     }
90144 });
90145
90146
90147 /**
90148  * @class Ext.grid.CellEditor
90149  * @extends Ext.Editor
90150  * Internal utility class that provides default configuration for cell editing.
90151  * @ignore
90152  */
90153 Ext.define('Ext.grid.CellEditor', {
90154     extend: 'Ext.Editor',
90155     constructor: function(config) {
90156         config = Ext.apply({}, config);
90157         
90158         if (config.field) {
90159             config.field.monitorTab = false;
90160         }
90161         if (!Ext.isDefined(config.autoSize)) {
90162             config.autoSize = {
90163                 width: 'boundEl'
90164             };
90165         }
90166         this.callParent([config]);
90167     },
90168     
90169     /**
90170      * @private
90171      * Hide the grid cell when editor is shown.
90172      */
90173     onShow: function() {
90174         var first = this.boundEl.first();
90175         if (first) {
90176             first.hide();
90177         }
90178         this.callParent(arguments);
90179     },
90180     
90181     /**
90182      * @private
90183      * Show grid cell when editor is hidden.
90184      */
90185     onHide: function() {
90186         var first = this.boundEl.first();
90187         if (first) {
90188             first.show();
90189         }
90190         this.callParent(arguments);
90191     },
90192     
90193     /**
90194      * @private
90195      * Fix checkbox blur when it is clicked.
90196      */
90197     afterRender: function() {
90198         this.callParent(arguments);
90199         var field = this.field;
90200         if (field.isXType('checkboxfield')) {
90201             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
90202             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
90203         }
90204     },
90205     
90206     /**
90207      * @private
90208      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
90209      */
90210     onCheckBoxMouseDown: function() {
90211         this.completeEdit = Ext.emptyFn;
90212     },
90213     
90214     /**
90215      * @private
90216      * Restore checkbox focus and completeEdit method.
90217      */
90218     onCheckBoxClick: function() {
90219         delete this.completeEdit;
90220         this.field.focus(false, 10);
90221     },
90222     
90223     alignment: "tl-tl",
90224     hideEl : false,
90225     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
90226     shim: false,
90227     shadow: false
90228 });
90229 /**
90230  * @class Ext.grid.ColumnLayout
90231  * @extends Ext.layout.container.HBox
90232  * @private
90233  *
90234  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
90235  *
90236  * <p>It adds the ability to shrink the vertical size of the inner container element back if a grouped
90237  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
90238  *
90239  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
90240  * <code>setPadding</code> on the columns so that they lay out correctly.</p>
90241  */
90242 Ext.define('Ext.grid.ColumnLayout', {
90243     extend: 'Ext.layout.container.HBox',
90244     alias: 'layout.gridcolumn',
90245     type : 'column',
90246
90247     reserveOffset: false,
90248
90249     shrinkToFit: false,
90250
90251     // Height-stretched innerCt must be able to revert back to unstretched height
90252     clearInnerCtOnLayout: true,
90253
90254     beforeLayout: function() {
90255         var me = this,
90256             i = 0,
90257             items = me.getLayoutItems(),
90258             len = items.length,
90259             item, returnValue,
90260             s;
90261
90262         // Scrollbar offset defined by width of any vertical scroller in the owning grid
90263         if (!Ext.isDefined(me.availableSpaceOffset)) {
90264             s = me.owner.up('tablepanel').verticalScroller;
90265             me.availableSpaceOffset = s ? s.width-1 : 0;
90266         }
90267
90268         returnValue = me.callParent(arguments);
90269
90270         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
90271         me.innerCt.setHeight(23);
90272
90273         // Unstretch child items before the layout which stretches them.
90274         for (; i < len; i++) {
90275             item = items[i];
90276             item.el.setStyle({
90277                 height: 'auto'
90278             });
90279             item.titleContainer.setStyle({
90280                 height: 'auto',
90281                 paddingTop: '0'
90282             });
90283             if (item.componentLayout && item.componentLayout.lastComponentSize) {
90284                 item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
90285             }
90286         }
90287         return returnValue;
90288     },
90289
90290     // Override to enforce the forceFit config.
90291     calculateChildBoxes: function(visibleItems, targetSize) {
90292         var me = this,
90293             calculations = me.callParent(arguments),
90294             boxes = calculations.boxes,
90295             metaData = calculations.meta,
90296             len = boxes.length, i = 0, box, item;
90297
90298         if (targetSize.width && !me.isHeader) {
90299             // If configured forceFit then all columns will be flexed
90300             if (me.owner.forceFit) {
90301
90302                 for (; i < len; i++) {
90303                     box = boxes[i];
90304                     item = box.component;
90305
90306                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
90307                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
90308
90309                     // For forceFit, just use allocated width as the flex value, and the proportions
90310                     // will end up the same whatever HeaderContainer width they are being forced into.
90311                     item.flex = box.width;
90312                 }
90313
90314                 // Recalculate based upon all columns now being flexed instead of sized.
90315                 calculations = me.callParent(arguments);
90316             }
90317             else if (metaData.tooNarrow) {
90318                 targetSize.width = metaData.desiredSize;
90319             }
90320         }
90321
90322         return calculations;
90323     },
90324
90325     afterLayout: function() {
90326         var me = this,
90327             owner = me.owner,
90328             topGrid,
90329             bothHeaderCts,
90330             otherHeaderCt,
90331             thisHeight,
90332             otherHeight,
90333             modifiedGrid,
90334             i = 0,
90335             items,
90336             len,
90337             headerHeight;
90338
90339         me.callParent(arguments);
90340
90341         // Set up padding in items
90342         if (!me.owner.hideHeaders) {
90343
90344             // If this is one HeaderContainer of a pair in a side-by-side locking view, then find the height
90345             // of the highest one, and sync the other one to that height.
90346             if (owner.lockableInjected) {
90347                 topGrid = owner.up('tablepanel').up('tablepanel');
90348                 bothHeaderCts = topGrid.query('headercontainer:not([isHeader])');
90349                 otherHeaderCt = (bothHeaderCts[0] === owner) ? bothHeaderCts[1] : bothHeaderCts[0];
90350
90351                 // Both sides must be rendered for this syncing operation to work.
90352                 if (!otherHeaderCt.rendered) {
90353                     return;
90354                 }
90355
90356                 // Get the height of the highest of both HeaderContainers
90357                 otherHeight = otherHeaderCt.layout.getRenderTarget().getViewSize().height;
90358                 if (!otherHeight) {
90359                     return;
90360                 }
90361                 thisHeight = this.getRenderTarget().getViewSize().height;
90362                 if (!thisHeight) {
90363                     return;
90364                 }
90365
90366                 // Prevent recursion back into here when the "other" grid, after adjusting to the new hight of its headerCt, attempts to inform its ownerCt
90367                 // Block the upward notification by flagging the top grid's component layout as busy.
90368                 topGrid.componentLayout.layoutBusy = true;
90369
90370                 // Assume that the correct header height is the height of this HeaderContainer
90371                 headerHeight = thisHeight;
90372
90373                 // Synch the height of the smaller HeaderContainer to the height of the highest one.
90374                 if (thisHeight > otherHeight) {
90375                     otherHeaderCt.layout.align = 'stretch';
90376                     otherHeaderCt.setCalculatedSize(otherHeaderCt.getWidth(), owner.getHeight(), otherHeaderCt.ownerCt);
90377                     delete otherHeaderCt.layout.align;
90378                     modifiedGrid = otherHeaderCt.up('tablepanel');
90379                 } else if (otherHeight > thisHeight) {
90380                     headerHeight = otherHeight;
90381                     this.align = 'stretch';
90382                     owner.setCalculatedSize(owner.getWidth(), otherHeaderCt.getHeight(), owner.ownerCt);
90383                     delete this.align;
90384                     modifiedGrid = owner.up('tablepanel');
90385                 }
90386                 topGrid.componentLayout.layoutBusy = false;
90387
90388                 // Gather all Header items across both Grids.
90389                 items = bothHeaderCts[0].layout.getLayoutItems().concat(bothHeaderCts[1].layout.getLayoutItems());
90390             } else {
90391                 headerHeight = this.getRenderTarget().getViewSize().height;
90392                 items = me.getLayoutItems();
90393             }
90394
90395             len = items.length;
90396             for (; i < len; i++) {
90397                 items[i].setPadding(headerHeight);
90398             }
90399
90400             // Size the View within the grid which has had its HeaderContainer entallened (That's a perfectly cromulent word BTW)
90401             if (modifiedGrid) {
90402                 setTimeout(function() {
90403                     modifiedGrid.doLayout();
90404                 }, 1);
90405             }
90406         }
90407     },
90408
90409     // FIX: when flexing we actually don't have enough space as we would
90410     // typically because of the scrollOffset on the GridView, must reserve this
90411     updateInnerCtSize: function(tSize, calcs) {
90412         var me = this,
90413             extra;
90414
90415         // Columns must not account for scroll offset
90416         if (!me.isHeader) {
90417             me.tooNarrow = calcs.meta.tooNarrow;
90418             extra = (me.reserveOffset ? me.availableSpaceOffset : 0);
90419
90420             if (calcs.meta.tooNarrow) {
90421                 tSize.width = calcs.meta.desiredSize + extra;
90422             } else {
90423                 tSize.width += extra;
90424             }
90425         }
90426
90427         return me.callParent(arguments);
90428     },
90429
90430     doOwnerCtLayouts: function() {
90431         var ownerCt = this.owner.ownerCt;
90432         if (!ownerCt.componentLayout.layoutBusy) {
90433             ownerCt.doComponentLayout();
90434         }
90435     }
90436 });
90437 /**
90438  * @class Ext.grid.LockingView
90439  * This class is used internally to provide a single interface when using
90440  * a locking grid. Internally, the locking grid creates two separate grids,
90441  * so this class is used to map calls appropriately.
90442  * @ignore
90443  */
90444 Ext.define('Ext.grid.LockingView', {
90445
90446     mixins: {
90447         observable: 'Ext.util.Observable'
90448     },
90449
90450     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
90451
90452     constructor: function(config){
90453         var me = this,
90454             eventNames = [],
90455             eventRe = me.eventRelayRe,
90456             locked = config.locked.getView(),
90457             normal = config.normal.getView(),
90458             events,
90459             event;
90460
90461         Ext.apply(me, {
90462             lockedView: locked,
90463             normalView: normal,
90464             lockedGrid: config.locked,
90465             normalGrid: config.normal,
90466             panel: config.panel
90467         });
90468         me.mixins.observable.constructor.call(me, config);
90469
90470         // relay events
90471         events = locked.events;
90472         for (event in events) {
90473             if (events.hasOwnProperty(event) && eventRe.test(event)) {
90474                 eventNames.push(event);
90475             }
90476         }
90477         me.relayEvents(locked, eventNames);
90478         me.relayEvents(normal, eventNames);
90479
90480         normal.on({
90481             scope: me,
90482             itemmouseleave: me.onItemMouseLeave,
90483             itemmouseenter: me.onItemMouseEnter
90484         });
90485
90486         locked.on({
90487             scope: me,
90488             itemmouseleave: me.onItemMouseLeave,
90489             itemmouseenter: me.onItemMouseEnter
90490         });
90491     },
90492
90493     getGridColumns: function() {
90494         var cols = this.lockedGrid.headerCt.getGridColumns();
90495         return cols.concat(this.normalGrid.headerCt.getGridColumns());
90496     },
90497
90498     getEl: function(column){
90499         return this.getViewForColumn(column).getEl();
90500     },
90501
90502     getViewForColumn: function(column) {
90503         var view = this.lockedView,
90504             inLocked;
90505
90506         view.headerCt.cascade(function(col){
90507             if (col === column) {
90508                 inLocked = true;
90509                 return false;
90510             }
90511         });
90512
90513         return inLocked ? view : this.normalView;
90514     },
90515
90516     onItemMouseEnter: function(view, record){
90517         var me = this,
90518             locked = me.lockedView,
90519             other = me.normalView,
90520             item;
90521
90522         if (view.trackOver) {
90523             if (view !== locked) {
90524                 other = locked;
90525             }
90526             item = other.getNode(record);
90527             other.highlightItem(item);
90528         }
90529     },
90530
90531     onItemMouseLeave: function(view, record){
90532         var me = this,
90533             locked = me.lockedView,
90534             other = me.normalView;
90535
90536         if (view.trackOver) {
90537             if (view !== locked) {
90538                 other = locked;
90539             }
90540             other.clearHighlight();
90541         }
90542     },
90543
90544     relayFn: function(name, args){
90545         args = args || [];
90546
90547         var view = this.lockedView;
90548         view[name].apply(view, args || []);
90549         view = this.normalView;
90550         view[name].apply(view, args || []);
90551     },
90552
90553     getSelectionModel: function(){
90554         return this.panel.getSelectionModel();
90555     },
90556
90557     getStore: function(){
90558         return this.panel.store;
90559     },
90560
90561     getNode: function(nodeInfo){
90562         // default to the normal view
90563         return this.normalView.getNode(nodeInfo);
90564     },
90565
90566     getCell: function(record, column){
90567         var view = this.getViewForColumn(column),
90568             row;
90569
90570         row = view.getNode(record);
90571         return Ext.fly(row).down(column.getCellSelector());
90572     },
90573
90574     getRecord: function(node){
90575         var result = this.lockedView.getRecord(node);
90576         if (!node) {
90577             result = this.normalView.getRecord(node);
90578         }
90579         return result;
90580     },
90581
90582     addElListener: function(eventName, fn, scope){
90583         this.relayFn('addElListener', arguments);
90584     },
90585
90586     refreshNode: function(){
90587         this.relayFn('refreshNode', arguments);
90588     },
90589
90590     refresh: function(){
90591         this.relayFn('refresh', arguments);
90592     },
90593
90594     bindStore: function(){
90595         this.relayFn('bindStore', arguments);
90596     },
90597
90598     addRowCls: function(){
90599         this.relayFn('addRowCls', arguments);
90600     },
90601
90602     removeRowCls: function(){
90603         this.relayFn('removeRowCls', arguments);
90604     }
90605
90606 });
90607 /**
90608  * @class Ext.grid.Lockable
90609  * @private
90610  *
90611  * Lockable is a private mixin which injects lockable behavior into any
90612  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
90613  * automatically inject the Ext.grid.Lockable mixin in when one of the
90614  * these conditions are met:
90615  *
90616  *  - The TablePanel has the lockable configuration set to true
90617  *  - One of the columns in the TablePanel has locked set to true/false
90618  *
90619  * Each TablePanel subclass must register an alias. It should have an array
90620  * of configurations to copy to the 2 separate tablepanel's that will be generated
90621  * to note what configurations should be copied. These are named normalCfgCopy and
90622  * lockedCfgCopy respectively.
90623  *
90624  * Columns which are locked must specify a fixed width. They do NOT support a
90625  * flex width.
90626  *
90627  * Configurations which are specified in this class will be available on any grid or
90628  * tree which is using the lockable functionality.
90629  */
90630 Ext.define('Ext.grid.Lockable', {
90631
90632     requires: ['Ext.grid.LockingView'],
90633
90634     /**
90635      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
90636      * locked grid view. This is turned on by default. If your grid is guaranteed
90637      * to have rows of all the same height, you should set this to false to
90638      * optimize performance.
90639      */
90640     syncRowHeight: true,
90641
90642     /**
90643      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
90644      * not specified lockable will determine the subgrid xtype to create by the
90645      * following rule. Use the superclasses xtype if the superclass is NOT
90646      * tablepanel, otherwise use the xtype itself.
90647      */
90648
90649     /**
90650      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
90651      * locked side of the grid. Any conflicting configurations between lockedViewConfig
90652      * and viewConfig will be overwritten by the lockedViewConfig.
90653      */
90654
90655     /**
90656      * @cfg {Object} normalViewConfig A view configuration to be applied to the
90657      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
90658      * and viewConfig will be overwritten by the normalViewConfig.
90659      */
90660
90661     // private variable to track whether or not the spacer is hidden/visible
90662     spacerHidden: true,
90663
90664     headerCounter: 0,
90665
90666     // i8n text
90667     unlockText: 'Unlock',
90668     lockText: 'Lock',
90669
90670     determineXTypeToCreate: function() {
90671         var me = this,
90672             typeToCreate;
90673
90674         if (me.subGridXType) {
90675             typeToCreate = me.subGridXType;
90676         } else {
90677             var xtypes     = this.getXTypes().split('/'),
90678                 xtypesLn   = xtypes.length,
90679                 xtype      = xtypes[xtypesLn - 1],
90680                 superxtype = xtypes[xtypesLn - 2];
90681
90682             if (superxtype !== 'tablepanel') {
90683                 typeToCreate = superxtype;
90684             } else {
90685                 typeToCreate = xtype;
90686             }
90687         }
90688
90689         return typeToCreate;
90690     },
90691
90692     // injectLockable will be invoked before initComponent's parent class implementation
90693     // is called, so throughout this method this. are configurations
90694     injectLockable: function() {
90695         // ensure lockable is set to true in the TablePanel
90696         this.lockable = true;
90697         // Instruct the TablePanel it already has a view and not to create one.
90698         // We are going to aggregate 2 copies of whatever TablePanel we are using
90699         this.hasView = true;
90700
90701         var me = this,
90702             // xtype of this class, 'treepanel' or 'gridpanel'
90703             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
90704             // alias.)
90705             xtype = me.determineXTypeToCreate(),
90706             // share the selection model
90707             selModel = me.getSelectionModel(),
90708             lockedGrid = {
90709                 xtype: xtype,
90710                 // Lockable does NOT support animations for Tree
90711                 enableAnimations: false,
90712                 scroll: false,
90713                 scrollerOwner: false,
90714                 selModel: selModel,
90715                 border: false,
90716                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
90717             },
90718             normalGrid = {
90719                 xtype: xtype,
90720                 enableAnimations: false,
90721                 scrollerOwner: false,
90722                 selModel: selModel,
90723                 border: false
90724             },
90725             i = 0,
90726             columns,
90727             lockedHeaderCt,
90728             normalHeaderCt;
90729
90730         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
90731
90732         // copy appropriate configurations to the respective
90733         // aggregated tablepanel instances and then delete them
90734         // from the master tablepanel.
90735         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
90736         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
90737         for (; i < me.normalCfgCopy.length; i++) {
90738             delete me[me.normalCfgCopy[i]];
90739         }
90740         for (i = 0; i < me.lockedCfgCopy.length; i++) {
90741             delete me[me.lockedCfgCopy[i]];
90742         }
90743
90744         me.addEvents(
90745             /**
90746              * @event lockcolumn
90747              * Fires when a column is locked.
90748              * @param {Ext.grid.Panel} this The gridpanel.
90749              * @param {Ext.grid.column.Column} column The column being locked.
90750              */
90751             'lockcolumn',
90752
90753             /**
90754              * @event unlockcolumn
90755              * Fires when a column is unlocked.
90756              * @param {Ext.grid.Panel} this The gridpanel.
90757              * @param {Ext.grid.column.Column} column The column being unlocked.
90758              */
90759             'unlockcolumn'
90760         );
90761
90762         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
90763
90764         me.lockedHeights = [];
90765         me.normalHeights = [];
90766
90767         columns = me.processColumns(me.columns);
90768
90769         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
90770         lockedGrid.columns = columns.locked;
90771         normalGrid.columns = columns.normal;
90772
90773         me.store = Ext.StoreManager.lookup(me.store);
90774         lockedGrid.store = me.store;
90775         normalGrid.store = me.store;
90776
90777         // normal grid should flex the rest of the width
90778         normalGrid.flex = 1;
90779         lockedGrid.viewConfig = me.lockedViewConfig || {};
90780         lockedGrid.viewConfig.loadingUseMsg = false;
90781         normalGrid.viewConfig = me.normalViewConfig || {};
90782
90783         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
90784         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
90785
90786         me.normalGrid = Ext.ComponentManager.create(normalGrid);
90787         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
90788
90789         me.view = Ext.create('Ext.grid.LockingView', {
90790             locked: me.lockedGrid,
90791             normal: me.normalGrid,
90792             panel: me
90793         });
90794
90795         if (me.syncRowHeight) {
90796             me.lockedGrid.getView().on({
90797                 refresh: me.onLockedGridAfterRefresh,
90798                 itemupdate: me.onLockedGridAfterUpdate,
90799                 scope: me
90800             });
90801
90802             me.normalGrid.getView().on({
90803                 refresh: me.onNormalGridAfterRefresh,
90804                 itemupdate: me.onNormalGridAfterUpdate,
90805                 scope: me
90806             });
90807         }
90808
90809         lockedHeaderCt = me.lockedGrid.headerCt;
90810         normalHeaderCt = me.normalGrid.headerCt;
90811
90812         lockedHeaderCt.lockedCt = true;
90813         lockedHeaderCt.lockableInjected = true;
90814         normalHeaderCt.lockableInjected = true;
90815
90816         lockedHeaderCt.on({
90817             columnshow: me.onLockedHeaderShow,
90818             columnhide: me.onLockedHeaderHide,
90819             columnmove: me.onLockedHeaderMove,
90820             sortchange: me.onLockedHeaderSortChange,
90821             columnresize: me.onLockedHeaderResize,
90822             scope: me
90823         });
90824
90825         normalHeaderCt.on({
90826             columnmove: me.onNormalHeaderMove,
90827             sortchange: me.onNormalHeaderSortChange,
90828             scope: me
90829         });
90830
90831         me.normalGrid.on({
90832             scrollershow: me.onScrollerShow,
90833             scrollerhide: me.onScrollerHide,
90834             scope: me
90835         });
90836
90837         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
90838
90839         me.modifyHeaderCt();
90840         me.items = [me.lockedGrid, me.normalGrid];
90841
90842         me.relayHeaderCtEvents(lockedHeaderCt);
90843         me.relayHeaderCtEvents(normalHeaderCt);
90844
90845         me.layout = {
90846             type: 'hbox',
90847             align: 'stretch'
90848         };
90849     },
90850
90851     processColumns: function(columns){
90852         // split apart normal and lockedWidths
90853         var i = 0,
90854             len = columns.length,
90855             lockedWidth = 1,
90856             lockedHeaders = [],
90857             normalHeaders = [],
90858             column;
90859
90860         for (; i < len; ++i) {
90861             column = columns[i];
90862             // mark the column as processed so that the locked attribute does not
90863             // trigger trying to aggregate the columns again.
90864             column.processed = true;
90865             if (column.locked) {
90866                 // <debug>
90867                 if (column.flex) {
90868                     Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
90869                 }
90870                 // </debug>
90871                 if (!column.hidden) {
90872                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
90873                 }
90874                 lockedHeaders.push(column);
90875             } else {
90876                 normalHeaders.push(column);
90877             }
90878             if (!column.headerId) {
90879                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
90880             }
90881         }
90882         return {
90883             lockedWidth: lockedWidth,
90884             locked: lockedHeaders,
90885             normal: normalHeaders
90886         };
90887     },
90888
90889     // create a new spacer after the table is refreshed
90890     onLockedGridAfterLayout: function() {
90891         var me         = this,
90892             lockedView = me.lockedGrid.getView();
90893         lockedView.on({
90894             beforerefresh: me.destroySpacer,
90895             scope: me
90896         });
90897     },
90898
90899     // trigger a pseudo refresh on the normal side
90900     onLockedHeaderMove: function() {
90901         if (this.syncRowHeight) {
90902             this.onNormalGridAfterRefresh();
90903         }
90904     },
90905
90906     // trigger a pseudo refresh on the locked side
90907     onNormalHeaderMove: function() {
90908         if (this.syncRowHeight) {
90909             this.onLockedGridAfterRefresh();
90910         }
90911     },
90912
90913     // create a spacer in lockedsection and store a reference
90914     // TODO: Should destroy before refreshing content
90915     getSpacerEl: function() {
90916         var me   = this,
90917             w,
90918             view,
90919             el;
90920
90921         if (!me.spacerEl) {
90922             // This affects scrolling all the way to the bottom of a locked grid
90923             // additional test, sort a column and make sure it synchronizes
90924             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
90925             view = me.lockedGrid.getView();
90926             el   = view.el;
90927
90928             me.spacerEl = Ext.DomHelper.append(el, {
90929                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
90930                 style: 'height: ' + w + 'px;'
90931             }, true);
90932         }
90933         return me.spacerEl;
90934     },
90935
90936     destroySpacer: function() {
90937         var me = this;
90938         if (me.spacerEl) {
90939             me.spacerEl.destroy();
90940             delete me.spacerEl;
90941         }
90942     },
90943
90944     // cache the heights of all locked rows and sync rowheights
90945     onLockedGridAfterRefresh: function() {
90946         var me     = this,
90947             view   = me.lockedGrid.getView(),
90948             el     = view.el,
90949             rowEls = el.query(view.getItemSelector()),
90950             ln     = rowEls.length,
90951             i = 0;
90952
90953         // reset heights each time.
90954         me.lockedHeights = [];
90955
90956         for (; i < ln; i++) {
90957             me.lockedHeights[i] = rowEls[i].clientHeight;
90958         }
90959         me.syncRowHeights();
90960     },
90961
90962     // cache the heights of all normal rows and sync rowheights
90963     onNormalGridAfterRefresh: function() {
90964         var me     = this,
90965             view   = me.normalGrid.getView(),
90966             el     = view.el,
90967             rowEls = el.query(view.getItemSelector()),
90968             ln     = rowEls.length,
90969             i = 0;
90970
90971         // reset heights each time.
90972         me.normalHeights = [];
90973
90974         for (; i < ln; i++) {
90975             me.normalHeights[i] = rowEls[i].clientHeight;
90976         }
90977         me.syncRowHeights();
90978     },
90979
90980     // rows can get bigger/smaller
90981     onLockedGridAfterUpdate: function(record, index, node) {
90982         this.lockedHeights[index] = node.clientHeight;
90983         this.syncRowHeights();
90984     },
90985
90986     // rows can get bigger/smaller
90987     onNormalGridAfterUpdate: function(record, index, node) {
90988         this.normalHeights[index] = node.clientHeight;
90989         this.syncRowHeights();
90990     },
90991
90992     // match the rowheights to the biggest rowheight on either
90993     // side
90994     syncRowHeights: function() {
90995         var me = this,
90996             lockedHeights = me.lockedHeights,
90997             normalHeights = me.normalHeights,
90998             calcHeights   = [],
90999             ln = lockedHeights.length,
91000             i  = 0,
91001             lockedView, normalView,
91002             lockedRowEls, normalRowEls,
91003             vertScroller = me.getVerticalScroller(),
91004             scrollTop;
91005
91006         // ensure there are an equal num of locked and normal
91007         // rows before synchronization
91008         if (lockedHeights.length && normalHeights.length) {
91009             lockedView = me.lockedGrid.getView();
91010             normalView = me.normalGrid.getView();
91011             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
91012             normalRowEls = normalView.el.query(normalView.getItemSelector());
91013
91014             // loop thru all of the heights and sync to the other side
91015             for (; i < ln; i++) {
91016                 // ensure both are numbers
91017                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
91018                     if (lockedHeights[i] > normalHeights[i]) {
91019                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
91020                     } else if (lockedHeights[i] < normalHeights[i]) {
91021                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
91022                     }
91023                 }
91024             }
91025
91026             // invalidate the scroller and sync the scrollers
91027             me.normalGrid.invalidateScroller();
91028
91029             // synchronize the view with the scroller, if we have a virtualScrollTop
91030             // then the user is using a PagingScroller
91031             if (vertScroller && vertScroller.setViewScrollTop) {
91032                 vertScroller.setViewScrollTop(me.virtualScrollTop);
91033             } else {
91034                 // We don't use setScrollTop here because if the scrollTop is
91035                 // set to the exact same value some browsers won't fire the scroll
91036                 // event. Instead, we directly set the scrollTop.
91037                 scrollTop = normalView.el.dom.scrollTop;
91038                 normalView.el.dom.scrollTop = scrollTop;
91039                 lockedView.el.dom.scrollTop = scrollTop;
91040             }
91041
91042             // reset the heights
91043             me.lockedHeights = [];
91044             me.normalHeights = [];
91045         }
91046     },
91047
91048     // track when scroller is shown
91049     onScrollerShow: function(scroller, direction) {
91050         if (direction === 'horizontal') {
91051             this.spacerHidden = false;
91052             this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
91053         }
91054     },
91055
91056     // track when scroller is hidden
91057     onScrollerHide: function(scroller, direction) {
91058         if (direction === 'horizontal') {
91059             this.spacerHidden = true;
91060             if (this.spacerEl) {
91061                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
91062             }
91063         }
91064     },
91065
91066
91067     // inject Lock and Unlock text
91068     modifyHeaderCt: function() {
91069         var me = this;
91070         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
91071         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
91072     },
91073
91074     onUnlockMenuClick: function() {
91075         this.unlock();
91076     },
91077
91078     onLockMenuClick: function() {
91079         this.lock();
91080     },
91081
91082     getMenuItems: function(locked) {
91083         var me            = this,
91084             unlockText    = me.unlockText,
91085             lockText      = me.lockText,
91086             unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
91087             lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
91088             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
91089             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
91090
91091         // runs in the scope of headerCt
91092         return function() {
91093             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
91094             o.push('-',{
91095                 cls: unlockCls,
91096                 text: unlockText,
91097                 handler: unlockHandler,
91098                 disabled: !locked
91099             });
91100             o.push({
91101                 cls: lockCls,
91102                 text: lockText,
91103                 handler: lockHandler,
91104                 disabled: locked
91105             });
91106             return o;
91107         };
91108     },
91109
91110     // going from unlocked section to locked
91111     /**
91112      * Locks the activeHeader as determined by which menu is open OR a header
91113      * as specified.
91114      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
91115      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
91116      * @private
91117      */
91118     lock: function(activeHd, toIdx) {
91119         var me         = this,
91120             normalGrid = me.normalGrid,
91121             lockedGrid = me.lockedGrid,
91122             normalHCt  = normalGrid.headerCt,
91123             lockedHCt  = lockedGrid.headerCt;
91124
91125         activeHd = activeHd || normalHCt.getMenu().activeHeader;
91126
91127         // if column was previously flexed, get/set current width
91128         // and remove the flex
91129         if (activeHd.flex) {
91130             activeHd.width = activeHd.getWidth();
91131             delete activeHd.flex;
91132         }
91133
91134         normalHCt.remove(activeHd, false);
91135         lockedHCt.suspendLayout = true;
91136         activeHd.locked = true;
91137         if (Ext.isDefined(toIdx)) {
91138             lockedHCt.insert(toIdx, activeHd);
91139         } else {
91140             lockedHCt.add(activeHd);
91141         }
91142         lockedHCt.suspendLayout = false;
91143         me.syncLockedSection();
91144
91145         me.fireEvent('lockcolumn', me, activeHd);
91146     },
91147
91148     syncLockedSection: function() {
91149         var me = this;
91150         me.syncLockedWidth();
91151         me.lockedGrid.getView().refresh();
91152         me.normalGrid.getView().refresh();
91153     },
91154
91155     // adjust the locked section to the width of its respective
91156     // headerCt
91157     syncLockedWidth: function() {
91158         var me = this,
91159             width = me.lockedGrid.headerCt.getFullWidth(true);
91160         me.lockedGrid.setWidth(width+1); // +1 for border pixel
91161         me.doComponentLayout();
91162     },
91163
91164     onLockedHeaderResize: function() {
91165         this.syncLockedWidth();
91166     },
91167
91168     onLockedHeaderHide: function() {
91169         this.syncLockedWidth();
91170     },
91171
91172     onLockedHeaderShow: function() {
91173         this.syncLockedWidth();
91174     },
91175
91176     onLockedHeaderSortChange: function(headerCt, header, sortState) {
91177         if (sortState) {
91178             // no real header, and silence the event so we dont get into an
91179             // infinite loop
91180             this.normalGrid.headerCt.clearOtherSortStates(null, true);
91181         }
91182     },
91183
91184     onNormalHeaderSortChange: function(headerCt, header, sortState) {
91185         if (sortState) {
91186             // no real header, and silence the event so we dont get into an
91187             // infinite loop
91188             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
91189         }
91190     },
91191
91192     // going from locked section to unlocked
91193     /**
91194      * Unlocks the activeHeader as determined by which menu is open OR a header
91195      * as specified.
91196      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
91197      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
91198      * @private
91199      */
91200     unlock: function(activeHd, toIdx) {
91201         var me         = this,
91202             normalGrid = me.normalGrid,
91203             lockedGrid = me.lockedGrid,
91204             normalHCt  = normalGrid.headerCt,
91205             lockedHCt  = lockedGrid.headerCt;
91206
91207         if (!Ext.isDefined(toIdx)) {
91208             toIdx = 0;
91209         }
91210         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
91211
91212         lockedHCt.remove(activeHd, false);
91213         me.syncLockedWidth();
91214         me.lockedGrid.getView().refresh();
91215         activeHd.locked = false;
91216         normalHCt.insert(toIdx, activeHd);
91217         me.normalGrid.getView().refresh();
91218
91219         me.fireEvent('unlockcolumn', me, activeHd);
91220     },
91221
91222     applyColumnsState: function (columns) {
91223         var me = this,
91224             lockedGrid = me.lockedGrid,
91225             lockedHeaderCt = lockedGrid.headerCt,
91226             normalHeaderCt = me.normalGrid.headerCt,
91227             lockedCols = lockedHeaderCt.items,
91228             normalCols = normalHeaderCt.items,
91229             existing,
91230             locked = [],
91231             normal = [],
91232             lockedDefault,
91233             lockedWidth = 1;
91234
91235         Ext.each(columns, function (col) {
91236             function matches (item) {
91237                 return item.headerId == col.id;
91238             }
91239
91240             lockedDefault = true;
91241             if (!(existing = lockedCols.findBy(matches))) {
91242                 existing = normalCols.findBy(matches);
91243                 lockedDefault = false;
91244             }
91245
91246             if (existing) {
91247                 if (existing.applyColumnState) {
91248                     existing.applyColumnState(col);
91249                 }
91250                 if (!Ext.isDefined(existing.locked)) {
91251                     existing.locked = lockedDefault;
91252                 }
91253                 if (existing.locked) {
91254                     locked.push(existing);
91255                     if (!existing.hidden && Ext.isNumber(existing.width)) {
91256                         lockedWidth += existing.width;
91257                     }
91258                 } else {
91259                     normal.push(existing);
91260                 }
91261             }
91262         });
91263
91264         // state and config must have the same columns (compare counts for now):
91265         if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
91266             lockedHeaderCt.removeAll(false);
91267             normalHeaderCt.removeAll(false);
91268
91269             lockedHeaderCt.add(locked);
91270             normalHeaderCt.add(normal);
91271
91272             lockedGrid.setWidth(lockedWidth);
91273         }
91274     },
91275
91276     getColumnsState: function () {
91277         var me = this,
91278             locked = me.lockedGrid.headerCt.getColumnsState(),
91279             normal = me.normalGrid.headerCt.getColumnsState();
91280
91281         return locked.concat(normal);
91282     },
91283
91284     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
91285     reconfigureLockable: function(store, columns) {
91286         var me = this,
91287             lockedGrid = me.lockedGrid,
91288             normalGrid = me.normalGrid;
91289
91290         if (columns) {
91291             lockedGrid.headerCt.suspendLayout = true;
91292             normalGrid.headerCt.suspendLayout = true;
91293             lockedGrid.headerCt.removeAll();
91294             normalGrid.headerCt.removeAll();
91295
91296             columns = me.processColumns(columns);
91297             lockedGrid.setWidth(columns.lockedWidth);
91298             lockedGrid.headerCt.add(columns.locked);
91299             normalGrid.headerCt.add(columns.normal);
91300         }
91301
91302         if (store) {
91303             store = Ext.data.StoreManager.lookup(store);
91304             me.store = store;
91305             lockedGrid.bindStore(store);
91306             normalGrid.bindStore(store);
91307         } else {
91308             lockedGrid.getView().refresh();
91309             normalGrid.getView().refresh();
91310         }
91311
91312         if (columns) {
91313             lockedGrid.headerCt.suspendLayout = false;
91314             normalGrid.headerCt.suspendLayout = false;
91315             lockedGrid.headerCt.forceComponentLayout();
91316             normalGrid.headerCt.forceComponentLayout();
91317         }
91318     }
91319 });
91320
91321 /**
91322  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
91323  * across different sections.
91324  */
91325 Ext.define('Ext.grid.Scroller', {
91326     extend: 'Ext.Component',
91327     alias: 'widget.gridscroller',
91328     weight: 110,
91329     baseCls: Ext.baseCSSPrefix + 'scroller',
91330     focusable: false,
91331     reservedSpace: 0,
91332
91333     renderTpl: [
91334         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
91335             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
91336         '</div>'
91337     ],
91338
91339     initComponent: function() {
91340         var me       = this,
91341             dock     = me.dock,
91342             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
91343
91344         me.offsets = {bottom: 0};
91345         me.scrollProp = 'scrollTop';
91346         me.vertical = true;
91347         me.sizeProp = 'width';
91348
91349         if (dock === 'top' || dock === 'bottom') {
91350             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
91351             me.sizeProp = 'height';
91352             me.scrollProp = 'scrollLeft';
91353             me.vertical = false;
91354             me.weight += 5;
91355         }
91356
91357         me.cls += (' ' + cls);
91358
91359         Ext.applyIf(me.renderSelectors, {
91360             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
91361             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
91362         });
91363         me.callParent();
91364     },
91365     
91366     ensureDimension: function(){
91367         var me = this,
91368             sizeProp = me.sizeProp;
91369             
91370         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
91371     },
91372
91373     initRenderData: function () {
91374         var me = this,
91375             ret = me.callParent(arguments) || {};
91376
91377         ret.baseId = me.id;
91378
91379         return ret;
91380     },
91381
91382     afterRender: function() {
91383         var me = this;
91384         me.callParent();
91385         
91386         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
91387         Ext.cache[me.el.id].skipGarbageCollection = true;
91388     },
91389
91390     onAdded: function(container) {
91391         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
91392         this.ownerGrid = container;
91393         this.callParent(arguments);
91394     },
91395
91396     getSizeCalculation: function() {
91397         var me     = this,
91398             owner  = me.getPanel(),
91399             width  = 1,
91400             height = 1,
91401             view, tbl;
91402
91403         if (!me.vertical) {
91404             // TODO: Must gravitate to a single region..
91405             // Horizontal scrolling only scrolls virtualized region
91406             var items  = owner.query('tableview'),
91407                 center = items[1] || items[0];
91408
91409             if (!center) {
91410                 return false;
91411             }
91412             // center is not guaranteed to have content, such as when there
91413             // are zero rows in the grid/tree. We read the width from the
91414             // headerCt instead.
91415             width = center.headerCt.getFullWidth();
91416
91417             if (Ext.isIEQuirks) {
91418                 width--;
91419             }
91420         } else {
91421             view = owner.down('tableview:not([lockableInjected])');
91422             if (!view || !view.el) {
91423                 return false;
91424             }
91425             tbl = view.el.child('table', true);
91426             if (!tbl) {
91427                 return false;
91428             }
91429
91430             // needs to also account for header and scroller (if still in picture)
91431             // should calculate from headerCt.
91432             height = tbl.offsetHeight;
91433         }
91434         if (isNaN(width)) {
91435             width = 1;
91436         }
91437         if (isNaN(height)) {
91438             height = 1;
91439         }
91440         return {
91441             width: width,
91442             height: height
91443         };
91444     },
91445
91446     invalidate: function(firstPass) {
91447         var me = this,
91448             stretchEl = me.stretchEl;
91449
91450         if (!stretchEl || !me.ownerCt) {
91451             return;
91452         }
91453
91454         var size  = me.getSizeCalculation(),
91455             scrollEl = me.scrollEl,
91456             elDom = scrollEl.dom,
91457             reservedSpace = me.reservedSpace,
91458             pos,
91459             extra = 5;
91460
91461         if (size) {
91462             stretchEl.setSize(size);
91463
91464             size = me.el.getSize(true);
91465
91466             if (me.vertical) {
91467                 size.width += extra;
91468                 size.height -= reservedSpace;
91469                 pos = 'left';
91470             } else {
91471                 size.width -= reservedSpace;
91472                 size.height += extra;
91473                 pos = 'top';
91474             }
91475
91476             scrollEl.setSize(size);
91477             elDom.style[pos] = (-extra) + 'px';
91478
91479             // BrowserBug: IE7
91480             // This makes the scroller enabled, when initially rendering.
91481             elDom.scrollTop = elDom.scrollTop;
91482         }
91483     },
91484
91485     afterComponentLayout: function() {
91486         this.callParent(arguments);
91487         this.invalidate();
91488     },
91489
91490     restoreScrollPos: function () {
91491         var me = this,
91492             el = this.scrollEl,
91493             elDom = el && el.dom;
91494
91495         if (me._scrollPos !== null && elDom) {
91496             elDom[me.scrollProp] = me._scrollPos;
91497             me._scrollPos = null;
91498         }
91499     },
91500
91501     setReservedSpace: function (reservedSpace) {
91502         var me = this;
91503         if (me.reservedSpace !== reservedSpace) {
91504             me.reservedSpace = reservedSpace;
91505             me.invalidate();
91506         }
91507     },
91508
91509     saveScrollPos: function () {
91510         var me = this,
91511             el = this.scrollEl,
91512             elDom = el && el.dom;
91513
91514         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
91515     },
91516
91517     /**
91518      * Sets the scrollTop and constrains the value between 0 and max.
91519      * @param {Number} scrollTop
91520      * @return {Number} The resulting scrollTop value after being constrained
91521      */
91522     setScrollTop: function(scrollTop) {
91523         var el = this.scrollEl,
91524             elDom = el && el.dom;
91525
91526         if (elDom) {
91527             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
91528         }
91529     },
91530
91531     /**
91532      * Sets the scrollLeft and constrains the value between 0 and max.
91533      * @param {Number} scrollLeft
91534      * @return {Number} The resulting scrollLeft value after being constrained
91535      */
91536     setScrollLeft: function(scrollLeft) {
91537         var el = this.scrollEl,
91538             elDom = el && el.dom;
91539
91540         if (elDom) {
91541             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
91542         }
91543     },
91544
91545     /**
91546      * Scroll by deltaY
91547      * @param {Number} delta
91548      * @return {Number} The resulting scrollTop value
91549      */
91550     scrollByDeltaY: function(delta) {
91551         var el = this.scrollEl,
91552             elDom = el && el.dom;
91553
91554         if (elDom) {
91555             return this.setScrollTop(elDom.scrollTop + delta);
91556         }
91557     },
91558
91559     /**
91560      * Scroll by deltaX
91561      * @param {Number} delta
91562      * @return {Number} The resulting scrollLeft value
91563      */
91564     scrollByDeltaX: function(delta) {
91565         var el = this.scrollEl,
91566             elDom = el && el.dom;
91567
91568         if (elDom) {
91569             return this.setScrollLeft(elDom.scrollLeft + delta);
91570         }
91571     },
91572
91573
91574     /**
91575      * Scroll to the top.
91576      */
91577     scrollToTop : function(){
91578         this.setScrollTop(0);
91579     },
91580
91581     // synchronize the scroller with the bound gridviews
91582     onElScroll: function(event, target) {
91583         this.fireEvent('bodyscroll', event, target);
91584     },
91585
91586     getPanel: function() {
91587         var me = this;
91588         if (!me.panel) {
91589             me.panel = this.up('[scrollerOwner]');
91590         }
91591         return me.panel;
91592     }
91593 });
91594
91595
91596 /**
91597  * @class Ext.grid.PagingScroller
91598  * @extends Ext.grid.Scroller
91599  */
91600 Ext.define('Ext.grid.PagingScroller', {
91601     extend: 'Ext.grid.Scroller',
91602     alias: 'widget.paginggridscroller',
91603     //renderTpl: null,
91604     //tpl: [
91605     //    '<tpl for="pages">',
91606     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
91607     //    '</tpl>'
91608     //],
91609     /**
91610      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
91611      * at what percentage to begin fetching the next page. For example if the pageSize is 100
91612      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
91613      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
91614      */
91615     percentageFromEdge: 0.35,
91616
91617     /**
91618      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
91619      * when scrolling the PagingScrollbar.
91620      */
91621     scrollToLoadBuffer: 200,
91622
91623     activePrefetch: true,
91624
91625     chunkSize: 50,
91626     snapIncrement: 25,
91627
91628     syncScroll: true,
91629
91630     initComponent: function() {
91631         var me = this,
91632             ds = me.store;
91633
91634         ds.on('guaranteedrange', me.onGuaranteedRange, me);
91635         me.callParent(arguments);
91636     },
91637
91638     onGuaranteedRange: function(range, start, end) {
91639         var me = this,
91640             ds = me.store,
91641             rs;
91642         // this should never happen
91643         if (range.length && me.visibleStart < range[0].index) {
91644             return;
91645         }
91646
91647         ds.loadRecords(range);
91648
91649         if (!me.firstLoad) {
91650             if (me.rendered) {
91651                 me.invalidate();
91652             } else {
91653                 me.on('afterrender', me.invalidate, me, {single: true});
91654             }
91655             me.firstLoad = true;
91656         } else {
91657             // adjust to visible
91658             // only sync if there is a paging scrollbar element and it has a scroll height (meaning it's currently in the DOM)
91659             if (me.scrollEl && me.scrollEl.dom && me.scrollEl.dom.scrollHeight) {
91660                 me.syncTo();
91661             }
91662         }
91663     },
91664
91665     syncTo: function() {
91666         var me            = this,
91667             pnl           = me.getPanel(),
91668             store         = pnl.store,
91669             scrollerElDom = this.scrollEl.dom,
91670             rowOffset     = me.visibleStart - store.guaranteedStart,
91671             scrollBy      = rowOffset * me.rowHeight,
91672             scrollHeight  = scrollerElDom.scrollHeight,
91673             clientHeight  = scrollerElDom.clientHeight,
91674             scrollTop     = scrollerElDom.scrollTop,
91675             useMaximum;
91676             
91677
91678         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
91679         // Instead we are using offsetHeight and hardcoding borders
91680         if (Ext.isIE9 && Ext.isStrict) {
91681             clientHeight = scrollerElDom.offsetHeight + 2;
91682         }
91683
91684         // This should always be zero or greater than zero but staying
91685         // safe and less than 0 we'll scroll to the bottom.
91686         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
91687         this.setViewScrollTop(scrollBy, useMaximum);
91688     },
91689
91690     getPageData : function(){
91691         var panel = this.getPanel(),
91692             store = panel.store,
91693             totalCount = store.getTotalCount();
91694
91695         return {
91696             total : totalCount,
91697             currentPage : store.currentPage,
91698             pageCount: Math.ceil(totalCount / store.pageSize),
91699             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
91700             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
91701         };
91702     },
91703
91704     onElScroll: function(e, t) {
91705         var me = this,
91706             panel = me.getPanel(),
91707             store = panel.store,
91708             pageSize = store.pageSize,
91709             guaranteedStart = store.guaranteedStart,
91710             guaranteedEnd = store.guaranteedEnd,
91711             totalCount = store.getTotalCount(),
91712             numFromEdge = Math.ceil(me.percentageFromEdge * pageSize),
91713             position = t.scrollTop,
91714             visibleStart = Math.floor(position / me.rowHeight),
91715             view = panel.down('tableview'),
91716             viewEl = view.el,
91717             visibleHeight = viewEl.getHeight(),
91718             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
91719             visibleEnd = visibleStart + visibleAhead,
91720             prevPage = Math.floor(visibleStart / pageSize),
91721             nextPage = Math.floor(visibleEnd / pageSize) + 2,
91722             lastPage = Math.ceil(totalCount / pageSize),
91723             snap = me.snapIncrement,
91724             requestStart = Math.floor(visibleStart / snap) * snap,
91725             requestEnd = requestStart + pageSize - 1,
91726             activePrefetch = me.activePrefetch;
91727
91728         me.visibleStart = visibleStart;
91729         me.visibleEnd = visibleEnd;
91730         
91731         
91732         me.syncScroll = true;
91733         if (totalCount >= pageSize) {
91734             // end of request was past what the total is, grab from the end back a pageSize
91735             if (requestEnd > totalCount - 1) {
91736                 me.cancelLoad();
91737                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
91738                     me.syncScroll = true;
91739                 }
91740                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
91741             // Out of range, need to reset the current data set
91742             } else if (visibleStart <= guaranteedStart || visibleEnd > guaranteedEnd) {
91743                 if (visibleStart <= guaranteedStart) {
91744                     // need to scroll up
91745                     requestStart -= snap;
91746                     requestEnd -= snap;
91747                     
91748                     if (requestStart < 0) {
91749                         requestStart = 0;
91750                         requestEnd = pageSize;
91751                     }
91752                 }
91753                 if (store.rangeSatisfied(requestStart, requestEnd)) {
91754                     me.cancelLoad();
91755                     store.guaranteeRange(requestStart, requestEnd);
91756                 } else {
91757                     store.mask();
91758                     me.attemptLoad(requestStart, requestEnd);
91759                 }
91760                 // dont sync the scroll view immediately, sync after the range has been guaranteed
91761                 me.syncScroll = false;
91762             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
91763                 me.syncScroll = true;
91764                 store.prefetchPage(prevPage);
91765             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
91766                 me.syncScroll = true;
91767                 store.prefetchPage(nextPage);
91768             }
91769         }
91770
91771         if (me.syncScroll) {
91772             me.syncTo();
91773         }
91774     },
91775
91776     getSizeCalculation: function() {
91777         // Use the direct ownerCt here rather than the scrollerOwner
91778         // because we are calculating widths/heights.
91779         var me     = this,
91780             owner  = me.ownerGrid,
91781             view   = owner.getView(),
91782             store  = me.store,
91783             dock   = me.dock,
91784             elDom  = me.el.dom,
91785             width  = 1,
91786             height = 1;
91787
91788         if (!me.rowHeight) {
91789             me.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
91790         }
91791
91792         // If the Store is *locally* filtered, use the filtered count from getCount.
91793         height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * me.rowHeight;
91794
91795         if (isNaN(width)) {
91796             width = 1;
91797         }
91798         if (isNaN(height)) {
91799             height = 1;
91800         }
91801         return {
91802             width: width,
91803             height: height
91804         };
91805     },
91806
91807     attemptLoad: function(start, end) {
91808         var me = this;
91809         if (!me.loadTask) {
91810             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
91811         }
91812         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
91813     },
91814
91815     cancelLoad: function() {
91816         if (this.loadTask) {
91817             this.loadTask.cancel();
91818         }
91819     },
91820
91821     doAttemptLoad:  function(start, end) {
91822         var store = this.getPanel().store;
91823         store.guaranteeRange(start, end);
91824     },
91825
91826     setViewScrollTop: function(scrollTop, useMax) {
91827         var me = this,
91828             owner = me.getPanel(),
91829             items = owner.query('tableview'),
91830             i = 0,
91831             len = items.length,
91832             center,
91833             centerEl,
91834             calcScrollTop,
91835             maxScrollTop,
91836             scrollerElDom = me.el.dom;
91837
91838         owner.virtualScrollTop = scrollTop;
91839
91840         center = items[1] || items[0];
91841         centerEl = center.el.dom;
91842
91843         maxScrollTop = ((owner.store.pageSize * me.rowHeight) - centerEl.clientHeight);
91844         calcScrollTop = (scrollTop % ((owner.store.pageSize * me.rowHeight) + 1));
91845         if (useMax) {
91846             calcScrollTop = maxScrollTop;
91847         }
91848         if (calcScrollTop > maxScrollTop) {
91849             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
91850             return;
91851             // calcScrollTop = maxScrollTop;
91852         }
91853         for (; i < len; i++) {
91854             items[i].el.dom.scrollTop = calcScrollTop;
91855         }
91856     }
91857 });
91858
91859 /**
91860  * @author Nicolas Ferrero
91861  *
91862  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
91863  *
91864  * TablePanel aggregates:
91865  *
91866  *  - a Selection Model
91867  *  - a View
91868  *  - a Store
91869  *  - Scrollers
91870  *  - Ext.grid.header.Container
91871  */
91872 Ext.define('Ext.panel.Table', {
91873     extend: 'Ext.panel.Panel',
91874
91875     alias: 'widget.tablepanel',
91876
91877     uses: [
91878         'Ext.selection.RowModel',
91879         'Ext.grid.Scroller',
91880         'Ext.grid.header.Container',
91881         'Ext.grid.Lockable'
91882     ],
91883
91884     extraBaseCls: Ext.baseCSSPrefix + 'grid',
91885     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
91886
91887     layout: 'fit',
91888     /**
91889      * @property {Boolean} hasView
91890      * True to indicate that a view has been injected into the panel.
91891      */
91892     hasView: false,
91893
91894     // each panel should dictate what viewType and selType to use
91895     /**
91896      * @cfg {String} viewType
91897      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
91898      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
91899      */
91900     viewType: null,
91901
91902     /**
91903      * @cfg {Object} viewConfig
91904      * A config object that will be applied to the grid's UI view. Any of the config options available for
91905      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
91906      */
91907
91908     /**
91909      * @cfg {Ext.view.Table} view
91910      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
91911      * view (instead of creating an entire View instance).
91912      */
91913
91914     /**
91915      * @cfg {String} selType
91916      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
91917      * a config object or nothing at all given in {@link #selModel} config.
91918      */
91919     selType: 'rowmodel',
91920
91921     /**
91922      * @cfg {Ext.selection.Model/Object} selModel
91923      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
91924      * config option determines to which type of selection model this config is applied.
91925      */
91926
91927     /**
91928      * @cfg {Boolean} multiSelect
91929      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
91930      */
91931
91932     /**
91933      * @cfg {Boolean} simpleSelect
91934      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
91935      */
91936
91937     /**
91938      * @cfg {Ext.data.Store} store (required)
91939      * The {@link Ext.data.Store Store} the grid should use as its data source.
91940      */
91941
91942     /**
91943      * @cfg {Number} scrollDelta
91944      * Number of pixels to scroll when scrolling with mousewheel.
91945      */
91946     scrollDelta: 40,
91947
91948     /**
91949      * @cfg {String/Boolean} scroll
91950      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
91951      * True implies 'both'. False implies 'none'.
91952      */
91953     scroll: true,
91954
91955     /**
91956      * @cfg {Ext.grid.column.Column[]} columns
91957      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
91958      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
91959      * column comes from.
91960      */
91961
91962     /**
91963      * @cfg {Boolean} forceFit
91964      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
91965      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
91966      * content width is used.
91967      */
91968
91969     /**
91970      * @cfg {Ext.grid.feature.Feature[]} features
91971      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
91972      */
91973
91974     /**
91975      * @cfg {Boolean} [hideHeaders=false]
91976      * True to hide column headers.
91977      */
91978
91979     /**
91980      * @cfg {Boolean} deferRowRender
91981      * Defaults to true to enable deferred row rendering.
91982      *
91983      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
91984      * that layouts with GridPanels appear, and lay out more quickly.
91985      */
91986
91987      deferRowRender: true,
91988      
91989     /**
91990      * @cfg {Boolean} sortableColumns
91991      * False to disable column sorting via clicking the header and via the Sorting menu items.
91992      */
91993     sortableColumns: true,
91994
91995     /**
91996      * @cfg {Boolean} [enableLocking=false]
91997      * True to enable locking support for this grid. Alternatively, locking will also be automatically
91998      * enabled if any of the columns in the column configuration contain the locked config option.
91999      */
92000     enableLocking: false,
92001
92002     verticalScrollDock: 'right',
92003     verticalScrollerType: 'gridscroller',
92004
92005     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
92006     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
92007
92008     // private property used to determine where to go down to find views
92009     // this is here to support locking.
92010     scrollerOwner: true,
92011
92012     invalidateScrollerOnRefresh: true,
92013
92014     /**
92015      * @cfg {Boolean} enableColumnMove
92016      * False to disable column dragging within this grid.
92017      */
92018     enableColumnMove: true,
92019
92020     /**
92021      * @cfg {Boolean} enableColumnResize
92022      * False to disable column resizing within this grid.
92023      */
92024     enableColumnResize: true,
92025
92026     /**
92027      * @cfg {Boolean} enableColumnHide
92028      * False to disable column hiding within this grid.
92029      */
92030     enableColumnHide: true,
92031
92032     initComponent: function() {
92033         //<debug>
92034         if (!this.viewType) {
92035             Ext.Error.raise("You must specify a viewType config.");
92036         }
92037         if (this.headers) {
92038             Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
92039         }
92040         //</debug>
92041
92042         var me          = this,
92043             scroll      = me.scroll,
92044             vertical    = false,
92045             horizontal  = false,
92046             headerCtCfg = me.columns || me.colModel,
92047             i           = 0,
92048             view,
92049             border = me.border;
92050
92051         if (me.hideHeaders) {
92052             border = false;
92053         }
92054
92055         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
92056         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
92057
92058         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
92059         // Either way, we extract a columns property referencing an array of Column definitions.
92060         if (headerCtCfg instanceof Ext.grid.header.Container) {
92061             me.headerCt = headerCtCfg;
92062             me.headerCt.border = border;
92063             me.columns = me.headerCt.items.items;
92064         } else {
92065             if (Ext.isArray(headerCtCfg)) {
92066                 headerCtCfg = {
92067                     items: headerCtCfg,
92068                     border: border
92069                 };
92070             }
92071             Ext.apply(headerCtCfg, {
92072                 forceFit: me.forceFit,
92073                 sortable: me.sortableColumns,
92074                 enableColumnMove: me.enableColumnMove,
92075                 enableColumnResize: me.enableColumnResize,
92076                 enableColumnHide: me.enableColumnHide,
92077                 border:  border
92078             });
92079             me.columns = headerCtCfg.items;
92080
92081              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
92082              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
92083              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
92084                  me.self.mixin('lockable', Ext.grid.Lockable);
92085                  me.injectLockable();
92086              }
92087         }
92088
92089         me.addEvents(
92090             /**
92091              * @event reconfigure
92092              * Fires after a reconfigure.
92093              * @param {Ext.panel.Table} this
92094              */
92095             'reconfigure',
92096             /**
92097              * @event viewready
92098              * Fires when the grid view is available (use this for selecting a default row).
92099              * @param {Ext.panel.Table} this
92100              */
92101             'viewready',
92102             /**
92103              * @event scrollerhide
92104              * Fires when a scroller is hidden.
92105              * @param {Ext.grid.Scroller} scroller
92106              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
92107              */
92108             'scrollerhide',
92109             /**
92110              * @event scrollershow
92111              * Fires when a scroller is shown.
92112              * @param {Ext.grid.Scroller} scroller
92113              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
92114              */
92115             'scrollershow'
92116         );
92117
92118         me.bodyCls = me.bodyCls || '';
92119         me.bodyCls += (' ' + me.extraBodyCls);
92120         
92121         me.cls = me.cls || '';
92122         me.cls += (' ' + me.extraBaseCls);
92123
92124         // autoScroll is not a valid configuration
92125         delete me.autoScroll;
92126
92127         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
92128         // 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.
92129         if (!me.hasView) {
92130
92131             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
92132             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
92133             if (!me.headerCt) {
92134                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
92135             }
92136
92137             // Extract the array of Column objects
92138             me.columns = me.headerCt.items.items;
92139
92140             if (me.hideHeaders) {
92141                 me.headerCt.height = 0;
92142                 me.headerCt.border = false;
92143                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
92144                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
92145                 // IE Quirks Mode fix
92146                 // If hidden configuration option was used, several layout calculations will be bypassed.
92147                 if (Ext.isIEQuirks) {
92148                     me.headerCt.style = {
92149                         display: 'none'
92150                     };
92151                 }
92152             }
92153
92154             // turn both on.
92155             if (scroll === true || scroll === 'both') {
92156                 vertical = horizontal = true;
92157             } else if (scroll === 'horizontal') {
92158                 horizontal = true;
92159             } else if (scroll === 'vertical') {
92160                 vertical = true;
92161             // All other values become 'none' or false.
92162             } else {
92163                 me.headerCt.availableSpaceOffset = 0;
92164             }
92165
92166             if (vertical) {
92167                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
92168                 me.mon(me.verticalScroller, {
92169                     bodyscroll: me.onVerticalScroll,
92170                     scope: me
92171                 });
92172             }
92173
92174             if (horizontal) {
92175                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
92176                 me.mon(me.horizontalScroller, {
92177                     bodyscroll: me.onHorizontalScroll,
92178                     scope: me
92179                 });
92180             }
92181
92182             me.headerCt.on('resize', me.onHeaderResize, me);
92183             me.relayHeaderCtEvents(me.headerCt);
92184             me.features = me.features || [];
92185             if (!Ext.isArray(me.features)) {
92186                 me.features = [me.features];
92187             }
92188             me.dockedItems = me.dockedItems || [];
92189             me.dockedItems.unshift(me.headerCt);
92190             me.viewConfig = me.viewConfig || {};
92191             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
92192
92193             // AbstractDataView will look up a Store configured as an object
92194             // getView converts viewConfig into a View instance
92195             view = me.getView();
92196
92197             view.on({
92198                 afterrender: function () {
92199                     // hijack the view el's scroll method
92200                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
92201                     // We use to listen to document.body wheel events, but that's a
92202                     // little much. We scope just to the view now.
92203                     me.mon(view.el, {
92204                         mousewheel: me.onMouseWheel,
92205                         scope: me
92206                     });
92207                 },
92208                 single: true
92209             });
92210             me.items = [view];
92211             me.hasView = true;
92212
92213             me.mon(view.store, {
92214                 load: me.onStoreLoad,
92215                 scope: me
92216             });
92217             me.mon(view, {
92218                 viewReady: me.onViewReady,
92219                 resize: me.onViewResize,
92220                 refresh: {
92221                     fn: me.onViewRefresh,
92222                     scope: me,
92223                     buffer: 50
92224                 },
92225                 scope: me
92226             });
92227             this.relayEvents(view, [
92228                 /**
92229                  * @event beforeitemmousedown
92230                  * @alias Ext.view.View#beforeitemmousedown
92231                  */
92232                 'beforeitemmousedown',
92233                 /**
92234                  * @event beforeitemmouseup
92235                  * @alias Ext.view.View#beforeitemmouseup
92236                  */
92237                 'beforeitemmouseup',
92238                 /**
92239                  * @event beforeitemmouseenter
92240                  * @alias Ext.view.View#beforeitemmouseenter
92241                  */
92242                 'beforeitemmouseenter',
92243                 /**
92244                  * @event beforeitemmouseleave
92245                  * @alias Ext.view.View#beforeitemmouseleave
92246                  */
92247                 'beforeitemmouseleave',
92248                 /**
92249                  * @event beforeitemclick
92250                  * @alias Ext.view.View#beforeitemclick
92251                  */
92252                 'beforeitemclick',
92253                 /**
92254                  * @event beforeitemdblclick
92255                  * @alias Ext.view.View#beforeitemdblclick
92256                  */
92257                 'beforeitemdblclick',
92258                 /**
92259                  * @event beforeitemcontextmenu
92260                  * @alias Ext.view.View#beforeitemcontextmenu
92261                  */
92262                 'beforeitemcontextmenu',
92263                 /**
92264                  * @event itemmousedown
92265                  * @alias Ext.view.View#itemmousedown
92266                  */
92267                 'itemmousedown',
92268                 /**
92269                  * @event itemmouseup
92270                  * @alias Ext.view.View#itemmouseup
92271                  */
92272                 'itemmouseup',
92273                 /**
92274                  * @event itemmouseenter
92275                  * @alias Ext.view.View#itemmouseenter
92276                  */
92277                 'itemmouseenter',
92278                 /**
92279                  * @event itemmouseleave
92280                  * @alias Ext.view.View#itemmouseleave
92281                  */
92282                 'itemmouseleave',
92283                 /**
92284                  * @event itemclick
92285                  * @alias Ext.view.View#itemclick
92286                  */
92287                 'itemclick',
92288                 /**
92289                  * @event itemdblclick
92290                  * @alias Ext.view.View#itemdblclick
92291                  */
92292                 'itemdblclick',
92293                 /**
92294                  * @event itemcontextmenu
92295                  * @alias Ext.view.View#itemcontextmenu
92296                  */
92297                 'itemcontextmenu',
92298                 /**
92299                  * @event beforecontainermousedown
92300                  * @alias Ext.view.View#beforecontainermousedown
92301                  */
92302                 'beforecontainermousedown',
92303                 /**
92304                  * @event beforecontainermouseup
92305                  * @alias Ext.view.View#beforecontainermouseup
92306                  */
92307                 'beforecontainermouseup',
92308                 /**
92309                  * @event beforecontainermouseover
92310                  * @alias Ext.view.View#beforecontainermouseover
92311                  */
92312                 'beforecontainermouseover',
92313                 /**
92314                  * @event beforecontainermouseout
92315                  * @alias Ext.view.View#beforecontainermouseout
92316                  */
92317                 'beforecontainermouseout',
92318                 /**
92319                  * @event beforecontainerclick
92320                  * @alias Ext.view.View#beforecontainerclick
92321                  */
92322                 'beforecontainerclick',
92323                 /**
92324                  * @event beforecontainerdblclick
92325                  * @alias Ext.view.View#beforecontainerdblclick
92326                  */
92327                 'beforecontainerdblclick',
92328                 /**
92329                  * @event beforecontainercontextmenu
92330                  * @alias Ext.view.View#beforecontainercontextmenu
92331                  */
92332                 'beforecontainercontextmenu',
92333                 /**
92334                  * @event containermouseup
92335                  * @alias Ext.view.View#containermouseup
92336                  */
92337                 'containermouseup',
92338                 /**
92339                  * @event containermouseover
92340                  * @alias Ext.view.View#containermouseover
92341                  */
92342                 'containermouseover',
92343                 /**
92344                  * @event containermouseout
92345                  * @alias Ext.view.View#containermouseout
92346                  */
92347                 'containermouseout',
92348                 /**
92349                  * @event containerclick
92350                  * @alias Ext.view.View#containerclick
92351                  */
92352                 'containerclick',
92353                 /**
92354                  * @event containerdblclick
92355                  * @alias Ext.view.View#containerdblclick
92356                  */
92357                 'containerdblclick',
92358                 /**
92359                  * @event containercontextmenu
92360                  * @alias Ext.view.View#containercontextmenu
92361                  */
92362                 'containercontextmenu',
92363                 /**
92364                  * @event selectionchange
92365                  * @alias Ext.selection.Model#selectionchange
92366                  */
92367                 'selectionchange',
92368                 /**
92369                  * @event beforeselect
92370                  * @alias Ext.selection.RowModel#beforeselect
92371                  */
92372                 'beforeselect',
92373                 /**
92374                  * @event select
92375                  * @alias Ext.selection.RowModel#select
92376                  */
92377                 'select',
92378                 /**
92379                  * @event beforedeselect
92380                  * @alias Ext.selection.RowModel#beforedeselect
92381                  */
92382                 'beforedeselect',
92383                 /**
92384                  * @event deselect
92385                  * @alias Ext.selection.RowModel#deselect
92386                  */
92387                 'deselect'
92388             ]);
92389         }
92390
92391         me.callParent(arguments);
92392     },
92393     
92394     onRender: function(){
92395         var vScroll = this.verticalScroller,
92396             hScroll = this.horizontalScroller;
92397
92398         if (vScroll) {
92399             vScroll.ensureDimension();
92400         }
92401         if (hScroll) {
92402             hScroll.ensureDimension();
92403         }
92404         this.callParent(arguments);    
92405     },
92406
92407     // state management
92408     initStateEvents: function(){
92409         var events = this.stateEvents;
92410         // push on stateEvents if they don't exist
92411         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
92412             if (Ext.Array.indexOf(events, event)) {
92413                 events.push(event);
92414             }
92415         });
92416         this.callParent();
92417     },
92418
92419     /**
92420      * Returns the horizontal scroller config.
92421      */
92422     initHorizontalScroller: function () {
92423         var me = this,
92424             ret = {
92425                 xtype: 'gridscroller',
92426                 dock: 'bottom',
92427                 section: me,
92428                 store: me.store
92429             };
92430
92431         return ret;
92432     },
92433
92434     /**
92435      * Returns the vertical scroller config.
92436      */
92437     initVerticalScroller: function () {
92438         var me = this,
92439             ret = me.verticalScroller || {};
92440
92441         Ext.applyIf(ret, {
92442             xtype: me.verticalScrollerType,
92443             dock: me.verticalScrollDock,
92444             store: me.store
92445         });
92446
92447         return ret;
92448     },
92449
92450     relayHeaderCtEvents: function (headerCt) {
92451         this.relayEvents(headerCt, [
92452             /**
92453              * @event columnresize
92454              * @alias Ext.grid.header.Container#columnresize
92455              */
92456             'columnresize',
92457             /**
92458              * @event columnmove
92459              * @alias Ext.grid.header.Container#columnmove
92460              */
92461             'columnmove',
92462             /**
92463              * @event columnhide
92464              * @alias Ext.grid.header.Container#columnhide
92465              */
92466             'columnhide',
92467             /**
92468              * @event columnshow
92469              * @alias Ext.grid.header.Container#columnshow
92470              */
92471             'columnshow',
92472             /**
92473              * @event sortchange
92474              * @alias Ext.grid.header.Container#sortchange
92475              */
92476             'sortchange'
92477         ]);
92478     },
92479
92480     getState: function(){
92481         var me = this,
92482             state = me.callParent(),
92483             sorter = me.store.sorters.first();
92484
92485         state.columns = (me.headerCt || me).getColumnsState();
92486
92487         if (sorter) {
92488             state.sort = {
92489                 property: sorter.property,
92490                 direction: sorter.direction
92491             };
92492         }
92493
92494         return state;
92495     },
92496
92497     applyState: function(state) {
92498         var me = this,
92499             sorter = state.sort,
92500             store = me.store,
92501             columns = state.columns;
92502
92503         delete state.columns;
92504
92505         // Ensure superclass has applied *its* state.
92506         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
92507         me.callParent(arguments);
92508
92509         if (columns) {
92510             (me.headerCt || me).applyColumnsState(columns);
92511         }
92512
92513         if (sorter) {
92514             if (store.remoteSort) {
92515                 store.sorters.add(Ext.create('Ext.util.Sorter', {
92516                     property: sorter.property,
92517                     direction: sorter.direction
92518                 }));
92519             }
92520             else {
92521                 store.sort(sorter.property, sorter.direction);
92522             }
92523         }
92524     },
92525
92526     /**
92527      * Returns the store associated with this Panel.
92528      * @return {Ext.data.Store} The store
92529      */
92530     getStore: function(){
92531         return this.store;
92532     },
92533
92534     /**
92535      * Gets the view for this panel.
92536      * @return {Ext.view.Table}
92537      */
92538     getView: function() {
92539         var me = this,
92540             sm;
92541
92542         if (!me.view) {
92543             sm = me.getSelectionModel();
92544             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
92545                 deferInitialRefresh: me.deferRowRender,
92546                 xtype: me.viewType,
92547                 store: me.store,
92548                 headerCt: me.headerCt,
92549                 selModel: sm,
92550                 features: me.features,
92551                 panel: me
92552             }));
92553             me.mon(me.view, {
92554                 uievent: me.processEvent,
92555                 scope: me
92556             });
92557             sm.view = me.view;
92558             me.headerCt.view = me.view;
92559             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
92560         }
92561         return me.view;
92562     },
92563
92564     /**
92565      * @private
92566      * @override
92567      * autoScroll is never valid for all classes which extend TablePanel.
92568      */
92569     setAutoScroll: Ext.emptyFn,
92570
92571     // This method hijacks Ext.view.Table's el scroll method.
92572     // This enables us to keep the virtualized scrollbars in sync
92573     // with the view. It currently does NOT support animation.
92574     elScroll: function(direction, distance, animate) {
92575         var me = this,
92576             scroller;
92577
92578         if (direction === "up" || direction === "left") {
92579             distance = -distance;
92580         }
92581         
92582         if (direction === "down" || direction === "up") {
92583             scroller = me.getVerticalScroller();
92584             
92585             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
92586             if (scroller) {
92587                 scroller.scrollByDeltaY(distance);
92588             }
92589         } else {
92590             scroller = me.getHorizontalScroller();
92591             
92592             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
92593             if (scroller) {
92594                 scroller.scrollByDeltaX(distance);
92595             }
92596         }
92597     },
92598
92599     /**
92600      * @private
92601      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
92602      * @param {String} type Event type, eg 'click'
92603      * @param {Ext.view.Table} view TableView Component
92604      * @param {HTMLElement} cell Cell HtmlElement the event took place within
92605      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
92606      * @param {Number} cellIndex Cell index within the row
92607      * @param {Ext.EventObject} e Original event
92608      */
92609     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
92610         var me = this,
92611             header;
92612
92613         if (cellIndex !== -1) {
92614             header = me.headerCt.getGridColumns()[cellIndex];
92615             return header.processEvent.apply(header, arguments);
92616         }
92617     },
92618
92619     /**
92620      * Requests a recalculation of scrollbars and puts them in if they are needed.
92621      */
92622     determineScrollbars: function() {
92623         // Set a flag so that afterComponentLayout does not recurse back into here.
92624         if (this.determineScrollbarsRunning) {
92625             return;
92626         }
92627         this.determineScrollbarsRunning = true;
92628         var me = this,
92629             view = me.view,
92630             box,
92631             tableEl,
92632             scrollWidth,
92633             clientWidth,
92634             scrollHeight,
92635             clientHeight,
92636             verticalScroller = me.verticalScroller,
92637             horizontalScroller = me.horizontalScroller,
92638             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
92639                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
92640             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
92641
92642         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
92643         if (!me.collapsed && view && view.viewReady) {
92644
92645             // Calculate maximum, *scrollbarless* space which the view has available.
92646             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
92647             box = view.el.getSize();
92648
92649             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
92650             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
92651
92652             // Calculate the width of the scrolling block
92653             // There will never be a horizontal scrollbar if all columns are flexed.
92654
92655             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
92656
92657             // Calculate the height of the scrolling block
92658             if (verticalScroller && verticalScroller.el) {
92659                 scrollHeight = verticalScroller.getSizeCalculation().height;
92660             } else {
92661                 tableEl = view.el.child('table', true);
92662                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
92663             }
92664
92665             // View is too high.
92666             // Definitely need a vertical scrollbar
92667             if (scrollHeight > clientHeight) {
92668                 reqScrollbars = 1;
92669
92670                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
92671                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
92672                     reqScrollbars = 3;
92673                 }
92674             }
92675
92676             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
92677             else {
92678                 // View is too wide.
92679                 // Definitely need a horizontal scrollbar
92680                 if (scrollWidth > clientWidth) {
92681                     reqScrollbars = 2;
92682
92683                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
92684                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
92685                         reqScrollbars = 3;
92686                     }
92687                 }
92688             }
92689
92690             // If scrollbar requirements have changed, change 'em...
92691             if (reqScrollbars !== curScrollbars) {
92692
92693                 // Suspend component layout while we add/remove the docked scrollers
92694                 me.suspendLayout = true;
92695                 if (reqScrollbars & 1) {
92696                     me.showVerticalScroller();
92697                 } else {
92698                     me.hideVerticalScroller();
92699                 }
92700                 if (reqScrollbars & 2) {
92701                     me.showHorizontalScroller();
92702                 } else {
92703                     me.hideHorizontalScroller();
92704                 }
92705                 me.suspendLayout = false;
92706
92707                 // Lay out the Component.
92708                 me.doComponentLayout();
92709                 // Lay out me.items
92710                 me.getLayout().layout();
92711             }
92712         }
92713         delete me.determineScrollbarsRunning;
92714     },
92715
92716     onViewResize: function() {
92717         this.determineScrollbars();
92718     },
92719
92720     afterComponentLayout: function() {
92721         this.callParent(arguments);
92722         this.determineScrollbars();
92723         this.invalidateScroller();
92724     },
92725
92726     onHeaderResize: function() {
92727         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
92728             this.determineScrollbars();
92729             this.invalidateScroller();
92730         }
92731     },
92732
92733     afterCollapse: function() {
92734         var me = this;
92735         if (me.verticalScroller) {
92736             me.verticalScroller.saveScrollPos();
92737         }
92738         if (me.horizontalScroller) {
92739             me.horizontalScroller.saveScrollPos();
92740         }
92741         me.callParent(arguments);
92742     },
92743
92744     afterExpand: function() {
92745         var me = this;
92746         me.callParent(arguments);
92747         if (me.verticalScroller) {
92748             me.verticalScroller.restoreScrollPos();
92749         }
92750         if (me.horizontalScroller) {
92751             me.horizontalScroller.restoreScrollPos();
92752         }
92753     },
92754
92755     /**
92756      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
92757      */
92758     hideHorizontalScroller: function() {
92759         var me = this;
92760
92761         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
92762             me.verticalScroller.setReservedSpace(0);
92763             me.removeDocked(me.horizontalScroller, false);
92764             me.removeCls(me.horizontalScrollerPresentCls);
92765             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
92766         }
92767
92768     },
92769
92770     /**
92771      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
92772      */
92773     showHorizontalScroller: function() {
92774         var me = this;
92775
92776         if (me.verticalScroller) {
92777             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
92778         }
92779         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
92780             me.addDocked(me.horizontalScroller);
92781             me.addCls(me.horizontalScrollerPresentCls);
92782             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
92783         }
92784     },
92785
92786     /**
92787      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
92788      */
92789     hideVerticalScroller: function() {
92790         var me = this;
92791
92792         me.setHeaderReserveOffset(false);
92793         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
92794             me.removeDocked(me.verticalScroller, false);
92795             me.removeCls(me.verticalScrollerPresentCls);
92796             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
92797         }
92798     },
92799
92800     /**
92801      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
92802      */
92803     showVerticalScroller: function() {
92804         var me = this;
92805
92806         me.setHeaderReserveOffset(true);
92807         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
92808             me.addDocked(me.verticalScroller);
92809             me.addCls(me.verticalScrollerPresentCls);
92810             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
92811         }
92812     },
92813
92814     setHeaderReserveOffset: function (reserveOffset) {
92815         var headerCt = this.headerCt,
92816             layout = headerCt.layout;
92817
92818         // only trigger a layout when reserveOffset is changing
92819         if (layout && layout.reserveOffset !== reserveOffset) {
92820             layout.reserveOffset = reserveOffset;
92821             if (!this.suspendLayout) {
92822                 headerCt.doLayout();
92823             }
92824         }
92825     },
92826
92827     /**
92828      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
92829      */
92830     invalidateScroller: function() {
92831         var me = this,
92832             vScroll = me.verticalScroller,
92833             hScroll = me.horizontalScroller;
92834
92835         if (vScroll) {
92836             vScroll.invalidate();
92837         }
92838         if (hScroll) {
92839             hScroll.invalidate();
92840         }
92841     },
92842
92843     // refresh the view when a header moves
92844     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
92845         this.view.refresh();
92846     },
92847
92848     // Section onHeaderHide is invoked after view.
92849     onHeaderHide: function(headerCt, header) {
92850         this.invalidateScroller();
92851     },
92852
92853     onHeaderShow: function(headerCt, header) {
92854         this.invalidateScroller();
92855     },
92856
92857     getVerticalScroller: function() {
92858         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
92859     },
92860
92861     getHorizontalScroller: function() {
92862         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
92863     },
92864
92865     onMouseWheel: function(e) {
92866         var me = this,
92867             vertScroller = me.getVerticalScroller(),
92868             horizScroller = me.getHorizontalScroller(),
92869             scrollDelta = -me.scrollDelta,
92870             deltas = e.getWheelDeltas(),
92871             deltaX = scrollDelta * deltas.x,
92872             deltaY = scrollDelta * deltas.y,
92873             vertScrollerEl, horizScrollerEl,
92874             vertScrollerElDom, horizScrollerElDom,
92875             horizontalCanScrollLeft, horizontalCanScrollRight,
92876             verticalCanScrollDown, verticalCanScrollUp;
92877
92878         // calculate whether or not both scrollbars can scroll right/left and up/down
92879         if (horizScroller) {
92880             horizScrollerEl = horizScroller.scrollEl;
92881             if (horizScrollerEl) {
92882                 horizScrollerElDom = horizScrollerEl.dom;
92883                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
92884                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
92885             }
92886         }
92887         if (vertScroller) {
92888             vertScrollerEl = vertScroller.scrollEl;
92889             if (vertScrollerEl) {
92890                 vertScrollerElDom = vertScrollerEl.dom;
92891                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
92892                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
92893             }
92894         }
92895
92896         if (horizScroller) {
92897             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
92898                 e.stopEvent();
92899                 horizScroller.scrollByDeltaX(deltaX);
92900             }
92901         }
92902         if (vertScroller) {
92903             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
92904                 e.stopEvent();
92905                 vertScroller.scrollByDeltaY(deltaY);
92906             }
92907         }
92908     },
92909
92910     /**
92911      * @private
92912      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
92913      */
92914     onViewReady: function() {
92915         var me = this;
92916         me.fireEvent('viewready', me);
92917         if (me.deferRowRender) {
92918             me.determineScrollbars();
92919             me.invalidateScroller();
92920         }
92921     },
92922
92923     /**
92924      * @private
92925      * Determines and invalidates scrollers on view refresh
92926      */
92927     onViewRefresh: function() {
92928         var me = this;
92929
92930         // Refresh *during* render must be ignored.
92931         if (!me.rendering) {
92932             this.determineScrollbars();
92933             if (this.invalidateScrollerOnRefresh) {
92934                 this.invalidateScroller();
92935             }
92936         }
92937     },
92938
92939     /**
92940      * Sets the scrollTop of the TablePanel.
92941      * @param {Number} top
92942      */
92943     setScrollTop: function(top) {
92944         var me               = this,
92945             rootCmp          = me.getScrollerOwner(),
92946             verticalScroller = me.getVerticalScroller();
92947
92948         rootCmp.virtualScrollTop = top;
92949         if (verticalScroller) {
92950             verticalScroller.setScrollTop(top);
92951         }
92952     },
92953
92954     getScrollerOwner: function() {
92955         var rootCmp = this;
92956         if (!this.scrollerOwner) {
92957             rootCmp = this.up('[scrollerOwner]');
92958         }
92959         return rootCmp;
92960     },
92961
92962     /**
92963      * Scrolls the TablePanel by deltaY
92964      * @param {Number} deltaY
92965      */
92966     scrollByDeltaY: function(deltaY) {
92967         var verticalScroller = this.getVerticalScroller();
92968
92969         if (verticalScroller) {
92970             verticalScroller.scrollByDeltaY(deltaY);
92971         }
92972     },
92973
92974     /**
92975      * Scrolls the TablePanel by deltaX
92976      * @param {Number} deltaX
92977      */
92978     scrollByDeltaX: function(deltaX) {
92979         var horizontalScroller = this.getHorizontalScroller();
92980
92981         if (horizontalScroller) {
92982             horizontalScroller.scrollByDeltaX(deltaX);
92983         }
92984     },
92985
92986     /**
92987      * Gets left hand side marker for header resizing.
92988      * @private
92989      */
92990     getLhsMarker: function() {
92991         var me = this;
92992
92993         if (!me.lhsMarker) {
92994             me.lhsMarker = Ext.DomHelper.append(me.el, {
92995                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
92996             }, true);
92997         }
92998         return me.lhsMarker;
92999     },
93000
93001     /**
93002      * Gets right hand side marker for header resizing.
93003      * @private
93004      */
93005     getRhsMarker: function() {
93006         var me = this;
93007
93008         if (!me.rhsMarker) {
93009             me.rhsMarker = Ext.DomHelper.append(me.el, {
93010                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
93011             }, true);
93012         }
93013         return me.rhsMarker;
93014     },
93015
93016     /**
93017      * Returns the selection model being used and creates it via the configuration if it has not been created already.
93018      * @return {Ext.selection.Model} selModel
93019      */
93020     getSelectionModel: function(){
93021         if (!this.selModel) {
93022             this.selModel = {};
93023         }
93024
93025         var mode = 'SINGLE',
93026             type;
93027         if (this.simpleSelect) {
93028             mode = 'SIMPLE';
93029         } else if (this.multiSelect) {
93030             mode = 'MULTI';
93031         }
93032
93033         Ext.applyIf(this.selModel, {
93034             allowDeselect: this.allowDeselect,
93035             mode: mode
93036         });
93037
93038         if (!this.selModel.events) {
93039             type = this.selModel.selType || this.selType;
93040             this.selModel = Ext.create('selection.' + type, this.selModel);
93041         }
93042
93043         if (!this.selModel.hasRelaySetup) {
93044             this.relayEvents(this.selModel, [
93045                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
93046             ]);
93047             this.selModel.hasRelaySetup = true;
93048         }
93049
93050         // lock the selection model if user
93051         // has disabled selection
93052         if (this.disableSelection) {
93053             this.selModel.locked = true;
93054         }
93055         return this.selModel;
93056     },
93057
93058     onVerticalScroll: function(event, target) {
93059         var owner = this.getScrollerOwner(),
93060             items = owner.query('tableview'),
93061             i = 0,
93062             len = items.length;
93063
93064         for (; i < len; i++) {
93065             items[i].el.dom.scrollTop = target.scrollTop;
93066         }
93067     },
93068
93069     onHorizontalScroll: function(event, target) {
93070         var owner = this.getScrollerOwner(),
93071             items = owner.query('tableview'),
93072             center = items[1] || items[0];
93073
93074         center.el.dom.scrollLeft = target.scrollLeft;
93075         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
93076     },
93077
93078     // template method meant to be overriden
93079     onStoreLoad: Ext.emptyFn,
93080
93081     getEditorParent: function() {
93082         return this.body;
93083     },
93084
93085     bindStore: function(store) {
93086         var me = this;
93087         me.store = store;
93088         me.getView().bindStore(store);
93089     },
93090     
93091     beforeDestroy: function(){
93092         // may be some duplication here since the horizontal and vertical
93093         // scroller may be part of the docked items, but we need to clean
93094         // them up in case they aren't visible.
93095         Ext.destroy(this.horizontalScroller, this.verticalScroller);
93096         this.callParent();
93097     },
93098
93099     /**
93100      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
93101      * to change them.
93102      * @param {Ext.data.Store} store (Optional) The new store.
93103      * @param {Object[]} columns (Optional) An array of column configs
93104      */
93105     reconfigure: function(store, columns) {
93106         var me = this,
93107             headerCt = me.headerCt;
93108
93109         if (me.lockable) {
93110             me.reconfigureLockable(store, columns);
93111         } else {
93112             if (columns) {
93113                 headerCt.suspendLayout = true;
93114                 headerCt.removeAll();
93115                 headerCt.add(columns);
93116             }
93117             if (store) {
93118                 store = Ext.StoreManager.lookup(store);
93119                 me.bindStore(store);
93120             } else {
93121                 me.getView().refresh();
93122             }
93123             if (columns) {
93124                 headerCt.suspendLayout = false;
93125                 me.forceComponentLayout();
93126             }
93127         }
93128         me.fireEvent('reconfigure', me);
93129     }
93130 });
93131 /**
93132  * This class encapsulates the user interface for a tabular data set.
93133  * It acts as a centralized manager for controlling the various interface
93134  * elements of the view. This includes handling events, such as row and cell
93135  * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
93136  * to provide visual feedback to the user.
93137  *
93138  * This class does not provide ways to manipulate the underlying data of the configured
93139  * {@link Ext.data.Store}.
93140  *
93141  * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
93142  * to be used directly.
93143  */
93144 Ext.define('Ext.view.Table', {
93145     extend: 'Ext.view.View',
93146     alias: 'widget.tableview',
93147     uses: [
93148         'Ext.view.TableChunker',
93149         'Ext.util.DelayedTask',
93150         'Ext.util.MixedCollection'
93151     ],
93152
93153     baseCls: Ext.baseCSSPrefix + 'grid-view',
93154
93155     // row
93156     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
93157     // cell
93158     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
93159
93160     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
93161     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
93162     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
93163     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
93164     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
93165     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
93166     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
93167
93168     // cfg docs inherited
93169     trackOver: true,
93170
93171     /**
93172      * Override this function to apply custom CSS classes to rows during rendering. This function should return the
93173      * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
93174      * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
93175      * Example usage:
93176      *
93177      *     viewConfig: {
93178      *         getRowClass: function(record, rowIndex, rowParams, store){
93179      *             return record.get("valid") ? "row-valid" : "row-error";
93180      *         }
93181      *     }
93182      *
93183      * @param {Ext.data.Model} record The record corresponding to the current row.
93184      * @param {Number} index The row index.
93185      * @param {Object} rowParams **DEPRECATED.** For row body use the
93186      * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
93187      * @param {Ext.data.Store} store The store this grid is bound to
93188      * @return {String} a CSS class name to add to the row.
93189      * @method
93190      */
93191     getRowClass: null,
93192
93193     initComponent: function() {
93194         var me = this;
93195
93196         me.scrollState = {};
93197         me.selModel.view = me;
93198         me.headerCt.view = me;
93199         me.initFeatures();
93200         me.tpl = '<div></div>';
93201         me.callParent();
93202         me.mon(me.store, {
93203             load: me.onStoreLoad,
93204             scope: me
93205         });
93206
93207         // this.addEvents(
93208         //     /**
93209         //      * @event rowfocus
93210         //      * @param {Ext.data.Model} record
93211         //      * @param {HTMLElement} row
93212         //      * @param {Number} rowIdx
93213         //      */
93214         //     'rowfocus'
93215         // );
93216     },
93217
93218     // scroll to top of the grid when store loads
93219     onStoreLoad: function(){
93220         var me = this;
93221
93222         if (me.invalidateScrollerOnRefresh) {
93223             if (Ext.isGecko) {
93224                 if (!me.scrollToTopTask) {
93225                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
93226                 }
93227                 me.scrollToTopTask.delay(1);
93228             } else {
93229                 me    .scrollToTop();
93230             }
93231         }
93232     },
93233
93234     // scroll the view to the top
93235     scrollToTop: Ext.emptyFn,
93236
93237     /**
93238      * Add a listener to the main view element. It will be destroyed with the view.
93239      * @private
93240      */
93241     addElListener: function(eventName, fn, scope){
93242         this.mon(this, eventName, fn, scope, {
93243             element: 'el'
93244         });
93245     },
93246
93247     /**
93248      * Get the columns used for generating a template via TableChunker.
93249      * See {@link Ext.grid.header.Container#getGridColumns}.
93250      * @private
93251      */
93252     getGridColumns: function() {
93253         return this.headerCt.getGridColumns();
93254     },
93255
93256     /**
93257      * Get a leaf level header by index regardless of what the nesting
93258      * structure is.
93259      * @private
93260      * @param {Number} index The index
93261      */
93262     getHeaderAtIndex: function(index) {
93263         return this.headerCt.getHeaderAtIndex(index);
93264     },
93265
93266     /**
93267      * Get the cell (td) for a particular record and column.
93268      * @param {Ext.data.Model} record
93269      * @param {Ext.grid.column.Column} column
93270      * @private
93271      */
93272     getCell: function(record, column) {
93273         var row = this.getNode(record);
93274         return Ext.fly(row).down(column.getCellSelector());
93275     },
93276
93277     /**
93278      * Get a reference to a feature
93279      * @param {String} id The id of the feature
93280      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
93281      */
93282     getFeature: function(id) {
93283         var features = this.featuresMC;
93284         if (features) {
93285             return features.get(id);
93286         }
93287     },
93288
93289     /**
93290      * Initializes each feature and bind it to this view.
93291      * @private
93292      */
93293     initFeatures: function() {
93294         var me = this,
93295             i = 0,
93296             features,
93297             len;
93298
93299         me.features = me.features || [];
93300         features = me.features;
93301         len = features.length;
93302
93303         me.featuresMC = Ext.create('Ext.util.MixedCollection');
93304         for (; i < len; i++) {
93305             // ensure feature hasnt already been instantiated
93306             if (!features[i].isFeature) {
93307                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
93308             }
93309             // inject a reference to view
93310             features[i].view = me;
93311             me.featuresMC.add(features[i]);
93312         }
93313     },
93314
93315     /**
93316      * Gives features an injection point to attach events to the markup that
93317      * has been created for this view.
93318      * @private
93319      */
93320     attachEventsForFeatures: function() {
93321         var features = this.features,
93322             ln       = features.length,
93323             i        = 0;
93324
93325         for (; i < ln; i++) {
93326             if (features[i].isFeature) {
93327                 features[i].attachEvents();
93328             }
93329         }
93330     },
93331
93332     afterRender: function() {
93333         var me = this;
93334
93335         me.callParent();
93336         me.mon(me.el, {
93337             scroll: me.fireBodyScroll,
93338             scope: me
93339         });
93340         me.el.unselectable();
93341         me.attachEventsForFeatures();
93342     },
93343
93344     fireBodyScroll: function(e, t) {
93345         this.fireEvent('bodyscroll', e, t);
93346     },
93347
93348     // TODO: Refactor headerCt dependency here to colModel
93349     /**
93350      * Uses the headerCt to transform data from dataIndex keys in a record to
93351      * headerId keys in each header and then run them through each feature to
93352      * get additional data for variables they have injected into the view template.
93353      * @private
93354      */
93355     prepareData: function(data, idx, record) {
93356         var me       = this,
93357             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
93358             features = me.features,
93359             ln       = features.length,
93360             i        = 0,
93361             node, feature;
93362
93363         for (; i < ln; i++) {
93364             feature = features[i];
93365             if (feature.isFeature) {
93366                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
93367             }
93368         }
93369
93370         return orig;
93371     },
93372
93373     // TODO: Refactor headerCt dependency here to colModel
93374     collectData: function(records, startIndex) {
93375         var preppedRecords = this.callParent(arguments),
93376             headerCt  = this.headerCt,
93377             fullWidth = headerCt.getFullWidth(),
93378             features  = this.features,
93379             ln = features.length,
93380             o = {
93381                 rows: preppedRecords,
93382                 fullWidth: fullWidth
93383             },
93384             i  = 0,
93385             feature,
93386             j = 0,
93387             jln,
93388             rowParams;
93389
93390         jln = preppedRecords.length;
93391         // process row classes, rowParams has been deprecated and has been moved
93392         // to the individual features that implement the behavior.
93393         if (this.getRowClass) {
93394             for (; j < jln; j++) {
93395                 rowParams = {};
93396                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
93397                 //<debug>
93398                 if (rowParams.alt) {
93399                     Ext.Error.raise("The getRowClass alt property is no longer supported.");
93400                 }
93401                 if (rowParams.tstyle) {
93402                     Ext.Error.raise("The getRowClass tstyle property is no longer supported.");
93403                 }
93404                 if (rowParams.cells) {
93405                     Ext.Error.raise("The getRowClass cells property is no longer supported.");
93406                 }
93407                 if (rowParams.body) {
93408                     Ext.Error.raise("The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.");
93409                 }
93410                 if (rowParams.bodyStyle) {
93411                     Ext.Error.raise("The getRowClass bodyStyle property is no longer supported.");
93412                 }
93413                 if (rowParams.cols) {
93414                     Ext.Error.raise("The getRowClass cols property is no longer supported.");
93415                 }
93416                 //</debug>
93417             }
93418         }
93419         // currently only one feature may implement collectData. This is to modify
93420         // what's returned to the view before its rendered
93421         for (; i < ln; i++) {
93422             feature = features[i];
93423             if (feature.isFeature && feature.collectData && !feature.disabled) {
93424                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
93425                 break;
93426             }
93427         }
93428         return o;
93429     },
93430
93431     // TODO: Refactor header resizing to column resizing
93432     /**
93433      * When a header is resized, setWidth on the individual columns resizer class,
93434      * the top level table, save/restore scroll state, generate a new template and
93435      * restore focus to the grid view's element so that keyboard navigation
93436      * continues to work.
93437      * @private
93438      */
93439     onHeaderResize: function(header, w, suppressFocus) {
93440         var me = this,
93441             el = me.el;
93442
93443         if (el) {
93444             me.saveScrollState();
93445             // Grab the col and set the width, css
93446             // class is generated in TableChunker.
93447             // Select composites because there may be several chunks.
93448
93449             // IE6 and IE7 bug.
93450             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
93451             // We need to increment the passed with in this case.
93452             if (Ext.isIE6 || Ext.isIE7) {
93453                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
93454                     w += 1;
93455                 }
93456             }
93457             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
93458             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
93459             me.restoreScrollState();
93460             if (!me.ignoreTemplate) {
93461                 me.setNewTemplate();
93462             }
93463             if (!suppressFocus) {
93464                 me.el.focus();
93465             }
93466         }
93467     },
93468
93469     /**
93470      * When a header is shown restore its oldWidth if it was previously hidden.
93471      * @private
93472      */
93473     onHeaderShow: function(headerCt, header, suppressFocus) {
93474         var me = this;
93475         me.ignoreTemplate = true;
93476         // restore headers that were dynamically hidden
93477         if (header.oldWidth) {
93478             me.onHeaderResize(header, header.oldWidth, suppressFocus);
93479             delete header.oldWidth;
93480         // flexed headers will have a calculated size set
93481         // this additional check has to do with the fact that
93482         // defaults: {width: 100} will fight with a flex value
93483         } else if (header.width && !header.flex) {
93484             me.onHeaderResize(header, header.width, suppressFocus);
93485         }
93486         delete me.ignoreTemplate;
93487         me.setNewTemplate();
93488     },
93489
93490     /**
93491      * When the header hides treat it as a resize to 0.
93492      * @private
93493      */
93494     onHeaderHide: function(headerCt, header, suppressFocus) {
93495         this.onHeaderResize(header, 0, suppressFocus);
93496     },
93497
93498     /**
93499      * Set a new template based on the current columns displayed in the
93500      * grid.
93501      * @private
93502      */
93503     setNewTemplate: function() {
93504         var me = this,
93505             columns = me.headerCt.getColumnsForTpl(true);
93506
93507         me.tpl = me.getTableChunker().getTableTpl({
93508             columns: columns,
93509             features: me.features
93510         });
93511     },
93512
93513     /**
93514      * Returns the configured chunker or default of Ext.view.TableChunker
93515      */
93516     getTableChunker: function() {
93517         return this.chunker || Ext.view.TableChunker;
93518     },
93519
93520     /**
93521      * Adds a CSS Class to a specific row.
93522      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
93523      * representing this row
93524      * @param {String} cls
93525      */
93526     addRowCls: function(rowInfo, cls) {
93527         var row = this.getNode(rowInfo);
93528         if (row) {
93529             Ext.fly(row).addCls(cls);
93530         }
93531     },
93532
93533     /**
93534      * Removes a CSS Class from a specific row.
93535      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
93536      * representing this row
93537      * @param {String} cls
93538      */
93539     removeRowCls: function(rowInfo, cls) {
93540         var row = this.getNode(rowInfo);
93541         if (row) {
93542             Ext.fly(row).removeCls(cls);
93543         }
93544     },
93545
93546     // GridSelectionModel invokes onRowSelect as selection changes
93547     onRowSelect : function(rowIdx) {
93548         this.addRowCls(rowIdx, this.selectedItemCls);
93549     },
93550
93551     // GridSelectionModel invokes onRowDeselect as selection changes
93552     onRowDeselect : function(rowIdx) {
93553         var me = this;
93554
93555         me.removeRowCls(rowIdx, me.selectedItemCls);
93556         me.removeRowCls(rowIdx, me.focusedItemCls);
93557     },
93558
93559     onCellSelect: function(position) {
93560         var cell = this.getCellByPosition(position);
93561         if (cell) {
93562             cell.addCls(this.selectedCellCls);
93563         }
93564     },
93565
93566     onCellDeselect: function(position) {
93567         var cell = this.getCellByPosition(position);
93568         if (cell) {
93569             cell.removeCls(this.selectedCellCls);
93570         }
93571
93572     },
93573
93574     onCellFocus: function(position) {
93575         //var cell = this.getCellByPosition(position);
93576         this.focusCell(position);
93577     },
93578
93579     getCellByPosition: function(position) {
93580         var row    = position.row,
93581             column = position.column,
93582             store  = this.store,
93583             node   = this.getNode(row),
93584             header = this.headerCt.getHeaderAtIndex(column),
93585             cellSelector,
93586             cell = false;
93587
93588         if (header && node) {
93589             cellSelector = header.getCellSelector();
93590             cell = Ext.fly(node).down(cellSelector);
93591         }
93592         return cell;
93593     },
93594
93595     // GridSelectionModel invokes onRowFocus to 'highlight'
93596     // the last row focused
93597     onRowFocus: function(rowIdx, highlight, supressFocus) {
93598         var me = this,
93599             row = me.getNode(rowIdx);
93600
93601         if (highlight) {
93602             me.addRowCls(rowIdx, me.focusedItemCls);
93603             if (!supressFocus) {
93604                 me.focusRow(rowIdx);
93605             }
93606             //this.el.dom.setAttribute('aria-activedescendant', row.id);
93607         } else {
93608             me.removeRowCls(rowIdx, me.focusedItemCls);
93609         }
93610     },
93611
93612     /**
93613      * Focuses a particular row and brings it into view. Will fire the rowfocus event.
93614      * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
93615      * An HTMLElement template node, index of a template node, the id of a template node or the
93616      * record associated with the node.
93617      */
93618     focusRow: function(rowIdx) {
93619         var me         = this,
93620             row        = me.getNode(rowIdx),
93621             el         = me.el,
93622             adjustment = 0,
93623             panel      = me.ownerCt,
93624             rowRegion,
93625             elRegion,
93626             record;
93627
93628         if (row && el) {
93629             elRegion  = el.getRegion();
93630             rowRegion = Ext.fly(row).getRegion();
93631             // row is above
93632             if (rowRegion.top < elRegion.top) {
93633                 adjustment = rowRegion.top - elRegion.top;
93634             // row is below
93635             } else if (rowRegion.bottom > elRegion.bottom) {
93636                 adjustment = rowRegion.bottom - elRegion.bottom;
93637             }
93638             record = me.getRecord(row);
93639             rowIdx = me.store.indexOf(record);
93640
93641             if (adjustment) {
93642                 // scroll the grid itself, so that all gridview's update.
93643                 panel.scrollByDeltaY(adjustment);
93644             }
93645             me.fireEvent('rowfocus', record, row, rowIdx);
93646         }
93647     },
93648
93649     focusCell: function(position) {
93650         var me          = this,
93651             cell        = me.getCellByPosition(position),
93652             el          = me.el,
93653             adjustmentY = 0,
93654             adjustmentX = 0,
93655             elRegion    = el.getRegion(),
93656             panel       = me.ownerCt,
93657             cellRegion,
93658             record;
93659
93660         if (cell) {
93661             cellRegion = cell.getRegion();
93662             // cell is above
93663             if (cellRegion.top < elRegion.top) {
93664                 adjustmentY = cellRegion.top - elRegion.top;
93665             // cell is below
93666             } else if (cellRegion.bottom > elRegion.bottom) {
93667                 adjustmentY = cellRegion.bottom - elRegion.bottom;
93668             }
93669
93670             // cell is left
93671             if (cellRegion.left < elRegion.left) {
93672                 adjustmentX = cellRegion.left - elRegion.left;
93673             // cell is right
93674             } else if (cellRegion.right > elRegion.right) {
93675                 adjustmentX = cellRegion.right - elRegion.right;
93676             }
93677
93678             if (adjustmentY) {
93679                 // scroll the grid itself, so that all gridview's update.
93680                 panel.scrollByDeltaY(adjustmentY);
93681             }
93682             if (adjustmentX) {
93683                 panel.scrollByDeltaX(adjustmentX);
93684             }
93685             el.focus();
93686             me.fireEvent('cellfocus', record, cell, position);
93687         }
93688     },
93689
93690     /**
93691      * Scrolls by delta. This affects this individual view ONLY and does not
93692      * synchronize across views or scrollers.
93693      * @param {Number} delta
93694      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
93695      * @private
93696      */
93697     scrollByDelta: function(delta, dir) {
93698         dir = dir || 'scrollTop';
93699         var elDom = this.el.dom;
93700         elDom[dir] = (elDom[dir] += delta);
93701     },
93702
93703     onUpdate: function(ds, index) {
93704         this.callParent(arguments);
93705     },
93706
93707     /**
93708      * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState
93709      */
93710     saveScrollState: function() {
93711         if (this.rendered) {
93712             var dom = this.el.dom, 
93713                 state = this.scrollState;
93714             
93715             state.left = dom.scrollLeft;
93716             state.top = dom.scrollTop;
93717         }
93718     },
93719
93720     /**
93721      * Restores the scrollState.
93722      * Must be used in conjunction with saveScrollState
93723      * @private
93724      */
93725     restoreScrollState: function() {
93726         if (this.rendered) {
93727             var dom = this.el.dom, 
93728                 state = this.scrollState, 
93729                 headerEl = this.headerCt.el.dom;
93730             
93731             headerEl.scrollLeft = dom.scrollLeft = state.left;
93732             dom.scrollTop = state.top;
93733         }
93734     },
93735
93736     /**
93737      * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
93738      * invalidates the scrollers.
93739      */
93740     refresh: function() {
93741         this.setNewTemplate();
93742         this.callParent(arguments);
93743     },
93744
93745     processItemEvent: function(record, row, rowIndex, e) {
93746         var me = this,
93747             cell = e.getTarget(me.cellSelector, row),
93748             cellIndex = cell ? cell.cellIndex : -1,
93749             map = me.statics().EventMap,
93750             selModel = me.getSelectionModel(),
93751             type = e.type,
93752             result;
93753
93754         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
93755             // CellModel, otherwise we can't tell which cell to invoke
93756             cell = me.getCellByPosition(selModel.getCurrentPosition());
93757             if (cell) {
93758                 cell = cell.dom;
93759                 cellIndex = cell.cellIndex;
93760             }
93761         }
93762
93763         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
93764
93765         if (result === false || me.callParent(arguments) === false) {
93766             return false;
93767         }
93768
93769         // Don't handle cellmouseenter and cellmouseleave events for now
93770         if (type == 'mouseover' || type == 'mouseout') {
93771             return true;
93772         }
93773
93774         return !(
93775             // We are adding cell and feature events
93776             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
93777             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
93778             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
93779             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
93780         );
93781     },
93782
93783     processSpecialEvent: function(e) {
93784         var me = this,
93785             map = me.statics().EventMap,
93786             features = me.features,
93787             ln = features.length,
93788             type = e.type,
93789             i, feature, prefix, featureTarget,
93790             beforeArgs, args,
93791             panel = me.ownerCt;
93792
93793         me.callParent(arguments);
93794
93795         if (type == 'mouseover' || type == 'mouseout') {
93796             return;
93797         }
93798
93799         for (i = 0; i < ln; i++) {
93800             feature = features[i];
93801             if (feature.hasFeatureEvent) {
93802                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
93803                 if (featureTarget) {
93804                     prefix = feature.eventPrefix;
93805                     // allows features to implement getFireEventArgs to change the
93806                     // fireEvent signature
93807                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
93808                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
93809
93810                     if (
93811                         // before view event
93812                         (me.fireEvent.apply(me, beforeArgs) === false) ||
93813                         // panel grid event
93814                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
93815                         // view event
93816                         (me.fireEvent.apply(me, args) === false) ||
93817                         // panel event
93818                         (panel.fireEvent.apply(panel, args) === false)
93819                     ) {
93820                         return false;
93821                     }
93822                 }
93823             }
93824         }
93825         return true;
93826     },
93827
93828     onCellMouseDown: Ext.emptyFn,
93829     onCellMouseUp: Ext.emptyFn,
93830     onCellClick: Ext.emptyFn,
93831     onCellDblClick: Ext.emptyFn,
93832     onCellContextMenu: Ext.emptyFn,
93833     onCellKeyDown: Ext.emptyFn,
93834     onBeforeCellMouseDown: Ext.emptyFn,
93835     onBeforeCellMouseUp: Ext.emptyFn,
93836     onBeforeCellClick: Ext.emptyFn,
93837     onBeforeCellDblClick: Ext.emptyFn,
93838     onBeforeCellContextMenu: Ext.emptyFn,
93839     onBeforeCellKeyDown: Ext.emptyFn,
93840
93841     /**
93842      * Expands a particular header to fit the max content width.
93843      * This will ONLY expand, not contract.
93844      * @private
93845      */
93846     expandToFit: function(header) {
93847         if (header) {
93848             var maxWidth = this.getMaxContentWidth(header);
93849             delete header.flex;
93850             header.setWidth(maxWidth);
93851         }
93852     },
93853
93854     /**
93855      * Returns the max contentWidth of the header's text and all cells
93856      * in the grid under this header.
93857      * @private
93858      */
93859     getMaxContentWidth: function(header) {
93860         var cellSelector = header.getCellInnerSelector(),
93861             cells        = this.el.query(cellSelector),
93862             i = 0,
93863             ln = cells.length,
93864             maxWidth = header.el.dom.scrollWidth,
93865             scrollWidth;
93866
93867         for (; i < ln; i++) {
93868             scrollWidth = cells[i].scrollWidth;
93869             if (scrollWidth > maxWidth) {
93870                 maxWidth = scrollWidth;
93871             }
93872         }
93873         return maxWidth;
93874     },
93875
93876     getPositionByEvent: function(e) {
93877         var me       = this,
93878             cellNode = e.getTarget(me.cellSelector),
93879             rowNode  = e.getTarget(me.itemSelector),
93880             record   = me.getRecord(rowNode),
93881             header   = me.getHeaderByCell(cellNode);
93882
93883         return me.getPosition(record, header);
93884     },
93885
93886     getHeaderByCell: function(cell) {
93887         if (cell) {
93888             var m = cell.className.match(this.cellRe);
93889             if (m && m[1]) {
93890                 return Ext.getCmp(m[1]);
93891             }
93892         }
93893         return false;
93894     },
93895
93896     /**
93897      * @param {Object} position The current row and column: an object containing the following properties:
93898      *
93899      * - row - The row index
93900      * - column - The column index
93901      *
93902      * @param {String} direction 'up', 'down', 'right' and 'left'
93903      * @param {Ext.EventObject} e event
93904      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
93905      * @param {Function} verifierFn A function to verify the validity of the calculated position.
93906      * When using this function, you must return true to allow the newPosition to be returned.
93907      * @param {Object} scope Scope to run the verifierFn in
93908      * @returns {Object} newPosition An object containing the following properties:
93909      *
93910      * - row - The row index
93911      * - column - The column index
93912      *
93913      * @private
93914      */
93915     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
93916         var me       = this,
93917             row      = pos.row,
93918             column   = pos.column,
93919             rowCount = me.store.getCount(),
93920             firstCol = me.getFirstVisibleColumnIndex(),
93921             lastCol  = me.getLastVisibleColumnIndex(),
93922             newPos   = {row: row, column: column},
93923             activeHeader = me.headerCt.getHeaderAtIndex(column);
93924
93925         // no active header or its currently hidden
93926         if (!activeHeader || activeHeader.hidden) {
93927             return false;
93928         }
93929
93930         e = e || {};
93931         direction = direction.toLowerCase();
93932         switch (direction) {
93933             case 'right':
93934                 // has the potential to wrap if its last
93935                 if (column === lastCol) {
93936                     // if bottom row and last column, deny right
93937                     if (preventWrap || row === rowCount - 1) {
93938                         return false;
93939                     }
93940                     if (!e.ctrlKey) {
93941                         // otherwise wrap to nextRow and firstCol
93942                         newPos.row = row + 1;
93943                         newPos.column = firstCol;
93944                     }
93945                 // go right
93946                 } else {
93947                     if (!e.ctrlKey) {
93948                         newPos.column = column + me.getRightGap(activeHeader);
93949                     } else {
93950                         newPos.column = lastCol;
93951                     }
93952                 }
93953                 break;
93954
93955             case 'left':
93956                 // has the potential to wrap
93957                 if (column === firstCol) {
93958                     // if top row and first column, deny left
93959                     if (preventWrap || row === 0) {
93960                         return false;
93961                     }
93962                     if (!e.ctrlKey) {
93963                         // otherwise wrap to prevRow and lastCol
93964                         newPos.row = row - 1;
93965                         newPos.column = lastCol;
93966                     }
93967                 // go left
93968                 } else {
93969                     if (!e.ctrlKey) {
93970                         newPos.column = column + me.getLeftGap(activeHeader);
93971                     } else {
93972                         newPos.column = firstCol;
93973                     }
93974                 }
93975                 break;
93976
93977             case 'up':
93978                 // if top row, deny up
93979                 if (row === 0) {
93980                     return false;
93981                 // go up
93982                 } else {
93983                     if (!e.ctrlKey) {
93984                         newPos.row = row - 1;
93985                     } else {
93986                         newPos.row = 0;
93987                     }
93988                 }
93989                 break;
93990
93991             case 'down':
93992                 // if bottom row, deny down
93993                 if (row === rowCount - 1) {
93994                     return false;
93995                 // go down
93996                 } else {
93997                     if (!e.ctrlKey) {
93998                         newPos.row = row + 1;
93999                     } else {
94000                         newPos.row = rowCount - 1;
94001                     }
94002                 }
94003                 break;
94004         }
94005
94006         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
94007             return false;
94008         } else {
94009             return newPos;
94010         }
94011     },
94012     getFirstVisibleColumnIndex: function() {
94013         var headerCt   = this.getHeaderCt(),
94014             allColumns = headerCt.getGridColumns(),
94015             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
94016             firstHeader = visHeaders[0];
94017
94018         return headerCt.getHeaderIndex(firstHeader);
94019     },
94020
94021     getLastVisibleColumnIndex: function() {
94022         var headerCt   = this.getHeaderCt(),
94023             allColumns = headerCt.getGridColumns(),
94024             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
94025             lastHeader = visHeaders[visHeaders.length - 1];
94026
94027         return headerCt.getHeaderIndex(lastHeader);
94028     },
94029
94030     getHeaderCt: function() {
94031         return this.headerCt;
94032     },
94033
94034     getPosition: function(record, header) {
94035         var me = this,
94036             store = me.store,
94037             gridCols = me.headerCt.getGridColumns();
94038
94039         return {
94040             row: store.indexOf(record),
94041             column: Ext.Array.indexOf(gridCols, header)
94042         };
94043     },
94044
94045     /**
94046      * Determines the 'gap' between the closest adjacent header to the right
94047      * that is not hidden.
94048      * @private
94049      */
94050     getRightGap: function(activeHeader) {
94051         var headerCt        = this.getHeaderCt(),
94052             headers         = headerCt.getGridColumns(),
94053             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
94054             i               = activeHeaderIdx + 1,
94055             nextIdx;
94056
94057         for (; i <= headers.length; i++) {
94058             if (!headers[i].hidden) {
94059                 nextIdx = i;
94060                 break;
94061             }
94062         }
94063
94064         return nextIdx - activeHeaderIdx;
94065     },
94066
94067     beforeDestroy: function() {
94068         if (this.rendered) {
94069             this.el.removeAllListeners();
94070         }
94071         this.callParent(arguments);
94072     },
94073
94074     /**
94075      * Determines the 'gap' between the closest adjacent header to the left
94076      * that is not hidden.
94077      * @private
94078      */
94079     getLeftGap: function(activeHeader) {
94080         var headerCt        = this.getHeaderCt(),
94081             headers         = headerCt.getGridColumns(),
94082             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
94083             i               = activeHeaderIdx - 1,
94084             prevIdx;
94085
94086         for (; i >= 0; i--) {
94087             if (!headers[i].hidden) {
94088                 prevIdx = i;
94089                 break;
94090             }
94091         }
94092
94093         return prevIdx - activeHeaderIdx;
94094     }
94095 });
94096 /**
94097  * @class Ext.grid.View
94098  * @extends Ext.view.Table
94099  *
94100  * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
94101  * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
94102  * option is passed to the grid:
94103  *
94104  *     Ext.create('Ext.grid.Panel', {
94105  *         // other options
94106  *         viewConfig: {
94107  *             stripeRows: false
94108  *         }
94109  *     });
94110  *
94111  * ## Drag Drop
94112  *
94113  * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
94114  * when creating the view.
94115  *
94116  *     Ext.create('Ext.grid.Panel', {
94117  *         // other options
94118  *         viewConfig: {
94119  *             plugins: {
94120  *                 ddGroup: 'people-group',
94121  *                 ptype: 'gridviewdragdrop',
94122  *                 enableDrop: false
94123  *             }
94124  *         }
94125  *     });
94126  */
94127 Ext.define('Ext.grid.View', {
94128     extend: 'Ext.view.Table',
94129     alias: 'widget.gridview',
94130
94131     /**
94132      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>true</tt>.
94133      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
94134      * the grid. A default CSS rule is provided which sets a background color, but you can override this
94135      * with a rule which either overrides the <b>background-color</b> style using the '!important'
94136      * modifier, or which uses a CSS selector of higher specificity.</p>
94137      */
94138     stripeRows: true,
94139
94140     invalidateScrollerOnRefresh: true,
94141
94142     /**
94143      * Scroll the GridView to the top by scrolling the scroller.
94144      * @private
94145      */
94146     scrollToTop : function(){
94147         if (this.rendered) {
94148             var section = this.ownerCt,
94149                 verticalScroller = section.verticalScroller;
94150
94151             if (verticalScroller) {
94152                 verticalScroller.scrollToTop();
94153             }
94154         }
94155     },
94156
94157     // after adding a row stripe rows from then on
94158     onAdd: function(ds, records, index) {
94159         this.callParent(arguments);
94160         this.doStripeRows(index);
94161     },
94162
94163     // after removing a row stripe rows from then on
94164     onRemove: function(ds, records, index) {
94165         this.callParent(arguments);
94166         this.doStripeRows(index);
94167     },
94168
94169     onUpdate: function(ds, record, operation) {
94170         var index = ds.indexOf(record);
94171         this.callParent(arguments);
94172         this.doStripeRows(index, index);
94173     },
94174
94175     /**
94176      * Stripe rows from a particular row index
94177      * @param {Number} startRow
94178      * @param {Number} endRow (Optional) argument specifying the last row to process. By default process up to the last row.
94179      * @private
94180      */
94181     doStripeRows: function(startRow, endRow) {
94182         // ensure stripeRows configuration is turned on
94183         if (this.stripeRows) {
94184             var rows   = this.getNodes(startRow, endRow),
94185                 rowsLn = rows.length,
94186                 i      = 0,
94187                 row;
94188
94189             for (; i < rowsLn; i++) {
94190                 row = rows[i];
94191                 // Remove prior applied row classes.
94192                 row.className = row.className.replace(this.rowClsRe, ' ');
94193                 startRow++;
94194                 // Every odd row will get an additional cls
94195                 if (startRow % 2 === 0) {
94196                     row.className += (' ' + this.altRowCls);
94197                 }
94198             }
94199         }
94200     },
94201
94202     refresh: function(firstPass) {
94203         this.callParent(arguments);
94204         this.doStripeRows(0);
94205         // TODO: Remove gridpanel dependency
94206         var g = this.up('gridpanel');
94207         if (g && this.invalidateScrollerOnRefresh) {
94208             g.invalidateScroller();
94209         }
94210     }
94211 });
94212
94213 /**
94214  * @author Aaron Conran
94215  * @docauthor Ed Spencer
94216  *
94217  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
94218  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
94219  *
94220  * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
94221  *
94222  * ## Basic GridPanel
94223  *
94224  *     @example
94225  *     Ext.create('Ext.data.Store', {
94226  *         storeId:'simpsonsStore',
94227  *         fields:['name', 'email', 'phone'],
94228  *         data:{'items':[
94229  *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
94230  *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
94231  *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
94232  *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
94233  *         ]},
94234  *         proxy: {
94235  *             type: 'memory',
94236  *             reader: {
94237  *                 type: 'json',
94238  *                 root: 'items'
94239  *             }
94240  *         }
94241  *     });
94242  *
94243  *     Ext.create('Ext.grid.Panel', {
94244  *         title: 'Simpsons',
94245  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
94246  *         columns: [
94247  *             { header: 'Name',  dataIndex: 'name' },
94248  *             { header: 'Email', dataIndex: 'email', flex: 1 },
94249  *             { header: 'Phone', dataIndex: 'phone' }
94250  *         ],
94251  *         height: 200,
94252  *         width: 400,
94253  *         renderTo: Ext.getBody()
94254  *     });
94255  *
94256  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
94257  * In most apps we would be placing the grid inside another container and wouldn't need to use the
94258  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
94259  * up and running.
94260  *
94261  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
94262  * and finally the grid rows under the headers.
94263  *
94264  * ## Configuring columns
94265  *
94266  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
94267  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
94268  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
94269  *
94270  *     columns: [
94271  *         {
94272  *             header: 'Name',
94273  *             dataIndex: 'name',
94274  *             sortable: false,
94275  *             hideable: false,
94276  *             flex: 1
94277  *         },
94278  *         {
94279  *             header: 'Email',
94280  *             dataIndex: 'email',
94281  *             hidden: true
94282  *         },
94283  *         {
94284  *             header: 'Phone',
94285  *             dataIndex: 'phone',
94286  *             width: 100
94287  *         }
94288  *     ]
94289  *
94290  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
94291  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
94292  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
94293  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
94294  *
94295  * ## Renderers
94296  *
94297  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
94298  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
94299  * we could define a renderer function for the email column to turn each email address into a mailto link:
94300  *
94301  *     columns: [
94302  *         {
94303  *             header: 'Email',
94304  *             dataIndex: 'email',
94305  *             renderer: function(value) {
94306  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
94307  *             }
94308  *         }
94309  *     ]
94310  *
94311  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
94312  *
94313  * ## Selection Models
94314  *
94315  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
94316  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
94317  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
94318  * CellSelectionModel, where individual cells are selected.
94319  *
94320  * Grids use a Row Selection Model by default, but this is easy to customise like so:
94321  *
94322  *     Ext.create('Ext.grid.Panel', {
94323  *         selType: 'cellmodel',
94324  *         store: ...
94325  *     });
94326  *
94327  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
94328  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
94329  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
94330  * conjunction with editing.
94331  *
94332  * ## Editing
94333  *
94334  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
94335  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
94336  * on both the name and the email columns:
94337  *
94338  *     Ext.create('Ext.grid.Panel', {
94339  *         title: 'Simpsons',
94340  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
94341  *         columns: [
94342  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
94343  *             { header: 'Email', dataIndex: 'email', flex: 1,
94344  *                 field: {
94345  *                     xtype: 'textfield',
94346  *                     allowBlank: false
94347  *                 }
94348  *             },
94349  *             { header: 'Phone', dataIndex: 'phone' }
94350  *         ],
94351  *         selType: 'cellmodel',
94352  *         plugins: [
94353  *             Ext.create('Ext.grid.plugin.CellEditing', {
94354  *                 clicksToEdit: 1
94355  *             })
94356  *         ],
94357  *         height: 200,
94358  *         width: 400,
94359  *         renderTo: Ext.getBody()
94360  *     });
94361  *
94362  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but
94363  * this time we've also specified a {@link Ext.grid.column.Column#field field} on two of our columns. For the Name column
94364  * we just want a default textfield to edit the value, so we specify 'textfield'. For the Email column we customized the
94365  * editor slightly by passing allowBlank: false, which will provide inline validation.
94366  *
94367  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
94368  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
94369  * single click.
94370  *
94371  * ## Row Editing
94372  *
94373  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
94374  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
94375  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
94376  *
94377  *     Ext.create('Ext.grid.Panel', {
94378  *         title: 'Simpsons',
94379  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
94380  *         columns: [
94381  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
94382  *             { header: 'Email', dataIndex: 'email', flex:1,
94383  *                 field: {
94384  *                     xtype: 'textfield',
94385  *                     allowBlank: false
94386  *                 }
94387  *             },
94388  *             { header: 'Phone', dataIndex: 'phone' }
94389  *         ],
94390  *         selType: 'rowmodel',
94391  *         plugins: [
94392  *             Ext.create('Ext.grid.plugin.RowEditing', {
94393  *                 clicksToEdit: 1
94394  *             })
94395  *         ],
94396  *         height: 200,
94397  *         width: 400,
94398  *         renderTo: Ext.getBody()
94399  *     });
94400  *
94401  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
94402  * editor will appear and enable us to edit each of the columns we have specified an editor for.
94403  *
94404  * ## Sorting & Filtering
94405  *
94406  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
94407  * easy to set up a grid to be sorted from the start:
94408  *
94409  *     var myGrid = Ext.create('Ext.grid.Panel', {
94410  *         store: {
94411  *             fields: ['name', 'email', 'phone'],
94412  *             sorters: ['name', 'phone']
94413  *         },
94414  *         columns: [
94415  *             { text: 'Name',  dataIndex: 'name' },
94416  *             { text: 'Email', dataIndex: 'email' }
94417  *         ]
94418  *     });
94419  *
94420  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
94421  * more than one field at run time it's easy to do so by adding new sorters to the store:
94422  *
94423  *     myGrid.store.sort([
94424  *         { property: 'name',  direction: 'ASC' },
94425  *         { property: 'email', direction: 'DESC' }
94426  *     ]);
94427  *
94428  * See {@link Ext.data.Store} for examples of filtering.
94429  *
94430  * ## Grouping
94431  *
94432  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to
94433  * group by the department that each employee works in. Here's how we might set that up:
94434  *
94435  *     @example
94436  *     var store = Ext.create('Ext.data.Store', {
94437  *         storeId:'employeeStore',
94438  *         fields:['name', 'senority', 'department'],
94439  *         groupField: 'department',
94440  *         data: {'employees':[
94441  *             { "name": "Michael Scott",  "senority": 7, "department": "Manangement" },
94442  *             { "name": "Dwight Schrute", "senority": 2, "department": "Sales" },
94443  *             { "name": "Jim Halpert",    "senority": 3, "department": "Sales" },
94444  *             { "name": "Kevin Malone",   "senority": 4, "department": "Accounting" },
94445  *             { "name": "Angela Martin",  "senority": 5, "department": "Accounting" }
94446  *         ]},
94447  *         proxy: {
94448  *             type: 'memory',
94449  *             reader: {
94450  *                 type: 'json',
94451  *                 root: 'employees'
94452  *             }
94453  *         }
94454  *     });
94455  *
94456  *     Ext.create('Ext.grid.Panel', {
94457  *         title: 'Employees',
94458  *         store: Ext.data.StoreManager.lookup('employeeStore'),
94459  *         columns: [
94460  *             { header: 'Name',     dataIndex: 'name' },
94461  *             { header: 'Senority', dataIndex: 'senority' }
94462  *         ],
94463  *         features: [{ftype:'grouping'}],
94464  *         width: 200,
94465  *         height: 275,
94466  *         renderTo: Ext.getBody()
94467  *     });
94468  *
94469  * ## Infinite Scrolling
94470  *
94471  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
94472  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
94473  * to a store with a pageSize specified.
94474  *
94475  *     var grid = Ext.create('Ext.grid.Panel', {
94476  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
94477  *         verticalScrollerType: 'paginggridscroller',
94478  *         // do not reset the scrollbar when the view refreshs
94479  *         invalidateScrollerOnRefresh: false,
94480  *         // infinite scrolling does not support selection
94481  *         disableSelection: true,
94482  *         // ...
94483  *     });
94484  *
94485  * ## Paging
94486  *
94487  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
94488  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
94489  *
94490  *     @example
94491  *     var itemsPerPage = 2;   // set the number of items you want per page
94492  *
94493  *     var store = Ext.create('Ext.data.Store', {
94494  *         id:'simpsonsStore',
94495  *         autoLoad: false,
94496  *         fields:['name', 'email', 'phone'],
94497  *         pageSize: itemsPerPage, // items per page
94498  *         proxy: {
94499  *             type: 'ajax',
94500  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
94501  *             reader: {
94502  *                 type: 'json',
94503  *                 root: 'items',
94504  *                 totalProperty: 'total'
94505  *             }
94506  *         }
94507  *     });
94508  *
94509  *     // specify segment of data you want to load using params
94510  *     store.load({
94511  *         params:{
94512  *             start:0,
94513  *             limit: itemsPerPage
94514  *         }
94515  *     });
94516  *
94517  *     Ext.create('Ext.grid.Panel', {
94518  *         title: 'Simpsons',
94519  *         store: store,
94520  *         columns: [
94521  *             {header: 'Name',  dataIndex: 'name'},
94522  *             {header: 'Email', dataIndex: 'email', flex:1},
94523  *             {header: 'Phone', dataIndex: 'phone'}
94524  *         ],
94525  *         width: 400,
94526  *         height: 125,
94527  *         dockedItems: [{
94528  *             xtype: 'pagingtoolbar',
94529  *             store: store,   // same store GridPanel is using
94530  *             dock: 'bottom',
94531  *             displayInfo: true
94532  *         }],
94533  *         renderTo: Ext.getBody()
94534  *     });
94535  */
94536 Ext.define('Ext.grid.Panel', {
94537     extend: 'Ext.panel.Table',
94538     requires: ['Ext.grid.View'],
94539     alias: ['widget.gridpanel', 'widget.grid'],
94540     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
94541     viewType: 'gridview',
94542
94543     lockable: false,
94544
94545     // Required for the Lockable Mixin. These are the configurations which will be copied to the
94546     // normal and locked sub tablepanels
94547     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
94548     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
94549
94550     /**
94551      * @cfg {Boolean} [columnLines=false] Adds column line styling
94552      */
94553
94554     initComponent: function() {
94555         var me = this;
94556
94557         if (me.columnLines) {
94558             me.setColumnLines(me.columnLines);
94559         }
94560
94561         me.callParent();
94562     },
94563
94564     setColumnLines: function(show) {
94565         var me = this,
94566             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
94567
94568         me[method]('with-col-lines');
94569     }
94570 });
94571
94572 // Currently has the following issues:
94573 // - Does not handle postEditValue
94574 // - Fields without editors need to sync with their values in Store
94575 // - starting to edit another record while already editing and dirty should probably prevent it
94576 // - aggregating validation messages
94577 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
94578 // - layout issues when changing sizes/width while hidden (layout bug)
94579
94580 /**
94581  * @class Ext.grid.RowEditor
94582  * @extends Ext.form.Panel
94583  *
94584  * Internal utility class used to provide row editing functionality. For developers, they should use
94585  * the RowEditing plugin to use this functionality with a grid.
94586  *
94587  * @ignore
94588  */
94589 Ext.define('Ext.grid.RowEditor', {
94590     extend: 'Ext.form.Panel',
94591     requires: [
94592         'Ext.tip.ToolTip',
94593         'Ext.util.HashMap',
94594         'Ext.util.KeyNav'
94595     ],
94596
94597     saveBtnText  : 'Update',
94598     cancelBtnText: 'Cancel',
94599     errorsText: 'Errors',
94600     dirtyText: 'You need to commit or cancel your changes',
94601
94602     lastScrollLeft: 0,
94603     lastScrollTop: 0,
94604
94605     border: false,
94606     
94607     // Change the hideMode to offsets so that we get accurate measurements when
94608     // the roweditor is hidden for laying out things like a TriggerField.
94609     hideMode: 'offsets',
94610
94611     initComponent: function() {
94612         var me = this,
94613             form;
94614
94615         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
94616
94617         me.layout = {
94618             type: 'hbox',
94619             align: 'middle'
94620         };
94621
94622         // Maintain field-to-column mapping
94623         // It's easy to get a field from a column, but not vice versa
94624         me.columns = Ext.create('Ext.util.HashMap');
94625         me.columns.getKey = function(columnHeader) {
94626             var f;
94627             if (columnHeader.getEditor) {
94628                 f = columnHeader.getEditor();
94629                 if (f) {
94630                     return f.id;
94631                 }
94632             }
94633             return columnHeader.id;
94634         };
94635         me.mon(me.columns, {
94636             add: me.onFieldAdd,
94637             remove: me.onFieldRemove,
94638             replace: me.onFieldReplace,
94639             scope: me
94640         });
94641
94642         me.callParent(arguments);
94643
94644         if (me.fields) {
94645             me.setField(me.fields);
94646             delete me.fields;
94647         }
94648
94649         form = me.getForm();
94650         form.trackResetOnLoad = true;
94651     },
94652
94653     onFieldChange: function() {
94654         var me = this,
94655             form = me.getForm(),
94656             valid = form.isValid();
94657         if (me.errorSummary && me.isVisible()) {
94658             me[valid ? 'hideToolTip' : 'showToolTip']();
94659         }
94660         if (me.floatingButtons) {
94661             me.floatingButtons.child('#update').setDisabled(!valid);
94662         }
94663         me.isValid = valid;
94664     },
94665
94666     afterRender: function() {
94667         var me = this,
94668             plugin = me.editingPlugin;
94669
94670         me.callParent(arguments);
94671         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
94672
94673         // Prevent from bubbling click events to the grid view
94674         me.mon(me.el, {
94675             click: Ext.emptyFn,
94676             stopPropagation: true
94677         });
94678
94679         me.el.swallowEvent([
94680             'keypress',
94681             'keydown'
94682         ]);
94683
94684         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
94685             enter: plugin.completeEdit,
94686             esc: plugin.onEscKey,
94687             scope: plugin
94688         });
94689
94690         me.mon(plugin.view, {
94691             beforerefresh: me.onBeforeViewRefresh,
94692             refresh: me.onViewRefresh,
94693             scope: me
94694         });
94695     },
94696
94697     onBeforeViewRefresh: function(view) {
94698         var me = this,
94699             viewDom = view.el.dom;
94700
94701         if (me.el.dom.parentNode === viewDom) {
94702             viewDom.removeChild(me.el.dom);
94703         }
94704     },
94705
94706     onViewRefresh: function(view) {
94707         var me = this,
94708             viewDom = view.el.dom,
94709             context = me.context,
94710             idx;
94711
94712         viewDom.appendChild(me.el.dom);
94713
94714         // Recover our row node after a view refresh
94715         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
94716             context.row = view.getNode(idx);
94717             me.reposition();
94718             if (me.tooltip && me.tooltip.isVisible()) {
94719                 me.tooltip.setTarget(context.row);
94720             }
94721         } else {
94722             me.editingPlugin.cancelEdit();
94723         }
94724     },
94725
94726     onCtScroll: function(e, target) {
94727         var me = this,
94728             scrollTop  = target.scrollTop,
94729             scrollLeft = target.scrollLeft;
94730
94731         if (scrollTop !== me.lastScrollTop) {
94732             me.lastScrollTop = scrollTop;
94733             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
94734                 me.repositionTip();
94735             }
94736         }
94737         if (scrollLeft !== me.lastScrollLeft) {
94738             me.lastScrollLeft = scrollLeft;
94739             me.reposition();
94740         }
94741     },
94742
94743     onColumnAdd: function(column) {
94744         this.setField(column);
94745     },
94746
94747     onColumnRemove: function(column) {
94748         this.columns.remove(column);
94749     },
94750
94751     onColumnResize: function(column, width) {
94752         column.getEditor().setWidth(width - 2);
94753         if (this.isVisible()) {
94754             this.reposition();
94755         }
94756     },
94757
94758     onColumnHide: function(column) {
94759         column.getEditor().hide();
94760         if (this.isVisible()) {
94761             this.reposition();
94762         }
94763     },
94764
94765     onColumnShow: function(column) {
94766         var field = column.getEditor();
94767         field.setWidth(column.getWidth() - 2).show();
94768         if (this.isVisible()) {
94769             this.reposition();
94770         }
94771     },
94772
94773     onColumnMove: function(column, fromIdx, toIdx) {
94774         var field = column.getEditor();
94775         if (this.items.indexOf(field) != toIdx) {
94776             this.move(fromIdx, toIdx);
94777         }
94778     },
94779
94780     onFieldAdd: function(map, fieldId, column) {
94781         var me = this,
94782             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
94783             field = column.getEditor({ xtype: 'displayfield' });
94784
94785         me.insert(colIdx, field);
94786     },
94787
94788     onFieldRemove: function(map, fieldId, column) {
94789         var me = this,
94790             field = column.getEditor(),
94791             fieldEl = field.el;
94792         me.remove(field, false);
94793         if (fieldEl) {
94794             fieldEl.remove();
94795         }
94796     },
94797
94798     onFieldReplace: function(map, fieldId, column, oldColumn) {
94799         var me = this;
94800         me.onFieldRemove(map, fieldId, oldColumn);
94801     },
94802
94803     clearFields: function() {
94804         var me = this,
94805             map = me.columns;
94806         map.each(function(fieldId) {
94807             map.removeAtKey(fieldId);
94808         });
94809     },
94810
94811     getFloatingButtons: function() {
94812         var me = this,
94813             cssPrefix = Ext.baseCSSPrefix,
94814             btnsCss = cssPrefix + 'grid-row-editor-buttons',
94815             plugin = me.editingPlugin,
94816             btns;
94817
94818         if (!me.floatingButtons) {
94819             btns = me.floatingButtons = Ext.create('Ext.Container', {
94820                 renderTpl: [
94821                     '<div class="{baseCls}-ml"></div>',
94822                     '<div class="{baseCls}-mr"></div>',
94823                     '<div class="{baseCls}-bl"></div>',
94824                     '<div class="{baseCls}-br"></div>',
94825                     '<div class="{baseCls}-bc"></div>'
94826                 ],
94827
94828                 renderTo: me.el,
94829                 baseCls: btnsCss,
94830                 layout: {
94831                     type: 'hbox',
94832                     align: 'middle'
94833                 },
94834                 defaults: {
94835                     margins: '0 1 0 1'
94836                 },
94837                 items: [{
94838                     itemId: 'update',
94839                     flex: 1,
94840                     xtype: 'button',
94841                     handler: plugin.completeEdit,
94842                     scope: plugin,
94843                     text: me.saveBtnText,
94844                     disabled: !me.isValid
94845                 }, {
94846                     flex: 1,
94847                     xtype: 'button',
94848                     handler: plugin.cancelEdit,
94849                     scope: plugin,
94850                     text: me.cancelBtnText
94851                 }]
94852             });
94853
94854             // Prevent from bubbling click events to the grid view
94855             me.mon(btns.el, {
94856                 // BrowserBug: Opera 11.01
94857                 //   causes the view to scroll when a button is focused from mousedown
94858                 mousedown: Ext.emptyFn,
94859                 click: Ext.emptyFn,
94860                 stopEvent: true
94861             });
94862         }
94863         return me.floatingButtons;
94864     },
94865
94866     reposition: function(animateConfig) {
94867         var me = this,
94868             context = me.context,
94869             row = context && Ext.get(context.row),
94870             btns = me.getFloatingButtons(),
94871             btnEl = btns.el,
94872             grid = me.editingPlugin.grid,
94873             viewEl = grid.view.el,
94874             scroller = grid.verticalScroller,
94875
94876             // always get data from ColumnModel as its what drives
94877             // the GridView's sizing
94878             mainBodyWidth = grid.headerCt.getFullWidth(),
94879             scrollerWidth = grid.getWidth(),
94880
94881             // use the minimum as the columns may not fill up the entire grid
94882             // width
94883             width = Math.min(mainBodyWidth, scrollerWidth),
94884             scrollLeft = grid.view.el.dom.scrollLeft,
94885             btnWidth = btns.getWidth(),
94886             left = (width - btnWidth) / 2 + scrollLeft,
94887             y, rowH, newHeight,
94888
94889             invalidateScroller = function() {
94890                 if (scroller) {
94891                     scroller.invalidate();
94892                     btnEl.scrollIntoView(viewEl, false);
94893                 }
94894                 if (animateConfig && animateConfig.callback) {
94895                     animateConfig.callback.call(animateConfig.scope || me);
94896                 }
94897             };
94898
94899         // need to set both top/left
94900         if (row && Ext.isElement(row.dom)) {
94901             // Bring our row into view if necessary, so a row editor that's already
94902             // visible and animated to the row will appear smooth
94903             row.scrollIntoView(viewEl, false);
94904
94905             // Get the y position of the row relative to its top-most static parent.
94906             // offsetTop will be relative to the table, and is incorrect
94907             // when mixed with certain grid features (e.g., grouping).
94908             y = row.getXY()[1] - 5;
94909             rowH = row.getHeight();
94910             newHeight = rowH + 10;
94911
94912             // IE doesn't set the height quite right.
94913             // This isn't a border-box issue, it even happens
94914             // in IE8 and IE7 quirks.
94915             // TODO: Test in IE9!
94916             if (Ext.isIE) {
94917                 newHeight += 2;
94918             }
94919
94920             // Set editor height to match the row height
94921             if (me.getHeight() != newHeight) {
94922                 me.setHeight(newHeight);
94923                 me.el.setLeft(0);
94924             }
94925
94926             if (animateConfig) {
94927                 var animObj = {
94928                     to: {
94929                         y: y
94930                     },
94931                     duration: animateConfig.duration || 125,
94932                     listeners: {
94933                         afteranimate: function() {
94934                             invalidateScroller();
94935                             y = row.getXY()[1] - 5;
94936                             me.el.setY(y);
94937                         }
94938                     }
94939                 };
94940                 me.animate(animObj);
94941             } else {
94942                 me.el.setY(y);
94943                 invalidateScroller();
94944             }
94945         }
94946         if (me.getWidth() != mainBodyWidth) {
94947             me.setWidth(mainBodyWidth);
94948         }
94949         btnEl.setLeft(left);
94950     },
94951
94952     getEditor: function(fieldInfo) {
94953         var me = this;
94954
94955         if (Ext.isNumber(fieldInfo)) {
94956             // Query only form fields. This just future-proofs us in case we add
94957             // other components to RowEditor later on.  Don't want to mess with
94958             // indices.
94959             return me.query('>[isFormField]')[fieldInfo];
94960         } else if (fieldInfo instanceof Ext.grid.column.Column) {
94961             return fieldInfo.getEditor();
94962         }
94963     },
94964
94965     removeField: function(field) {
94966         var me = this;
94967
94968         // Incase we pass a column instead, which is fine
94969         field = me.getEditor(field);
94970         me.mun(field, 'validitychange', me.onValidityChange, me);
94971
94972         // Remove field/column from our mapping, which will fire the event to
94973         // remove the field from our container
94974         me.columns.removeKey(field.id);
94975     },
94976
94977     setField: function(column) {
94978         var me = this,
94979             field;
94980
94981         if (Ext.isArray(column)) {
94982             Ext.Array.forEach(column, me.setField, me);
94983             return;
94984         }
94985
94986         // Get a default display field if necessary
94987         field = column.getEditor(null, {
94988             xtype: 'displayfield',
94989             // Default display fields will not return values. This is done because
94990             // the display field will pick up column renderers from the grid.
94991             getModelData: function() {
94992                 return null;
94993             }
94994         });
94995         field.margins = '0 0 0 2';
94996         field.setWidth(column.getDesiredWidth() - 2);
94997         me.mon(field, 'change', me.onFieldChange, me);
94998
94999         // Maintain mapping of fields-to-columns
95000         // This will fire events that maintain our container items
95001         me.columns.add(field.id, column);
95002         if (column.hidden) {
95003             me.onColumnHide(column);
95004         }
95005         if (me.isVisible() && me.context) {
95006             me.renderColumnData(field, me.context.record);
95007         }
95008     },
95009
95010     loadRecord: function(record) {
95011         var me = this,
95012             form = me.getForm();
95013         form.loadRecord(record);
95014         if (form.isValid()) {
95015             me.hideToolTip();
95016         } else {
95017             me.showToolTip();
95018         }
95019
95020         // render display fields so they honor the column renderer/template
95021         Ext.Array.forEach(me.query('>displayfield'), function(field) {
95022             me.renderColumnData(field, record);
95023         }, me);
95024     },
95025
95026     renderColumnData: function(field, record) {
95027         var me = this,
95028             grid = me.editingPlugin.grid,
95029             headerCt = grid.headerCt,
95030             view = grid.view,
95031             store = view.store,
95032             column = me.columns.get(field.id),
95033             value = record.get(column.dataIndex);
95034
95035         // honor our column's renderer (TemplateHeader sets renderer for us!)
95036         if (column.renderer) {
95037             var metaData = { tdCls: '', style: '' },
95038                 rowIdx = store.indexOf(record),
95039                 colIdx = headerCt.getHeaderIndex(column);
95040
95041             value = column.renderer.call(
95042                 column.scope || headerCt.ownerCt,
95043                 value,
95044                 metaData,
95045                 record,
95046                 rowIdx,
95047                 colIdx,
95048                 store,
95049                 view
95050             );
95051         }
95052
95053         field.setRawValue(value);
95054         field.resetOriginalValue();
95055     },
95056
95057     beforeEdit: function() {
95058         var me = this;
95059
95060         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
95061             me.showToolTip();
95062             return false;
95063         }
95064     },
95065
95066     /**
95067      * Start editing the specified grid at the specified position.
95068      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
95069      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
95070      */
95071     startEdit: function(record, columnHeader) {
95072         var me = this,
95073             grid = me.editingPlugin.grid,
95074             view = grid.getView(),
95075             store = grid.store,
95076             context = me.context = Ext.apply(me.editingPlugin.context, {
95077                 view: grid.getView(),
95078                 store: store
95079             });
95080
95081         // make sure our row is selected before editing
95082         context.grid.getSelectionModel().select(record);
95083
95084         // Reload the record data
95085         me.loadRecord(record);
95086
95087         if (!me.isVisible()) {
95088             me.show();
95089             me.focusContextCell();
95090         } else {
95091             me.reposition({
95092                 callback: this.focusContextCell
95093             });
95094         }
95095     },
95096
95097     // Focus the cell on start edit based upon the current context
95098     focusContextCell: function() {
95099         var field = this.getEditor(this.context.colIdx);
95100         if (field && field.focus) {
95101             field.focus();
95102         }
95103     },
95104
95105     cancelEdit: function() {
95106         var me = this,
95107             form = me.getForm();
95108
95109         me.hide();
95110         form.clearInvalid();
95111         form.reset();
95112     },
95113
95114     completeEdit: function() {
95115         var me = this,
95116             form = me.getForm();
95117
95118         if (!form.isValid()) {
95119             return;
95120         }
95121
95122         form.updateRecord(me.context.record);
95123         me.hide();
95124         return true;
95125     },
95126
95127     onShow: function() {
95128         var me = this;
95129         me.callParent(arguments);
95130         me.reposition();
95131     },
95132
95133     onHide: function() {
95134         var me = this;
95135         me.callParent(arguments);
95136         me.hideToolTip();
95137         me.invalidateScroller();
95138         if (me.context) {
95139             me.context.view.focus();
95140             me.context = null;
95141         }
95142     },
95143
95144     isDirty: function() {
95145         var me = this,
95146             form = me.getForm();
95147         return form.isDirty();
95148     },
95149
95150     getToolTip: function() {
95151         var me = this,
95152             tip;
95153
95154         if (!me.tooltip) {
95155             tip = me.tooltip = Ext.createWidget('tooltip', {
95156                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
95157                 title: me.errorsText,
95158                 autoHide: false,
95159                 closable: true,
95160                 closeAction: 'disable',
95161                 anchor: 'left'
95162             });
95163         }
95164         return me.tooltip;
95165     },
95166
95167     hideToolTip: function() {
95168         var me = this,
95169             tip = me.getToolTip();
95170         if (tip.rendered) {
95171             tip.disable();
95172         }
95173         me.hiddenTip = false;
95174     },
95175
95176     showToolTip: function() {
95177         var me = this,
95178             tip = me.getToolTip(),
95179             context = me.context,
95180             row = Ext.get(context.row),
95181             viewEl = context.grid.view.el;
95182
95183         tip.setTarget(row);
95184         tip.showAt([-10000, -10000]);
95185         tip.body.update(me.getErrors());
95186         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
95187         me.repositionTip();
95188         tip.doLayout();
95189         tip.enable();
95190     },
95191
95192     repositionTip: function() {
95193         var me = this,
95194             tip = me.getToolTip(),
95195             context = me.context,
95196             row = Ext.get(context.row),
95197             viewEl = context.grid.view.el,
95198             viewHeight = viewEl.getHeight(),
95199             viewTop = me.lastScrollTop,
95200             viewBottom = viewTop + viewHeight,
95201             rowHeight = row.getHeight(),
95202             rowTop = row.dom.offsetTop,
95203             rowBottom = rowTop + rowHeight;
95204
95205         if (rowBottom > viewTop && rowTop < viewBottom) {
95206             tip.show();
95207             me.hiddenTip = false;
95208         } else {
95209             tip.hide();
95210             me.hiddenTip = true;
95211         }
95212     },
95213
95214     getErrors: function() {
95215         var me = this,
95216             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
95217             errors = [];
95218
95219         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
95220             errors = errors.concat(
95221                 Ext.Array.map(field.getErrors(), function(e) {
95222                     return '<li>' + e + '</li>';
95223                 })
95224             );
95225         }, me);
95226
95227         return dirtyText + '<ul>' + errors.join('') + '</ul>';
95228     },
95229
95230     invalidateScroller: function() {
95231         var me = this,
95232             context = me.context,
95233             scroller = context.grid.verticalScroller;
95234
95235         if (scroller) {
95236             scroller.invalidate();
95237         }
95238     }
95239 });
95240 /**
95241  * @class Ext.grid.header.Container
95242  * @extends Ext.container.Container
95243  *
95244  * Container which holds headers and is docked at the top or bottom of a TablePanel.
95245  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
95246  * As headers are hidden, moved or resized the headercontainer is responsible for
95247  * triggering changes within the view.
95248  */
95249 Ext.define('Ext.grid.header.Container', {
95250     extend: 'Ext.container.Container',
95251     uses: [
95252         'Ext.grid.ColumnLayout',
95253         'Ext.grid.column.Column',
95254         'Ext.menu.Menu',
95255         'Ext.menu.CheckItem',
95256         'Ext.menu.Separator',
95257         'Ext.grid.plugin.HeaderResizer',
95258         'Ext.grid.plugin.HeaderReorderer'
95259     ],
95260     border: true,
95261
95262     alias: 'widget.headercontainer',
95263
95264     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
95265     dock: 'top',
95266
95267     /**
95268      * @cfg {Number} weight
95269      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
95270      * This is so that it has more priority over things like toolbars.
95271      */
95272     weight: 100,
95273     defaultType: 'gridcolumn',
95274     /**
95275      * @cfg {Number} defaultWidth
95276      * Width of the header if no width or flex is specified. Defaults to 100.
95277      */
95278     defaultWidth: 100,
95279
95280
95281     sortAscText: 'Sort Ascending',
95282     sortDescText: 'Sort Descending',
95283     sortClearText: 'Clear Sort',
95284     columnsText: 'Columns',
95285
95286     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
95287     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
95288     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
95289
95290     // private; will probably be removed by 4.0
95291     triStateSort: false,
95292
95293     ddLock: false,
95294
95295     dragging: false,
95296
95297     /**
95298      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
95299      * @type Boolean
95300      * @property isGroupHeader
95301      */
95302
95303     /**
95304      * @cfg {Boolean} sortable
95305      * Provides the default sortable state for all Headers within this HeaderContainer.
95306      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
95307      * shared across every header and therefore turning it off will remove the menu
95308      * items for every header.
95309      */
95310     sortable: true,
95311
95312     initComponent: function() {
95313         var me = this;
95314
95315         me.headerCounter = 0;
95316         me.plugins = me.plugins || [];
95317
95318         // TODO: Pass in configurations to turn on/off dynamic
95319         //       resizing and disable resizing all together
95320
95321         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
95322         // Nested Group Headers are themselves HeaderContainers
95323         if (!me.isHeader) {
95324             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
95325             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
95326             if (!me.enableColumnResize) {
95327                 me.resizer.disable();
95328             }
95329             if (!me.enableColumnMove) {
95330                 me.reorderer.disable();
95331             }
95332             me.plugins.push(me.reorderer, me.resizer);
95333         }
95334
95335         // Base headers do not need a box layout
95336         if (me.isHeader && !me.items) {
95337             me.layout = 'auto';
95338         }
95339         // HeaderContainer and Group header needs a gridcolumn layout.
95340         else {
95341             me.layout = {
95342                 type: 'gridcolumn',
95343                 availableSpaceOffset: me.availableSpaceOffset,
95344                 align: 'stretchmax',
95345                 resetStretch: true
95346             };
95347         }
95348         me.defaults = me.defaults || {};
95349         Ext.applyIf(me.defaults, {
95350             width: me.defaultWidth,
95351             triStateSort: me.triStateSort,
95352             sortable: me.sortable
95353         });
95354         me.callParent();
95355         me.addEvents(
95356             /**
95357              * @event columnresize
95358              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95359              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95360              * @param {Number} width
95361              */
95362             'columnresize',
95363
95364             /**
95365              * @event headerclick
95366              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95367              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95368              * @param {Ext.EventObject} e
95369              * @param {HTMLElement} t
95370              */
95371             'headerclick',
95372
95373             /**
95374              * @event headertriggerclick
95375              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95376              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95377              * @param {Ext.EventObject} e
95378              * @param {HTMLElement} t
95379              */
95380             'headertriggerclick',
95381
95382             /**
95383              * @event columnmove
95384              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95385              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95386              * @param {Number} fromIdx
95387              * @param {Number} toIdx
95388              */
95389             'columnmove',
95390             /**
95391              * @event columnhide
95392              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95393              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95394              */
95395             'columnhide',
95396             /**
95397              * @event columnshow
95398              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95399              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95400              */
95401             'columnshow',
95402             /**
95403              * @event sortchange
95404              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
95405              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
95406              * @param {String} direction
95407              */
95408             'sortchange',
95409             /**
95410              * @event menucreate
95411              * Fired immediately after the column header menu is created.
95412              * @param {Ext.grid.header.Container} ct This instance
95413              * @param {Ext.menu.Menu} menu The Menu that was created
95414              */
95415             'menucreate'
95416         );
95417     },
95418
95419     onDestroy: function() {
95420         Ext.destroy(this.resizer, this.reorderer);
95421         this.callParent();
95422     },
95423     
95424     applyDefaults: function(config){
95425         /*
95426          * Ensure header.Container defaults don't get applied to a RowNumberer 
95427          * if an xtype is supplied. This isn't an ideal solution however it's 
95428          * much more likely that a RowNumberer with no options will be created, 
95429          * wanting to use the defaults specified on the class as opposed to 
95430          * those setup on the Container.
95431          */
95432         if (config && !config.isComponent && config.xtype == 'rownumberer') {
95433             return config;
95434         }
95435         return this.callParent([config]);
95436     },
95437
95438     applyColumnsState: function(columns) {
95439         if (!columns || !columns.length) {
95440             return;
95441         }
95442
95443         var me = this,
95444             i = 0,
95445             index,
95446             col;
95447
95448         Ext.each(columns, function (columnState) {
95449             col = me.down('gridcolumn[headerId=' + columnState.id + ']');
95450             if (col) {
95451                 index = me.items.indexOf(col);
95452                 if (i !== index) {
95453                     me.moveHeader(index, i);
95454                 }
95455
95456                 if (col.applyColumnState) {
95457                     col.applyColumnState(columnState);
95458                 }
95459                 ++i;
95460             }
95461         });
95462     },
95463
95464     getColumnsState: function () {
95465         var me = this,
95466             columns = [],
95467             state;
95468
95469         me.items.each(function (col) {
95470             state = col.getColumnState && col.getColumnState();
95471             if (state) {
95472                 columns.push(state);
95473             }
95474         });
95475
95476         return columns;
95477     },
95478
95479     // Invalidate column cache on add
95480     // We cannot refresh the View on every add because this method is called
95481     // when the HeaderDropZone moves Headers around, that will also refresh the view
95482     onAdd: function(c) {
95483         var me = this;
95484         if (!c.headerId) {
95485             c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
95486         }
95487         //<debug warn>
95488         if (Ext.global.console && Ext.global.console.warn) {
95489             if (!me._usedIDs) me._usedIDs = {};
95490             if (me._usedIDs[c.headerId]) {
95491                 Ext.global.console.warn(this.$className, 'attempted to reuse an existing id', c.headerId);
95492             }
95493             me._usedIDs[c.headerId] = true;
95494         }
95495         //</debug>
95496         me.callParent(arguments);
95497         me.purgeCache();
95498     },
95499
95500     // Invalidate column cache on remove
95501     // We cannot refresh the View on every remove because this method is called
95502     // when the HeaderDropZone moves Headers around, that will also refresh the view
95503     onRemove: function(c) {
95504         var me = this;
95505         me.callParent(arguments);
95506         me.purgeCache();
95507     },
95508
95509     afterRender: function() {
95510         this.callParent();
95511         var store   = this.up('[store]').store,
95512             sorters = store.sorters,
95513             first   = sorters.first(),
95514             hd;
95515
95516         if (first) {
95517             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
95518             if (hd) {
95519                 hd.setSortState(first.direction, false, true);
95520             }
95521         }
95522     },
95523
95524     afterLayout: function() {
95525         if (!this.isHeader) {
95526             var me = this,
95527                 topHeaders = me.query('>gridcolumn:not([hidden])'),
95528                 viewEl,
95529                 firstHeaderEl,
95530                 lastHeaderEl;
95531
95532             me.callParent(arguments);
95533
95534             if (topHeaders.length) {
95535                 firstHeaderEl = topHeaders[0].el;
95536                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
95537                     if (me.pastFirstHeaderEl) {
95538                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
95539                     }
95540                     firstHeaderEl.addCls(me.firstHeaderCls);
95541                     me.pastFirstHeaderEl = firstHeaderEl;
95542                 }
95543
95544                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
95545                 if (lastHeaderEl !== me.pastLastHeaderEl) {
95546                     if (me.pastLastHeaderEl) {
95547                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
95548                     }
95549                     lastHeaderEl.addCls(me.lastHeaderCls);
95550                     me.pastLastHeaderEl = lastHeaderEl;
95551                 }
95552             }
95553         }
95554
95555     },
95556
95557     onHeaderShow: function(header, preventLayout) {
95558         // Pass up to the GridSection
95559         var me = this,
95560             gridSection = me.ownerCt,
95561             menu = me.getMenu(),
95562             topItems, topItemsVisible,
95563             colCheckItem,
95564             itemToEnable,
95565             len, i;
95566
95567         if (menu) {
95568
95569             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
95570             if (colCheckItem) {
95571                 colCheckItem.setChecked(true, true);
95572             }
95573
95574             // There's more than one header visible, and we've disabled some checked items... re-enable them
95575             topItems = menu.query('#columnItem>menucheckitem[checked]');
95576             topItemsVisible = topItems.length;
95577             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
95578                 if (topItemsVisible == 1) {
95579                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
95580                 }
95581                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
95582                     itemToEnable = me.disabledMenuItems[i];
95583                     if (!itemToEnable.isDestroyed) {
95584                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
95585                     }
95586                 }
95587                 if (topItemsVisible == 1) {
95588                     me.disabledMenuItems = topItems;
95589                 } else {
95590                     me.disabledMenuItems = [];
95591                 }
95592             }
95593         }
95594
95595         // Only update the grid UI when we are notified about base level Header shows;
95596         // Group header shows just cause a layout of the HeaderContainer
95597         if (!header.isGroupHeader) {
95598             if (me.view) {
95599                 me.view.onHeaderShow(me, header, true);
95600             }
95601             if (gridSection) {
95602                 gridSection.onHeaderShow(me, header);
95603             }
95604         }
95605         me.fireEvent('columnshow', me, header);
95606
95607         // The header's own hide suppresses cascading layouts, so lay the headers out now
95608         if (preventLayout !== true) {
95609             me.doLayout();
95610         }
95611     },
95612
95613     doComponentLayout: function(){
95614         var me = this;
95615         if (me.view && me.view.saveScrollState) {
95616             me.view.saveScrollState();
95617         }
95618         me.callParent(arguments);
95619         if (me.view && me.view.restoreScrollState) {
95620             me.view.restoreScrollState();
95621         }
95622     },
95623
95624     onHeaderHide: function(header, suppressLayout) {
95625         // Pass up to the GridSection
95626         var me = this,
95627             gridSection = me.ownerCt,
95628             menu = me.getMenu(),
95629             colCheckItem;
95630
95631         if (menu) {
95632
95633             // If the header was hidden programmatically, sync the Menu state
95634             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
95635             if (colCheckItem) {
95636                 colCheckItem.setChecked(false, true);
95637             }
95638             me.setDisabledItems();
95639         }
95640
95641         // Only update the UI when we are notified about base level Header hides;
95642         if (!header.isGroupHeader) {
95643             if (me.view) {
95644                 me.view.onHeaderHide(me, header, true);
95645             }
95646             if (gridSection) {
95647                 gridSection.onHeaderHide(me, header);
95648             }
95649
95650             // The header's own hide suppresses cascading layouts, so lay the headers out now
95651             if (!suppressLayout) {
95652                 me.doLayout();
95653             }
95654         }
95655         me.fireEvent('columnhide', me, header);
95656     },
95657
95658     setDisabledItems: function(){
95659         var me = this,
95660             menu = me.getMenu(),
95661             i = 0,
95662             len,
95663             itemsToDisable,
95664             itemToDisable;
95665
95666         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
95667         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
95668         if ((itemsToDisable.length === 1)) {
95669             if (!me.disabledMenuItems) {
95670                 me.disabledMenuItems = [];
95671             }
95672
95673             // If down to only one column visible, also disable any descendant checkitems
95674             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
95675                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
95676             }
95677
95678             len = itemsToDisable.length;
95679             // Disable any further unchecking at any level.
95680             for (i = 0; i < len; i++) {
95681                 itemToDisable = itemsToDisable[i];
95682                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
95683
95684                     // If we only want to disable check change: it might be a disabled item, so enable it prior to
95685                     // setting its correct disablement level.
95686                     itemToDisable.disabled = false;
95687                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
95688                     me.disabledMenuItems.push(itemToDisable);
95689                 }
95690             }
95691         }
95692     },
95693
95694     /**
95695      * Temporarily lock the headerCt. This makes it so that clicking on headers
95696      * don't trigger actions like sorting or opening of the header menu. This is
95697      * done because extraneous events may be fired on the headers after interacting
95698      * with a drag drop operation.
95699      * @private
95700      */
95701     tempLock: function() {
95702         this.ddLock = true;
95703         Ext.Function.defer(function() {
95704             this.ddLock = false;
95705         }, 200, this);
95706     },
95707
95708     onHeaderResize: function(header, w, suppressFocus) {
95709         this.tempLock();
95710         if (this.view && this.view.rendered) {
95711             this.view.onHeaderResize(header, w, suppressFocus);
95712         }
95713     },
95714
95715     onHeaderClick: function(header, e, t) {
95716         this.fireEvent("headerclick", this, header, e, t);
95717     },
95718
95719     onHeaderTriggerClick: function(header, e, t) {
95720         // generate and cache menu, provide ability to cancel/etc
95721         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
95722             this.showMenuBy(t, header);
95723         }
95724     },
95725
95726     showMenuBy: function(t, header) {
95727         var menu = this.getMenu(),
95728             ascItem  = menu.down('#ascItem'),
95729             descItem = menu.down('#descItem'),
95730             sortableMth;
95731
95732         menu.activeHeader = menu.ownerCt = header;
95733         menu.setFloatParent(header);
95734         // TODO: remove coupling to Header's titleContainer el
95735         header.titleContainer.addCls(this.headerOpenCls);
95736
95737         // enable or disable asc & desc menu items based on header being sortable
95738         sortableMth = header.sortable ? 'enable' : 'disable';
95739         if (ascItem) {
95740             ascItem[sortableMth]();
95741         }
95742         if (descItem) {
95743             descItem[sortableMth]();
95744         }
95745         menu.showBy(t);
95746     },
95747
95748     // remove the trigger open class when the menu is hidden
95749     onMenuDeactivate: function() {
95750         var menu = this.getMenu();
95751         // TODO: remove coupling to Header's titleContainer el
95752         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
95753     },
95754
95755     moveHeader: function(fromIdx, toIdx) {
95756
95757         // An automatically expiring lock
95758         this.tempLock();
95759         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
95760     },
95761
95762     purgeCache: function() {
95763         var me = this;
95764         // Delete column cache - column order has changed.
95765         delete me.gridDataColumns;
95766         delete me.hideableColumns;
95767
95768         // Menu changes when columns are moved. It will be recreated.
95769         if (me.menu) {
95770             me.menu.destroy();
95771             delete me.menu;
95772         }
95773     },
95774
95775     onHeaderMoved: function(header, fromIdx, toIdx) {
95776         var me = this,
95777             gridSection = me.ownerCt;
95778
95779         if (gridSection && gridSection.onHeaderMove) {
95780             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
95781         }
95782         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
95783     },
95784
95785     /**
95786      * Gets the menu (and will create it if it doesn't already exist)
95787      * @private
95788      */
95789     getMenu: function() {
95790         var me = this;
95791
95792         if (!me.menu) {
95793             me.menu = Ext.create('Ext.menu.Menu', {
95794                 hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
95795                 items: me.getMenuItems(),
95796                 listeners: {
95797                     deactivate: me.onMenuDeactivate,
95798                     scope: me
95799                 }
95800             });
95801             me.setDisabledItems();
95802             me.fireEvent('menucreate', me, me.menu);
95803         }
95804         return me.menu;
95805     },
95806
95807     /**
95808      * Returns an array of menu items to be placed into the shared menu
95809      * across all headers in this header container.
95810      * @returns {Array} menuItems
95811      */
95812     getMenuItems: function() {
95813         var me = this,
95814             menuItems = [],
95815             hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
95816
95817         if (me.sortable) {
95818             menuItems = [{
95819                 itemId: 'ascItem',
95820                 text: me.sortAscText,
95821                 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
95822                 handler: me.onSortAscClick,
95823                 scope: me
95824             },{
95825                 itemId: 'descItem',
95826                 text: me.sortDescText,
95827                 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
95828                 handler: me.onSortDescClick,
95829                 scope: me
95830             }];
95831         }
95832         if (hideableColumns && hideableColumns.length) {
95833             menuItems.push('-', {
95834                 itemId: 'columnItem',
95835                 text: me.columnsText,
95836                 cls: Ext.baseCSSPrefix + 'cols-icon',
95837                 menu: hideableColumns
95838             });
95839         }
95840         return menuItems;
95841     },
95842
95843     // sort asc when clicking on item in menu
95844     onSortAscClick: function() {
95845         var menu = this.getMenu(),
95846             activeHeader = menu.activeHeader;
95847
95848         activeHeader.setSortState('ASC');
95849     },
95850
95851     // sort desc when clicking on item in menu
95852     onSortDescClick: function() {
95853         var menu = this.getMenu(),
95854             activeHeader = menu.activeHeader;
95855
95856         activeHeader.setSortState('DESC');
95857     },
95858
95859     /**
95860      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
95861      */
95862     getColumnMenu: function(headerContainer) {
95863         var menuItems = [],
95864             i = 0,
95865             item,
95866             items = headerContainer.query('>gridcolumn[hideable]'),
95867             itemsLn = items.length,
95868             menuItem;
95869
95870         for (; i < itemsLn; i++) {
95871             item = items[i];
95872             menuItem = Ext.create('Ext.menu.CheckItem', {
95873                 text: item.text,
95874                 checked: !item.hidden,
95875                 hideOnClick: false,
95876                 headerId: item.id,
95877                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
95878                 checkHandler: this.onColumnCheckChange,
95879                 scope: this
95880             });
95881             if (itemsLn === 1) {
95882                 menuItem.disabled = true;
95883             }
95884             menuItems.push(menuItem);
95885
95886             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
95887             // then the associated menu item must also be destroyed.
95888             item.on({
95889                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
95890             });
95891         }
95892         return menuItems;
95893     },
95894
95895     onColumnCheckChange: function(checkItem, checked) {
95896         var header = Ext.getCmp(checkItem.headerId);
95897         header[checked ? 'show' : 'hide']();
95898     },
95899
95900     /**
95901      * Get the columns used for generating a template via TableChunker.
95902      * Returns an array of all columns and their
95903      *  - dataIndex
95904      *  - align
95905      *  - width
95906      *  - id
95907      *  - columnId - used to create an identifying CSS class
95908      *  - cls The tdCls configuration from the Column object
95909      *  @private
95910      */
95911     getColumnsForTpl: function(flushCache) {
95912         var cols    = [],
95913             headers   = this.getGridColumns(flushCache),
95914             headersLn = headers.length,
95915             i = 0,
95916             header,
95917             width;
95918
95919         for (; i < headersLn; i++) {
95920             header = headers[i];
95921
95922             if (header.hidden || header.up('headercontainer[hidden=true]')) {
95923                 width = 0;
95924             } else {
95925                 width = header.getDesiredWidth();
95926                 // IE6 and IE7 bug.
95927                 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
95928                 // We need to increment the passed with in this case.
95929                 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
95930                     width += 1;
95931                 }
95932             }
95933             cols.push({
95934                 dataIndex: header.dataIndex,
95935                 align: header.align,
95936                 width: width,
95937                 id: header.id,
95938                 cls: header.tdCls,
95939                 columnId: header.getItemId()
95940             });
95941         }
95942         return cols;
95943     },
95944
95945     /**
95946      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
95947      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
95948      */
95949     getColumnCount: function() {
95950         return this.getGridColumns().length;
95951     },
95952
95953     /**
95954      * Gets the full width of all columns that are visible.
95955      */
95956     getFullWidth: function(flushCache) {
95957         var fullWidth = 0,
95958             headers     = this.getVisibleGridColumns(flushCache),
95959             headersLn   = headers.length,
95960             i         = 0;
95961
95962         for (; i < headersLn; i++) {
95963             if (!isNaN(headers[i].width)) {
95964                 // use headers getDesiredWidth if its there
95965                 if (headers[i].getDesiredWidth) {
95966                     fullWidth += headers[i].getDesiredWidth();
95967                 // if injected a diff cmp use getWidth
95968                 } else {
95969                     fullWidth += headers[i].getWidth();
95970                 }
95971             }
95972         }
95973         return fullWidth;
95974     },
95975
95976     // invoked internally by a header when not using triStateSorting
95977     clearOtherSortStates: function(activeHeader) {
95978         var headers   = this.getGridColumns(),
95979             headersLn = headers.length,
95980             i         = 0,
95981             oldSortState;
95982
95983         for (; i < headersLn; i++) {
95984             if (headers[i] !== activeHeader) {
95985                 oldSortState = headers[i].sortState;
95986                 // unset the sortstate and dont recurse
95987                 headers[i].setSortState(null, true);
95988                 //if (!silent && oldSortState !== null) {
95989                 //    this.fireEvent('sortchange', this, headers[i], null);
95990                 //}
95991             }
95992         }
95993     },
95994
95995     /**
95996      * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
95997      * level, and does not return <i>grouped</i> headers which contain sub headers.
95998      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
95999      * @returns {Array}
96000      */
96001     getVisibleGridColumns: function(refreshCache) {
96002         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
96003     },
96004
96005     /**
96006      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
96007      * level, and does not return <i>grouped</i> headers which contain sub headers.
96008      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
96009      * @returns {Array}
96010      */
96011     getGridColumns: function(refreshCache) {
96012         var me = this,
96013             result = refreshCache ? null : me.gridDataColumns;
96014
96015         // Not already got the column cache, so collect the base columns
96016         if (!result) {
96017             me.gridDataColumns = result = [];
96018             me.cascade(function(c) {
96019                 if ((c !== me) && !c.isGroupHeader) {
96020                     result.push(c);
96021                 }
96022             });
96023         }
96024
96025         return result;
96026     },
96027
96028     /**
96029      * @private
96030      * For use by column headers in determining whether there are any hideable columns when deciding whether or not
96031      * the header menu should be disabled.
96032      */
96033     getHideableColumns: function(refreshCache) {
96034         var me = this,
96035             result = refreshCache ? null : me.hideableColumns;
96036
96037         if (!result) {
96038             result = me.hideableColumns = me.query('[hideable]');
96039         }
96040         return result;
96041     },
96042
96043     /**
96044      * Get the index of a leaf level header regardless of what the nesting
96045      * structure is.
96046      */
96047     getHeaderIndex: function(header) {
96048         var columns = this.getGridColumns();
96049         return Ext.Array.indexOf(columns, header);
96050     },
96051
96052     /**
96053      * Get a leaf level header by index regardless of what the nesting
96054      * structure is.
96055      */
96056     getHeaderAtIndex: function(index) {
96057         var columns = this.getGridColumns();
96058         return columns[index];
96059     },
96060
96061     /**
96062      * Maps the record data to base it on the header id's.
96063      * This correlates to the markup/template generated by
96064      * TableChunker.
96065      */
96066     prepareData: function(data, rowIdx, record, view, panel) {
96067         var obj       = {},
96068             headers   = this.gridDataColumns || this.getGridColumns(),
96069             headersLn = headers.length,
96070             colIdx    = 0,
96071             header,
96072             headerId,
96073             renderer,
96074             value,
96075             metaData,
96076             store = panel.store;
96077
96078         for (; colIdx < headersLn; colIdx++) {
96079             metaData = {
96080                 tdCls: '',
96081                 style: ''
96082             };
96083             header = headers[colIdx];
96084             headerId = header.id;
96085             renderer = header.renderer;
96086             value = data[header.dataIndex];
96087
96088             // When specifying a renderer as a string, it always resolves
96089             // to Ext.util.Format
96090             if (typeof renderer === "string") {
96091                 header.renderer = renderer = Ext.util.Format[renderer];
96092             }
96093
96094             if (typeof renderer === "function") {
96095                 value = renderer.call(
96096                     header.scope || this.ownerCt,
96097                     value,
96098                     // metadata per cell passing an obj by reference so that
96099                     // it can be manipulated inside the renderer
96100                     metaData,
96101                     record,
96102                     rowIdx,
96103                     colIdx,
96104                     store,
96105                     view
96106                 );
96107             }
96108
96109             // <debug>
96110             if (metaData.css) {
96111                 // This warning attribute is used by the compat layer
96112                 obj.cssWarning = true;
96113                 metaData.tdCls = metaData.css;
96114                 delete metaData.css;
96115             }
96116             // </debug>
96117
96118             obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
96119             obj[headerId+'-tdCls'] = metaData.tdCls;
96120             obj[headerId+'-tdAttr'] = metaData.tdAttr;
96121             obj[headerId+'-style'] = metaData.style;
96122             if (value === undefined || value === null || value === '') {
96123                 value = '&#160;';
96124             }
96125             obj[headerId] = value;
96126         }
96127         return obj;
96128     },
96129
96130     expandToFit: function(header) {
96131         if (this.view) {
96132             this.view.expandToFit(header);
96133         }
96134     }
96135 });
96136
96137 /**
96138  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
96139  * both the grid header configuration as well as displaying data within the grid itself. If the
96140  * {@link #columns} configuration is specified, this column will become a column group and can
96141  * contain other columns inside. In general, this class will not be created directly, rather
96142  * an array of column configurations will be passed to the grid:
96143  *
96144  *     @example
96145  *     Ext.create('Ext.data.Store', {
96146  *         storeId:'employeeStore',
96147  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
96148  *         data:[
96149  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
96150  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
96151  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
96152  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
96153  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
96154  *         ]
96155  *     });
96156  *
96157  *     Ext.create('Ext.grid.Panel', {
96158  *         title: 'Column Demo',
96159  *         store: Ext.data.StoreManager.lookup('employeeStore'),
96160  *         columns: [
96161  *             {text: 'First Name',  dataIndex:'firstname'},
96162  *             {text: 'Last Name',  dataIndex:'lastname'},
96163  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
96164  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
96165  *         ],
96166  *         width: 400,
96167  *         renderTo: Ext.getBody()
96168  *     });
96169  *
96170  * # Convenience Subclasses
96171  *
96172  * There are several column subclasses that provide default rendering for various data types
96173  *
96174  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
96175  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
96176  *  - {@link Ext.grid.column.Date}: Renders for date values
96177  *  - {@link Ext.grid.column.Number}: Renders for numeric values
96178  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
96179  *
96180  * # Setting Sizes
96181  *
96182  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
96183  * be given an explicit width value or a flex configuration. If no width is specified the grid will
96184  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
96185  * the width of the child columns, so a width option should not be specified in that case.
96186  *
96187  * # Header Options
96188  *
96189  *  - {@link #text}: Sets the header text for the column
96190  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
96191  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
96192  *  - {@link #menuDisabled}: Disables the column header menu
96193  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
96194  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
96195  *
96196  * # Data Options
96197  *
96198  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
96199  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
96200  */
96201 Ext.define('Ext.grid.column.Column', {
96202     extend: 'Ext.grid.header.Container',
96203     alias: 'widget.gridcolumn',
96204     requires: ['Ext.util.KeyNav'],
96205     alternateClassName: 'Ext.grid.Column',
96206
96207     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
96208
96209     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
96210     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
96211
96212     handleWidth: 5,
96213
96214     sortState: null,
96215
96216     possibleSortStates: ['ASC', 'DESC'],
96217
96218     renderTpl:
96219         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
96220             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
96221                 '{text}' +
96222             '</span>' +
96223             '<tpl if="!values.menuDisabled">'+
96224                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
96225             '</tpl>' +
96226         '</div>',
96227
96228     /**
96229      * @cfg {Object[]} columns
96230      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
96231      * `columns` config.
96232      *
96233      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
96234      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
96235      */
96236
96237     /**
96238      * @cfg {String} dataIndex
96239      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
96240      * which to draw the column's value. **Required.**
96241      */
96242     dataIndex: null,
96243
96244     /**
96245      * @cfg {String} text
96246      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
96247      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
96248      */
96249     text: '&#160;',
96250
96251     /**
96252      * @cfg {Boolean} sortable
96253      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
96254      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
96255      */
96256     sortable: true,
96257
96258     /**
96259      * @cfg {Boolean} groupable
96260      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
96261      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
96262      * disable (but still show) the group option in the header menu for the column.
96263      */
96264
96265     /**
96266      * @cfg {Boolean} fixed
96267      * @deprecated.
96268      * True to prevent the column from being resizable.
96269      */
96270
96271     /**
96272      * @cfg {Boolean} resizable
96273      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
96274      */
96275
96276     /**
96277      * @cfg {Boolean} hideable
96278      * False to prevent the user from hiding this column. Defaults to true.
96279      */
96280     hideable: true,
96281
96282     /**
96283      * @cfg {Boolean} menuDisabled
96284      * True to disable the column header menu containing sort/hide options. Defaults to false.
96285      */
96286     menuDisabled: false,
96287
96288     /**
96289      * @cfg {Function} renderer
96290      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
96291      * before it is rendered. Example:
96292      *
96293      *     {
96294      *         renderer: function(value){
96295      *             if (value === 1) {
96296      *                 return '1 person';
96297      *             }
96298      *             return value + ' people';
96299      *         }
96300      *     }
96301      *
96302      * @cfg {Object} renderer.value The data value for the current cell
96303      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
96304      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
96305      * @cfg {Ext.data.Model} renderer.record The record for the current row
96306      * @cfg {Number} renderer.rowIndex The index of the current row
96307      * @cfg {Number} renderer.colIndex The index of the current column
96308      * @cfg {Ext.data.Store} renderer.store The data store
96309      * @cfg {Ext.view.View} renderer.view The current view
96310      * @cfg {String} renderer.return The HTML string to be rendered.
96311      */
96312     renderer: false,
96313
96314     /**
96315      * @cfg {String} align
96316      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
96317      */
96318     align: 'left',
96319
96320     /**
96321      * @cfg {Boolean} draggable
96322      * False to disable drag-drop reordering of this column. Defaults to true.
96323      */
96324     draggable: true,
96325
96326     // Header does not use the typical ComponentDraggable class and therefore we
96327     // override this with an emptyFn. It is controlled at the HeaderDragZone.
96328     initDraggable: Ext.emptyFn,
96329
96330     /**
96331      * @cfg {String} tdCls
96332      * A CSS class names to apply to the table cells for this column.
96333      */
96334
96335     /**
96336      * @cfg {Object/String} editor
96337      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
96338      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
96339      */
96340
96341     /**
96342      * @cfg {Object/String} field
96343      * Alias for {@link #editor}.
96344      * @deprecated 4.0.5 Use {@link #editor} instead.
96345      */
96346
96347     /**
96348      * @property {Ext.Element} triggerEl
96349      * Element that acts as button for column header dropdown menu.
96350      */
96351
96352     /**
96353      * @property {Ext.Element} textEl
96354      * Element that contains the text in column header.
96355      */
96356
96357     /**
96358      * @private
96359      * Set in this class to identify, at runtime, instances which are not instances of the
96360      * HeaderContainer base class, but are in fact, the subclass: Header.
96361      */
96362     isHeader: true,
96363
96364     initComponent: function() {
96365         var me = this,
96366             i,
96367             len,
96368             item;
96369
96370         if (Ext.isDefined(me.header)) {
96371             me.text = me.header;
96372             delete me.header;
96373         }
96374
96375         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
96376         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
96377         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
96378         if (me.flex) {
96379             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
96380         }
96381         // Non-flexed Headers may never be squeezed in the event of a shortfall so
96382         // always set their minWidth to their current width.
96383         else {
96384             me.minWidth = me.width;
96385         }
96386
96387         if (!me.triStateSort) {
96388             me.possibleSortStates.length = 2;
96389         }
96390
96391         // A group header; It contains items which are themselves Headers
96392         if (Ext.isDefined(me.columns)) {
96393             me.isGroupHeader = true;
96394
96395             //<debug>
96396             if (me.dataIndex) {
96397                 Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
96398             }
96399             if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
96400                 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.');
96401             }
96402             //</debug>
96403
96404             // The headers become child items
96405             me.items = me.columns;
96406             delete me.columns;
96407             delete me.flex;
96408             me.width = 0;
96409
96410             // Acquire initial width from sub headers
96411             for (i = 0, len = me.items.length; i < len; i++) {
96412                 item = me.items[i];
96413                 if (!item.hidden) {
96414                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
96415                 }
96416                 //<debug>
96417                 if (item.flex) {
96418                     Ext.Error.raise('Ext.grid.column.Column: items of a grouped header do not support flexed values. Each item must explicitly define its width.');
96419                 }
96420                 //</debug>
96421             }
96422             me.minWidth = me.width;
96423
96424             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
96425             me.sortable = false;
96426             me.resizable = false;
96427             me.align = 'center';
96428         }
96429
96430         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
96431
96432         // Initialize as a HeaderContainer
96433         me.callParent(arguments);
96434     },
96435
96436     onAdd: function(childHeader) {
96437         childHeader.isSubHeader = true;
96438         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
96439         this.callParent(arguments);
96440     },
96441
96442     onRemove: function(childHeader) {
96443         childHeader.isSubHeader = false;
96444         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
96445         this.callParent(arguments);
96446     },
96447
96448     initRenderData: function() {
96449         var me = this;
96450
96451         Ext.applyIf(me.renderData, {
96452             text: me.text,
96453             menuDisabled: me.menuDisabled
96454         });
96455         return me.callParent(arguments);
96456     },
96457
96458     applyColumnState: function (state) {
96459         var me = this,
96460             defined = Ext.isDefined;
96461             
96462         // apply any columns
96463         me.applyColumnsState(state.columns);
96464
96465         // Only state properties which were saved should be restored.
96466         // (Only user-changed properties were saved by getState)
96467         if (defined(state.hidden)) {
96468             me.hidden = state.hidden;
96469         }
96470         if (defined(state.locked)) {
96471             me.locked = state.locked;
96472         }
96473         if (defined(state.sortable)) {
96474             me.sortable = state.sortable;
96475         }
96476         if (defined(state.width)) {
96477             delete me.flex;
96478             me.width = state.width;
96479         } else if (defined(state.flex)) {
96480             delete me.width;
96481             me.flex = state.flex;
96482         }
96483     },
96484
96485     getColumnState: function () {
96486         var me = this,
96487             columns = [],
96488             state = {
96489                 id: me.headerId
96490             };
96491
96492         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
96493         
96494         if (me.isGroupHeader) {
96495             me.items.each(function(column){
96496                 columns.push(column.getColumnState());
96497             });
96498             if (columns.length) {
96499                 state.columns = columns;
96500             }
96501         } else if (me.isSubHeader && me.ownerCt.hidden) {
96502             // don't set hidden on the children so they can auto height
96503             delete me.hidden;
96504         }
96505
96506         if ('width' in state) {
96507             delete state.flex; // width wins
96508         }
96509         return state;
96510     },
96511
96512     /**
96513      * Sets the header text for this Column.
96514      * @param {String} text The header to display on this Column.
96515      */
96516     setText: function(text) {
96517         this.text = text;
96518         if (this.rendered) {
96519             this.textEl.update(text);
96520         }
96521     },
96522
96523     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
96524     // Group Headers are themselves HeaderContainers
96525     getOwnerHeaderCt: function() {
96526         return this.up(':not([isHeader])');
96527     },
96528
96529     /**
96530      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
96531      * is a group column, it returns `false`.
96532      * @return {Number}
96533      */
96534     getIndex: function() {
96535         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
96536     },
96537
96538     onRender: function() {
96539         var me = this,
96540             grid = me.up('tablepanel');
96541
96542         // Disable the menu if there's nothing to show in the menu, ie:
96543         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
96544         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
96545             me.menuDisabled = true;
96546         }
96547         me.callParent(arguments);
96548     },
96549
96550     afterRender: function() {
96551         var me = this,
96552             el = me.el;
96553
96554         me.callParent(arguments);
96555
96556         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
96557
96558         me.mon(el, {
96559             click:     me.onElClick,
96560             dblclick:  me.onElDblClick,
96561             scope:     me
96562         });
96563
96564         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
96565         // must be fixed when focus management will be implemented.
96566         if (!Ext.isIE8 || !Ext.isStrict) {
96567             me.mon(me.getFocusEl(), {
96568                 focus: me.onTitleMouseOver,
96569                 blur: me.onTitleMouseOut,
96570                 scope: me
96571             });
96572         }
96573
96574         me.mon(me.titleContainer, {
96575             mouseenter:  me.onTitleMouseOver,
96576             mouseleave:  me.onTitleMouseOut,
96577             scope:      me
96578         });
96579
96580         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
96581             enter: me.onEnterKey,
96582             down: me.onDownKey,
96583             scope: me
96584         });
96585     },
96586
96587     /**
96588      * Sets the width of this Column.
96589      * @param {Number} width New width.
96590      */
96591     setWidth: function(width, /* private - used internally */ doLayout) {
96592         var me = this,
96593             headerCt = me.ownerCt,
96594             siblings,
96595             len, i,
96596             oldWidth = me.getWidth(),
96597             groupWidth = 0,
96598             sibling;
96599
96600         if (width !== oldWidth) {
96601             me.oldWidth = oldWidth;
96602
96603             // Non-flexed Headers may never be squeezed in the event of a shortfall so
96604             // always set the minWidth to their current width.
96605             me.minWidth = me.width = width;
96606
96607             // Bubble size changes upwards to group headers
96608             if (headerCt.isGroupHeader) {
96609                 siblings = headerCt.items.items;
96610                 len = siblings.length;
96611
96612                 for (i = 0; i < len; i++) {
96613                     sibling = siblings[i];
96614                     if (!sibling.hidden) {
96615                         groupWidth += (sibling === me) ? width : sibling.getWidth();
96616                     }
96617                 }
96618                 headerCt.setWidth(groupWidth, doLayout);
96619             } else if (doLayout !== false) {
96620                 // Allow the owning Container to perform the sizing
96621                 headerCt.doLayout();
96622             }
96623         }
96624     },
96625
96626     afterComponentLayout: function(width, height) {
96627         var me = this,
96628             ownerHeaderCt = this.getOwnerHeaderCt();
96629
96630         me.callParent(arguments);
96631
96632         // Only changes at the base level inform the grid's HeaderContainer which will update the View
96633         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
96634         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
96635         if (width && !me.isGroupHeader && ownerHeaderCt) {
96636             ownerHeaderCt.onHeaderResize(me, width, true);
96637         }
96638         if (me.oldWidth && (width !== me.oldWidth)) {
96639             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
96640         }
96641         delete me.oldWidth;
96642     },
96643
96644     // private
96645     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
96646     // Total available header height must be passed to enable padding for inner elements to be calculated.
96647     setPadding: function(headerHeight) {
96648         var me = this,
96649             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
96650
96651         // Top title containing element must stretch to match height of sibling group headers
96652         if (!me.isGroupHeader) {
96653             if (me.titleContainer.getHeight() < headerHeight) {
96654                 me.titleContainer.dom.style.height = headerHeight + 'px';
96655             }
96656         }
96657         headerHeight = me.titleContainer.getViewSize().height;
96658
96659         // Vertically center the header text in potentially vertically stretched header
96660         if (lineHeight) {
96661             me.titleContainer.setStyle({
96662                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
96663             });
96664         }
96665
96666         // Only IE needs this
96667         if (Ext.isIE && me.triggerEl) {
96668             me.triggerEl.setHeight(headerHeight);
96669         }
96670     },
96671
96672     onDestroy: function() {
96673         var me = this;
96674         // force destroy on the textEl, IE reports a leak
96675         Ext.destroy(me.textEl, me.keyNav);
96676         delete me.keyNav;
96677         me.callParent(arguments);
96678     },
96679
96680     onTitleMouseOver: function() {
96681         this.titleContainer.addCls(this.hoverCls);
96682     },
96683
96684     onTitleMouseOut: function() {
96685         this.titleContainer.removeCls(this.hoverCls);
96686     },
96687
96688     onDownKey: function(e) {
96689         if (this.triggerEl) {
96690             this.onElClick(e, this.triggerEl.dom || this.el.dom);
96691         }
96692     },
96693
96694     onEnterKey: function(e) {
96695         this.onElClick(e, this.el.dom);
96696     },
96697
96698     /**
96699      * @private
96700      * Double click
96701      * @param e
96702      * @param t
96703      */
96704     onElDblClick: function(e, t) {
96705         var me = this,
96706             ownerCt = me.ownerCt;
96707         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
96708             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
96709         }
96710     },
96711
96712     onElClick: function(e, t) {
96713
96714         // The grid's docked HeaderContainer.
96715         var me = this,
96716             ownerHeaderCt = me.getOwnerHeaderCt();
96717
96718         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
96719             // Firefox doesn't check the current target in a within check.
96720             // Therefore we check the target directly and then within (ancestors)
96721             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
96722                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
96723             // if its not on the left hand edge, sort
96724             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
96725                 me.toggleSortState();
96726                 ownerHeaderCt.onHeaderClick(me, e, t);
96727             }
96728         }
96729     },
96730
96731     /**
96732      * @private
96733      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
96734      * @param {String} type Event type, eg 'click'
96735      * @param {Ext.view.Table} view TableView Component
96736      * @param {HTMLElement} cell Cell HtmlElement the event took place within
96737      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
96738      * @param {Number} cellIndex Cell index within the row
96739      * @param {Ext.EventObject} e Original event
96740      */
96741     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
96742         return this.fireEvent.apply(this, arguments);
96743     },
96744
96745     toggleSortState: function() {
96746         var me = this,
96747             idx,
96748             nextIdx;
96749
96750         if (me.sortable) {
96751             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
96752
96753             nextIdx = (idx + 1) % me.possibleSortStates.length;
96754             me.setSortState(me.possibleSortStates[nextIdx]);
96755         }
96756     },
96757
96758     doSort: function(state) {
96759         var ds = this.up('tablepanel').store;
96760         ds.sort({
96761             property: this.getSortParam(),
96762             direction: state
96763         });
96764     },
96765
96766     /**
96767      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
96768      * need to be overriden in most cases.
96769      * @return {String}
96770      */
96771     getSortParam: function() {
96772         return this.dataIndex;
96773     },
96774
96775     //setSortState: function(state, updateUI) {
96776     //setSortState: function(state, doSort) {
96777     setSortState: function(state, skipClear, initial) {
96778         var me = this,
96779             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
96780             ascCls = colSortClsPrefix + 'ASC',
96781             descCls = colSortClsPrefix + 'DESC',
96782             nullCls = colSortClsPrefix + 'null',
96783             ownerHeaderCt = me.getOwnerHeaderCt(),
96784             oldSortState = me.sortState;
96785
96786         if (oldSortState !== state && me.getSortParam()) {
96787             me.addCls(colSortClsPrefix + state);
96788             // don't trigger a sort on the first time, we just want to update the UI
96789             if (state && !initial) {
96790                 me.doSort(state);
96791             }
96792             switch (state) {
96793                 case 'DESC':
96794                     me.removeCls([ascCls, nullCls]);
96795                     break;
96796                 case 'ASC':
96797                     me.removeCls([descCls, nullCls]);
96798                     break;
96799                 case null:
96800                     me.removeCls([ascCls, descCls]);
96801                     break;
96802             }
96803             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
96804                 ownerHeaderCt.clearOtherSortStates(me);
96805             }
96806             me.sortState = state;
96807             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
96808         }
96809     },
96810
96811     hide: function() {
96812         var me = this,
96813             items,
96814             len, i,
96815             lb,
96816             newWidth = 0,
96817             ownerHeaderCt = me.getOwnerHeaderCt();
96818
96819         // Hiding means setting to zero width, so cache the width
96820         me.oldWidth = me.getWidth();
96821
96822         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
96823         if (me.isGroupHeader) {
96824             items = me.items.items;
96825             me.callParent(arguments);
96826             ownerHeaderCt.onHeaderHide(me);
96827             for (i = 0, len = items.length; i < len; i++) {
96828                 items[i].hidden = true;
96829                 ownerHeaderCt.onHeaderHide(items[i], true);
96830             }
96831             return;
96832         }
96833
96834         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
96835         lb = me.ownerCt.componentLayout.layoutBusy;
96836         me.ownerCt.componentLayout.layoutBusy = true;
96837         me.callParent(arguments);
96838         me.ownerCt.componentLayout.layoutBusy = lb;
96839
96840         // Notify owning HeaderContainer
96841         ownerHeaderCt.onHeaderHide(me);
96842
96843         if (me.ownerCt.isGroupHeader) {
96844             // If we've just hidden the last header in a group, then hide the group
96845             items = me.ownerCt.query('>:not([hidden])');
96846             if (!items.length) {
96847                 me.ownerCt.hide();
96848             }
96849             // Size the group down to accommodate fewer sub headers
96850             else {
96851                 for (i = 0, len = items.length; i < len; i++) {
96852                     newWidth += items[i].getWidth();
96853                 }
96854                 me.ownerCt.minWidth = newWidth;
96855                 me.ownerCt.setWidth(newWidth);
96856             }
96857         }
96858     },
96859
96860     show: function() {
96861         var me = this,
96862             ownerCt = me.ownerCt,
96863             ownerCtCompLayout = ownerCt.componentLayout,
96864             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
96865             ownerCtLayout = ownerCt.layout,
96866             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
96867             items,
96868             len, i,
96869             item,
96870             newWidth = 0;
96871
96872         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
96873
96874         // Suspend our owner's layouts (both component and container):
96875         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
96876
96877         me.callParent(arguments);
96878
96879         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
96880         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
96881
96882         // If a sub header, ensure that the group header is visible
96883         if (me.isSubHeader) {
96884             if (!ownerCt.isVisible()) {
96885                 ownerCt.show();
96886             }
96887         }
96888
96889         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
96890         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
96891             items = me.query('>*');
96892             for (i = 0, len = items.length; i < len; i++) {
96893                 item = items[i];
96894                 item.preventLayout = true;
96895                 item.show();
96896                 newWidth += item.getWidth();
96897                 delete item.preventLayout;
96898             }
96899             me.setWidth(newWidth);
96900         }
96901
96902         // Resize the owning group to accommodate
96903         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
96904             items = ownerCt.query('>:not([hidden])');
96905             for (i = 0, len = items.length; i < len; i++) {
96906                 newWidth += items[i].getWidth();
96907             }
96908             ownerCt.minWidth = newWidth;
96909             ownerCt.setWidth(newWidth);
96910         }
96911
96912         // Notify owning HeaderContainer
96913         ownerCt = me.getOwnerHeaderCt();
96914         if (ownerCt) {
96915             ownerCt.onHeaderShow(me, me.preventLayout);
96916         }
96917     },
96918
96919     getDesiredWidth: function() {
96920         var me = this;
96921         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
96922             // headers always have either a width or a flex
96923             // because HeaderContainer sets a defaults width
96924             // therefore we can ignore the natural width
96925             // we use the componentLayout's tracked width so that
96926             // we can calculate the desired width when rendered
96927             // but not visible because its being obscured by a layout
96928             return me.componentLayout.lastComponentSize.width;
96929         // Flexed but yet to be rendered this could be the case
96930         // where a HeaderContainer and Headers are simply used as data
96931         // structures and not rendered.
96932         }
96933         else if (me.flex) {
96934             // this is going to be wrong, the defaultWidth
96935             return me.width;
96936         }
96937         else {
96938             return me.width;
96939         }
96940     },
96941
96942     getCellSelector: function() {
96943         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
96944     },
96945
96946     getCellInnerSelector: function() {
96947         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
96948     },
96949
96950     isOnLeftEdge: function(e) {
96951         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
96952     },
96953
96954     isOnRightEdge: function(e) {
96955         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
96956     }
96957
96958     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
96959     // when the editing plugin is injected
96960
96961     /**
96962      * @method getEditor
96963      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
96964      * associated with the Header the method will return false. If the field has not been instantiated it will be
96965      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
96966      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
96967      * @param {Object} defaultField An object representing a default field to be created
96968      * @return {Ext.form.field.Field} field
96969      */
96970     /**
96971      * @method setEditor
96972      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
96973      * been enabled on the grid.
96974      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
96975      * assumed.
96976      */
96977 });
96978
96979 /**
96980  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
96981  * an automatic row numbering column.
96982  * 
96983  * Usage:
96984  *
96985  *     columns: [
96986  *         {xtype: 'rownumberer'},
96987  *         {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
96988  *         {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
96989  *         {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
96990  *         {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
96991  *         {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
96992  *     ]
96993  *
96994  */
96995 Ext.define('Ext.grid.RowNumberer', {
96996     extend: 'Ext.grid.column.Column',
96997     alias: 'widget.rownumberer',
96998
96999     /**
97000      * @cfg {String} text
97001      * Any valid text or HTML fragment to display in the header cell for the row number column.
97002      */
97003     text: "&#160",
97004
97005     /**
97006      * @cfg {Number} width
97007      * The default width in pixels of the row number column.
97008      */
97009     width: 23,
97010
97011     /**
97012      * @cfg {Boolean} sortable
97013      * True if the row number column is sortable.
97014      * @hide
97015      */
97016     sortable: false,
97017
97018     align: 'right',
97019
97020     constructor : function(config){
97021         this.callParent(arguments);
97022         if (this.rowspan) {
97023             this.renderer = Ext.Function.bind(this.renderer, this);
97024         }
97025     },
97026
97027     // private
97028     resizable: false,
97029     hideable: false,
97030     menuDisabled: true,
97031     dataIndex: '',
97032     cls: Ext.baseCSSPrefix + 'row-numberer',
97033     rowspan: undefined,
97034
97035     // private
97036     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
97037         if (this.rowspan){
97038             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
97039         }
97040
97041         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
97042         return store.indexOfTotal(record) + 1;
97043     }
97044 });
97045
97046 /**
97047  * @class Ext.view.DropZone
97048  * @extends Ext.dd.DropZone
97049  * @private
97050  */
97051 Ext.define('Ext.view.DropZone', {
97052     extend: 'Ext.dd.DropZone',
97053
97054     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
97055     indicatorCls: 'x-grid-drop-indicator',
97056
97057     constructor: function(config) {
97058         var me = this;
97059         Ext.apply(me, config);
97060
97061         // Create a ddGroup unless one has been configured.
97062         // User configuration of ddGroups allows users to specify which
97063         // DD instances can interact with each other. Using one
97064         // based on the id of the View would isolate it and mean it can only
97065         // interact with a DragZone on the same View also using a generated ID.
97066         if (!me.ddGroup) {
97067             me.ddGroup = 'view-dd-zone-' + me.view.id;
97068         }
97069
97070         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
97071         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
97072         // same element, so a DragZone on this same View must use the View's parent element as its element.
97073         me.callParent([me.view.el]);
97074     },
97075
97076 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
97077 //  its data does not become corrupted by processing mouse events.
97078     fireViewEvent: function() {
97079         var me = this,
97080             result;
97081
97082         me.lock();
97083         result = me.view.fireEvent.apply(me.view, arguments);
97084         me.unlock();
97085         return result;
97086     },
97087
97088     getTargetFromEvent : function(e) {
97089         var node = e.getTarget(this.view.getItemSelector()),
97090             mouseY, nodeList, testNode, i, len, box;
97091
97092 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
97093 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
97094         if (!node) {
97095             mouseY = e.getPageY();
97096             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
97097                 testNode = nodeList[i];
97098                 box = Ext.fly(testNode).getBox();
97099                 if (mouseY <= box.bottom) {
97100                     return testNode;
97101                 }
97102             }
97103         }
97104         return node;
97105     },
97106
97107     getIndicator: function() {
97108         var me = this;
97109
97110         if (!me.indicator) {
97111             me.indicator = Ext.createWidget('component', {
97112                 html: me.indicatorHtml,
97113                 cls: me.indicatorCls,
97114                 ownerCt: me.view,
97115                 floating: true,
97116                 shadow: false
97117             });
97118         }
97119         return me.indicator;
97120     },
97121
97122     getPosition: function(e, node) {
97123         var y      = e.getXY()[1],
97124             region = Ext.fly(node).getRegion(),
97125             pos;
97126
97127         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
97128             pos = "before";
97129         } else {
97130             pos = "after";
97131         }
97132         return pos;
97133     },
97134
97135     /**
97136      * @private Determines whether the record at the specified offset from the passed record
97137      * is in the drag payload.
97138      * @param records
97139      * @param record
97140      * @param offset
97141      * @returns {Boolean} True if the targeted record is in the drag payload
97142      */
97143     containsRecordAtOffset: function(records, record, offset) {
97144         if (!record) {
97145             return false;
97146         }
97147         var view = this.view,
97148             recordIndex = view.indexOf(record),
97149             nodeBefore = view.getNode(recordIndex + offset),
97150             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
97151
97152         return recordBefore && Ext.Array.contains(records, recordBefore);
97153     },
97154
97155     positionIndicator: function(node, data, e) {
97156         var me = this,
97157             view = me.view,
97158             pos = me.getPosition(e, node),
97159             overRecord = view.getRecord(node),
97160             draggingRecords = data.records,
97161             indicator, indicatorY;
97162
97163         if (!Ext.Array.contains(draggingRecords, overRecord) && (
97164             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
97165             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
97166         )) {
97167             me.valid = true;
97168
97169             if (me.overRecord != overRecord || me.currentPosition != pos) {
97170
97171                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
97172                 if (pos == 'after') {
97173                     indicatorY += Ext.fly(node).getHeight();
97174                 }
97175                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
97176
97177                 // Cache the overRecord and the 'before' or 'after' indicator.
97178                 me.overRecord = overRecord;
97179                 me.currentPosition = pos;
97180             }
97181         } else {
97182             me.invalidateDrop();
97183         }
97184     },
97185
97186     invalidateDrop: function() {
97187         if (this.valid) {
97188             this.valid = false;
97189             this.getIndicator().hide();
97190         }
97191     },
97192
97193     // The mouse is over a View node
97194     onNodeOver: function(node, dragZone, e, data) {
97195         var me = this;
97196
97197         if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
97198             me.positionIndicator(node, data, e);
97199         }
97200         return me.valid ? me.dropAllowed : me.dropNotAllowed;
97201     },
97202
97203     // Moved out of the DropZone without dropping.
97204     // Remove drop position indicator
97205     notifyOut: function(node, dragZone, e, data) {
97206         var me = this;
97207
97208         me.callParent(arguments);
97209         delete me.overRecord;
97210         delete me.currentPosition;
97211         if (me.indicator) {
97212             me.indicator.hide();
97213         }
97214     },
97215
97216     // The mouse is past the end of all nodes (or there are no nodes)
97217     onContainerOver : function(dd, e, data) {
97218         var me = this,
97219             view = me.view,
97220             count = view.store.getCount();
97221
97222         // There are records, so position after the last one
97223         if (count) {
97224             me.positionIndicator(view.getNode(count - 1), data, e);
97225         }
97226
97227         // No records, position the indicator at the top
97228         else {
97229             delete me.overRecord;
97230             delete me.currentPosition;
97231             me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
97232             me.valid = true;
97233         }
97234         return me.dropAllowed;
97235     },
97236
97237     onContainerDrop : function(dd, e, data) {
97238         return this.onNodeDrop(dd, null, e, data);
97239     },
97240
97241     onNodeDrop: function(node, dragZone, e, data) {
97242         var me = this,
97243             dropped = false,
97244
97245             // Create a closure to perform the operation which the event handler may use.
97246             // Users may now return <code>false</code> from the beforedrop handler, and perform any kind
97247             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
97248             // and complete the drop gesture at some point in the future by calling this function.
97249             processDrop = function () {
97250                 me.invalidateDrop();
97251                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
97252                 dropped = true;
97253                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
97254             },
97255             performOperation = false;
97256
97257         if (me.valid) {
97258             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
97259             if (performOperation !== false) {
97260                 // If the processDrop function was called in the event handler, do not do it again.
97261                 if (!dropped) {
97262                     processDrop();
97263                 }
97264             }
97265         }
97266         return performOperation;
97267     },
97268     
97269     destroy: function(){
97270         Ext.destroy(this.indicator);
97271         delete this.indicator;
97272         this.callParent();
97273     }
97274 });
97275
97276 Ext.define('Ext.grid.ViewDropZone', {
97277     extend: 'Ext.view.DropZone',
97278
97279     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
97280     indicatorCls: 'x-grid-drop-indicator',
97281
97282     handleNodeDrop : function(data, record, position) {
97283         var view = this.view,
97284             store = view.getStore(),
97285             index, records, i, len;
97286
97287         // If the copy flag is set, create a copy of the Models with the same IDs
97288         if (data.copy) {
97289             records = data.records;
97290             data.records = [];
97291             for (i = 0, len = records.length; i < len; i++) {
97292                 data.records.push(records[i].copy(records[i].getId()));
97293             }
97294         } else {
97295             /*
97296              * Remove from the source store. We do this regardless of whether the store
97297              * is the same bacsue the store currently doesn't handle moving records
97298              * within the store. In the future it should be possible to do this.
97299              * Here was pass the isMove parameter if we're moving to the same view.
97300              */
97301             data.view.store.remove(data.records, data.view === view);
97302         }
97303
97304         index = store.indexOf(record);
97305
97306         // 'after', or undefined (meaning a drop at index -1 on an empty View)...
97307         if (position !== 'before') {
97308             index++;
97309         }
97310         store.insert(index, data.records);
97311         view.getSelectionModel().select(data.records);
97312     }
97313 });
97314 /**
97315  * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
97316  * handler for each icon.
97317  *
97318  *     @example
97319  *     Ext.create('Ext.data.Store', {
97320  *         storeId:'employeeStore',
97321  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
97322  *         data:[
97323  *             {firstname:"Michael", lastname:"Scott"},
97324  *             {firstname:"Dwight", lastname:"Schrute"},
97325  *             {firstname:"Jim", lastname:"Halpert"},
97326  *             {firstname:"Kevin", lastname:"Malone"},
97327  *             {firstname:"Angela", lastname:"Martin"}
97328  *         ]
97329  *     });
97330  *
97331  *     Ext.create('Ext.grid.Panel', {
97332  *         title: 'Action Column Demo',
97333  *         store: Ext.data.StoreManager.lookup('employeeStore'),
97334  *         columns: [
97335  *             {text: 'First Name',  dataIndex:'firstname'},
97336  *             {text: 'Last Name',  dataIndex:'lastname'},
97337  *             {
97338  *                 xtype:'actioncolumn',
97339  *                 width:50,
97340  *                 items: [{
97341  *                     icon: 'extjs/examples/shared/icons/fam/cog_edit.png',  // Use a URL in the icon config
97342  *                     tooltip: 'Edit',
97343  *                     handler: function(grid, rowIndex, colIndex) {
97344  *                         var rec = grid.getStore().getAt(rowIndex);
97345  *                         alert("Edit " + rec.get('firstname'));
97346  *                     }
97347  *                 },{
97348  *                     icon: 'extjs/examples/restful/images/delete.png',
97349  *                     tooltip: 'Delete',
97350  *                     handler: function(grid, rowIndex, colIndex) {
97351  *                         var rec = grid.getStore().getAt(rowIndex);
97352  *                         alert("Terminate " + rec.get('firstname'));
97353  *                     }
97354  *                 }]
97355  *             }
97356  *         ],
97357  *         width: 250,
97358  *         renderTo: Ext.getBody()
97359  *     });
97360  *
97361  * The action column can be at any index in the columns array, and a grid can have any number of
97362  * action columns.
97363  */
97364 Ext.define('Ext.grid.column.Action', {
97365     extend: 'Ext.grid.column.Column',
97366     alias: ['widget.actioncolumn'],
97367     alternateClassName: 'Ext.grid.ActionColumn',
97368
97369     /**
97370      * @cfg {String} icon
97371      * The URL of an image to display as the clickable element in the column. Defaults to
97372      * `{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}`.
97373      */
97374     /**
97375      * @cfg {String} iconCls
97376      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
97377      * a `{@link #getClass}` function.
97378      */
97379     /**
97380      * @cfg {Function} handler
97381      * A function called when the icon is clicked.
97382      * @cfg {Ext.view.Table} handler.view The owning TableView.
97383      * @cfg {Number} handler.rowIndex The row index clicked on.
97384      * @cfg {Number} handler.colIndex The column index clicked on.
97385      * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #items} were not configured).
97386      * @cfg {Event} handler.e The click event.
97387      */
97388     /**
97389      * @cfg {Object} scope
97390      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
97391      * Defaults to this Column.
97392      */
97393     /**
97394      * @cfg {String} tooltip
97395      * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
97396      * have been initialized.
97397      */
97398     /* @cfg {Boolean} disabled
97399      * If true, the action will not respond to click events, and will be displayed semi-opaque.
97400      */
97401     /**
97402      * @cfg {Boolean} [stopSelection=true]
97403      * Prevent grid _row_ selection upon mousedown.
97404      */
97405     /**
97406      * @cfg {Function} getClass
97407      * A function which returns the CSS class to apply to the icon image.
97408      *
97409      * @cfg {Object} getClass.v The value of the column's configured field (if any).
97410      *
97411      * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
97412      * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
97413      * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
97414      * element *within* the table cell (e.g. 'style="color:red;"').
97415      *
97416      * @cfg {Ext.data.Model} getClass.r The Record providing the data.
97417      *
97418      * @cfg {Number} getClass.rowIndex The row index..
97419      *
97420      * @cfg {Number} getClass.colIndex The column index.
97421      *
97422      * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
97423      */
97424     /**
97425      * @cfg {Object[]} items
97426      * An Array which may contain multiple icon definitions, each element of which may contain:
97427      *
97428      * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
97429      *
97430      * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
97431      * configure the item with a `getClass` function.
97432      *
97433      * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
97434      * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
97435      * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
97436      * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
97437      * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
97438      * container element _within_ the table cell (e.g. 'style="color:red;"').
97439      * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
97440      * @cfg {Number} items.getClass.rowIndex The row index..
97441      * @cfg {Number} items.getClass.colIndex The column index.
97442      * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
97443      *
97444      * @cfg {Function} items.handler A function called when the icon is clicked.
97445      *
97446      * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
97447      * are executed. Fallback defaults are this Column's configured scope, then this Column.
97448      *
97449      * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
97450      * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
97451      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
97452      */
97453     /**
97454      * @property {Array} items
97455      * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
97456      * an `enable` and `disable` method added which will enable and disable the associated action, and
97457      * update the displayed icon accordingly.
97458      */
97459     header: '&#160;',
97460
97461     actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
97462
97463     /**
97464      * @cfg {String} altText
97465      * The alt text to use for the image element.
97466      */
97467     altText: '',
97468
97469     sortable: false,
97470
97471     constructor: function(config) {
97472         var me = this,
97473             cfg = Ext.apply({}, config),
97474             items = cfg.items || [me],
97475             l = items.length,
97476             i,
97477             item;
97478
97479         // This is a Container. Delete the items config to be reinstated after construction.
97480         delete cfg.items;
97481         me.callParent([cfg]);
97482
97483         // Items is an array property of ActionColumns
97484         me.items = items;
97485
97486 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
97487 //      class name x-action-col-{n}
97488         me.renderer = function(v, meta) {
97489 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
97490             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
97491
97492             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
97493             for (i = 0; i < l; i++) {
97494                 item = items[i];
97495                 item.disable = Ext.Function.bind(me.disableAction, me, [i]);
97496                 item.enable = Ext.Function.bind(me.enableAction, me, [i]);
97497                 v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
97498                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
97499                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
97500                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
97501             }
97502             return v;
97503         };
97504     },
97505
97506     /**
97507      * Enables this ActionColumn's action at the specified index.
97508      */
97509     enableAction: function(index) {
97510         var me = this;
97511
97512         if (!index) {
97513             index = 0;
97514         } else if (!Ext.isNumber(index)) {
97515             index = Ext.Array.indexOf(me.items, index);
97516         }
97517         me.items[index].disabled = false;
97518         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
97519     },
97520
97521     /**
97522      * Disables this ActionColumn's action at the specified index.
97523      */
97524     disableAction: function(index) {
97525         var me = this;
97526
97527         if (!index) {
97528             index = 0;
97529         } else if (!Ext.isNumber(index)) {
97530             index = Ext.Array.indexOf(me.items, index);
97531         }
97532         me.items[index].disabled = true;
97533         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
97534     },
97535
97536     destroy: function() {
97537         delete this.items;
97538         delete this.renderer;
97539         return this.callParent(arguments);
97540     },
97541
97542     /**
97543      * @private
97544      * Process and refire events routed from the GridView's processEvent method.
97545      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
97546      * Returns the event handler's status to allow canceling of GridView's bubbling process.
97547      */
97548     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
97549         var me = this,
97550             match = e.getTarget().className.match(me.actionIdRe),
97551             item, fn;
97552             
97553         if (match) {
97554             item = me.items[parseInt(match[1], 10)];
97555             if (item) {
97556                 if (type == 'click') {
97557                     fn = item.handler || me.handler;
97558                     if (fn && !item.disabled) {
97559                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
97560                     }
97561                 } else if (type == 'mousedown' && item.stopSelection !== false) {
97562                     return false;
97563                 }
97564             }
97565         }
97566         return me.callParent(arguments);
97567     },
97568
97569     cascade: function(fn, scope) {
97570         fn.call(scope||this, this);
97571     },
97572
97573     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
97574     getRefItems: function() {
97575         return [];
97576     }
97577 });
97578 /**
97579  * A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
97580  * config option of {@link Ext.grid.column.Column} for more details.
97581  *
97582  *     @example
97583  *     Ext.create('Ext.data.Store', {
97584  *        storeId:'sampleStore',
97585  *        fields:[
97586  *            {name: 'framework', type: 'string'},
97587  *            {name: 'rocks', type: 'boolean'}
97588  *        ],
97589  *        data:{'items':[
97590  *            { 'framework': "Ext JS 4",     'rocks': true  },
97591  *            { 'framework': "Sencha Touch", 'rocks': true  },
97592  *            { 'framework': "Ext GWT",      'rocks': true  }, 
97593  *            { 'framework': "Other Guys",   'rocks': false } 
97594  *        ]},
97595  *        proxy: {
97596  *            type: 'memory',
97597  *            reader: {
97598  *                type: 'json',
97599  *                root: 'items'
97600  *            }
97601  *        }
97602  *     });
97603  *     
97604  *     Ext.create('Ext.grid.Panel', {
97605  *         title: 'Boolean Column Demo',
97606  *         store: Ext.data.StoreManager.lookup('sampleStore'),
97607  *         columns: [
97608  *             { text: 'Framework',  dataIndex: 'framework', flex: 1 },
97609  *             {
97610  *                 xtype: 'booleancolumn', 
97611  *                 text: 'Rocks',
97612  *                 trueText: 'Yes',
97613  *                 falseText: 'No', 
97614  *                 dataIndex: 'rocks'
97615  *             }
97616  *         ],
97617  *         height: 200,
97618  *         width: 400,
97619  *         renderTo: Ext.getBody()
97620  *     });
97621  */
97622 Ext.define('Ext.grid.column.Boolean', {
97623     extend: 'Ext.grid.column.Column',
97624     alias: ['widget.booleancolumn'],
97625     alternateClassName: 'Ext.grid.BooleanColumn',
97626
97627     /**
97628      * @cfg {String} trueText
97629      * The string returned by the renderer when the column value is not falsey.
97630      */
97631     trueText: 'true',
97632
97633     /**
97634      * @cfg {String} falseText
97635      * The string returned by the renderer when the column value is falsey (but not undefined).
97636      */
97637     falseText: 'false',
97638
97639     /**
97640      * @cfg {String} undefinedText
97641      * The string returned by the renderer when the column value is undefined.
97642      */
97643     undefinedText: '&#160;',
97644
97645     constructor: function(cfg){
97646         this.callParent(arguments);
97647         var trueText      = this.trueText,
97648             falseText     = this.falseText,
97649             undefinedText = this.undefinedText;
97650
97651         this.renderer = function(value){
97652             if(value === undefined){
97653                 return undefinedText;
97654             }
97655             if(!value || value === 'false'){
97656                 return falseText;
97657             }
97658             return trueText;
97659         };
97660     }
97661 });
97662 /**
97663  * A Column definition class which renders a passed date according to the default locale, or a configured
97664  * {@link #format}.
97665  *
97666  *     @example
97667  *     Ext.create('Ext.data.Store', {
97668  *         storeId:'sampleStore',
97669  *         fields:[
97670  *             { name: 'symbol', type: 'string' },
97671  *             { name: 'date',   type: 'date' },
97672  *             { name: 'change', type: 'number' },
97673  *             { name: 'volume', type: 'number' },
97674  *             { name: 'topday', type: 'date' }                        
97675  *         ],
97676  *         data:[
97677  *             { symbol: "msft",   date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
97678  *             { symbol: "goog",   date: '2011/04/22', change: 0.81, volume: 3053782,  topday: '04/11/2010' },
97679  *             { symbol: "apple",  date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },            
97680  *             { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351,  topday: '04/22/2010' }            
97681  *         ]
97682  *     });
97683  *     
97684  *     Ext.create('Ext.grid.Panel', {
97685  *         title: 'Date Column Demo',
97686  *         store: Ext.data.StoreManager.lookup('sampleStore'),
97687  *         columns: [
97688  *             { text: 'Symbol',   dataIndex: 'symbol', flex: 1 },
97689  *             { text: 'Date',     dataIndex: 'date',   xtype: 'datecolumn',   format:'Y-m-d' },
97690  *             { text: 'Change',   dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
97691  *             { text: 'Volume',   dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
97692  *             { text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn',   format:'l' }            
97693  *         ],
97694  *         height: 200,
97695  *         width: 450,
97696  *         renderTo: Ext.getBody()
97697  *     });
97698  */
97699 Ext.define('Ext.grid.column.Date', {
97700     extend: 'Ext.grid.column.Column',
97701     alias: ['widget.datecolumn'],
97702     requires: ['Ext.Date'],
97703     alternateClassName: 'Ext.grid.DateColumn',
97704
97705     /**
97706      * @cfg {String} format
97707      * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
97708      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
97709      * in a locale file.
97710      */
97711
97712     initComponent: function(){
97713         var me = this;
97714         
97715         me.callParent(arguments);
97716         if (!me.format) {
97717             me.format = Ext.Date.defaultFormat;
97718         }
97719         me.renderer = Ext.util.Format.dateRenderer(me.format);
97720     }
97721 });
97722 /**
97723  * A Column definition class which renders a numeric data field according to a {@link #format} string.
97724  *
97725  *     @example
97726  *     Ext.create('Ext.data.Store', {
97727  *        storeId:'sampleStore',
97728  *        fields:[
97729  *            { name: 'symbol', type: 'string' },
97730  *            { name: 'price',  type: 'number' },
97731  *            { name: 'change', type: 'number' },
97732  *            { name: 'volume', type: 'number' },            
97733  *        ],
97734  *        data:[
97735  *            { symbol: "msft",   price: 25.76,  change: 2.43, volume: 61606325 },
97736  *            { symbol: "goog",   price: 525.73, change: 0.81, volume: 3053782  },
97737  *            { symbol: "apple",  price: 342.41, change: 1.35, volume: 24484858 },            
97738  *            { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351  }            
97739  *        ]
97740  *     });
97741  *     
97742  *     Ext.create('Ext.grid.Panel', {
97743  *         title: 'Number Column Demo',
97744  *         store: Ext.data.StoreManager.lookup('sampleStore'),
97745  *         columns: [
97746  *             { text: 'Symbol',         dataIndex: 'symbol', flex: 1 },
97747  *             { text: 'Current Price',  dataIndex: 'price',  renderer: Ext.util.Format.usMoney },
97748  *             { text: 'Change',         dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
97749  *             { text: 'Volume',         dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
97750  *         ],
97751  *         height: 200,
97752  *         width: 400,
97753  *         renderTo: Ext.getBody()
97754  *     });
97755  */
97756 Ext.define('Ext.grid.column.Number', {
97757     extend: 'Ext.grid.column.Column',
97758     alias: ['widget.numbercolumn'],
97759     requires: ['Ext.util.Format'],
97760     alternateClassName: 'Ext.grid.NumberColumn',
97761
97762     /**
97763      * @cfg {String} format
97764      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
97765      */
97766     format : '0,000.00',
97767
97768     constructor: function(cfg) {
97769         this.callParent(arguments);
97770         this.renderer = Ext.util.Format.numberRenderer(this.format);
97771     }
97772 });
97773 /**
97774  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
97775  * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
97776  * {@link Ext.XTemplate XTemplate}.
97777  * 
97778  *     @example
97779  *     Ext.create('Ext.data.Store', {
97780  *         storeId:'employeeStore',
97781  *         fields:['firstname', 'lastname', 'senority', 'department'],
97782  *         groupField: 'department',
97783  *         data:[
97784  *             { firstname: "Michael", lastname: "Scott",   senority: 7, department: "Manangement" },
97785  *             { firstname: "Dwight",  lastname: "Schrute", senority: 2, department: "Sales" },
97786  *             { firstname: "Jim",     lastname: "Halpert", senority: 3, department: "Sales" },
97787  *             { firstname: "Kevin",   lastname: "Malone",  senority: 4, department: "Accounting" },
97788  *             { firstname: "Angela",  lastname: "Martin",  senority: 5, department: "Accounting" }                        
97789  *         ]
97790  *     });
97791  *     
97792  *     Ext.create('Ext.grid.Panel', {
97793  *         title: 'Column Template Demo',
97794  *         store: Ext.data.StoreManager.lookup('employeeStore'),
97795  *         columns: [
97796  *             { text: 'Full Name',       xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
97797  *             { text: 'Deparment (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({senority})' }
97798  *         ],
97799  *         height: 200,
97800  *         width: 300,
97801  *         renderTo: Ext.getBody()
97802  *     });
97803  */
97804 Ext.define('Ext.grid.column.Template', {
97805     extend: 'Ext.grid.column.Column',
97806     alias: ['widget.templatecolumn'],
97807     requires: ['Ext.XTemplate'],
97808     alternateClassName: 'Ext.grid.TemplateColumn',
97809
97810     /**
97811      * @cfg {String/Ext.XTemplate} tpl
97812      * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
97813      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
97814      * column's rendered value.
97815      */
97816
97817     constructor: function(cfg){
97818         var me = this,
97819             tpl;
97820             
97821         me.callParent(arguments);
97822         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
97823
97824         me.renderer = function(value, p, record) {
97825             var data = Ext.apply({}, record.data, record.getAssociatedData());
97826             return tpl.apply(data);
97827         };
97828     }
97829 });
97830
97831 /**
97832  * @class Ext.grid.feature.Feature
97833  * @extends Ext.util.Observable
97834  * 
97835  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
97836  * hooks that allows the developer to inject additional functionality at certain points throughout the 
97837  * grid creation cycle. This class provides the base template methods that are available to the developer,
97838  * it should be extended.
97839  * 
97840  * There are several built in features that extend this class, for example:
97841  *
97842  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
97843  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
97844  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
97845  * 
97846  * ## Using Features
97847  * A feature is added to the grid by specifying it an array of features in the configuration:
97848  * 
97849  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
97850  *     Ext.create('Ext.grid.Panel', {
97851  *         // other options
97852  *         features: [groupingFeature]
97853  *     });
97854  * 
97855  * @abstract
97856  */
97857 Ext.define('Ext.grid.feature.Feature', {
97858     extend: 'Ext.util.Observable',
97859     alias: 'feature.feature',
97860     
97861     isFeature: true,
97862     disabled: false,
97863     
97864     /**
97865      * @property {Boolean}
97866      * Most features will expose additional events, some may not and will
97867      * need to change this to false.
97868      */
97869     hasFeatureEvent: true,
97870     
97871     /**
97872      * @property {String}
97873      * Prefix to use when firing events on the view.
97874      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
97875      */
97876     eventPrefix: null,
97877     
97878     /**
97879      * @property {String}
97880      * Selector used to determine when to fire the event with the eventPrefix.
97881      */
97882     eventSelector: null,
97883     
97884     /**
97885      * @property {Ext.view.Table}
97886      * Reference to the TableView.
97887      */
97888     view: null,
97889     
97890     /**
97891      * @property {Ext.grid.Panel}
97892      * Reference to the grid panel
97893      */
97894     grid: null,
97895     
97896     /**
97897      * Most features will not modify the data returned to the view.
97898      * This is limited to one feature that manipulates the data per grid view.
97899      */
97900     collectData: false,
97901         
97902     getFeatureTpl: function() {
97903         return '';
97904     },
97905     
97906     /**
97907      * Abstract method to be overriden when a feature should add additional
97908      * arguments to its event signature. By default the event will fire:
97909      * - view - The underlying Ext.view.Table
97910      * - featureTarget - The matched element by the defined {@link #eventSelector}
97911      *
97912      * The method must also return the eventName as the first index of the array
97913      * to be passed to fireEvent.
97914      * @template
97915      */
97916     getFireEventArgs: function(eventName, view, featureTarget, e) {
97917         return [eventName, view, featureTarget, e];
97918     },
97919     
97920     /**
97921      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
97922      * @template
97923      */
97924     attachEvents: function() {
97925         
97926     },
97927     
97928     getFragmentTpl: function() {
97929         return;
97930     },
97931     
97932     /**
97933      * Allows a feature to mutate the metaRowTpl.
97934      * The array received as a single argument can be manipulated to add things
97935      * on the end/begining of a particular row.
97936      * @template
97937      */
97938     mutateMetaRowTpl: function(metaRowTplArray) {
97939         
97940     },
97941     
97942     /**
97943      * Allows a feature to inject member methods into the metaRowTpl. This is
97944      * important for embedding functionality which will become part of the proper
97945      * row tpl.
97946      * @template
97947      */
97948     getMetaRowTplFragments: function() {
97949         return {};
97950     },
97951
97952     getTableFragments: function() {
97953         return {};
97954     },
97955     
97956     /**
97957      * Provide additional data to the prepareData call within the grid view.
97958      * @param {Object} data The data for this particular record.
97959      * @param {Number} idx The row index for this record.
97960      * @param {Ext.data.Model} record The record instance
97961      * @param {Object} orig The original result from the prepareData call to massage.
97962      * @template
97963      */
97964     getAdditionalData: function(data, idx, record, orig) {
97965         return {};
97966     },
97967     
97968     /**
97969      * Enable a feature
97970      */
97971     enable: function() {
97972         this.disabled = false;
97973     },
97974     
97975     /**
97976      * Disable a feature
97977      */
97978     disable: function() {
97979         this.disabled = true;
97980     }
97981     
97982 });
97983 /**
97984  * @class Ext.grid.feature.AbstractSummary
97985  * @extends Ext.grid.feature.Feature
97986  * A small abstract class that contains the shared behaviour for any summary
97987  * calculations to be used in the grid.
97988  */
97989 Ext.define('Ext.grid.feature.AbstractSummary', {
97990     
97991     /* Begin Definitions */
97992    
97993     extend: 'Ext.grid.feature.Feature',
97994     
97995     alias: 'feature.abstractsummary',
97996    
97997     /* End Definitions */
97998    
97999    /**
98000     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
98001     */
98002     showSummaryRow: true,
98003     
98004     // @private
98005     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
98006     
98007     /**
98008      * Toggle whether or not to show the summary row.
98009      * @param {Boolean} visible True to show the summary row
98010      */
98011     toggleSummaryRow: function(visible){
98012         this.showSummaryRow = !!visible;
98013     },
98014     
98015     /**
98016      * Gets any fragments to be used in the tpl
98017      * @private
98018      * @return {Object} The fragments
98019      */
98020     getSummaryFragments: function(){
98021         var fragments = {};
98022         if (this.showSummaryRow) {
98023             Ext.apply(fragments, {
98024                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
98025             });
98026         }
98027         return fragments;
98028     },
98029     
98030     /**
98031      * Prints a summary row
98032      * @private
98033      * @param {Object} index The index in the template
98034      * @return {String} The value of the summary row
98035      */
98036     printSummaryRow: function(index){
98037         var inner = this.view.getTableChunker().metaRowTpl.join(''),
98038             prefix = Ext.baseCSSPrefix;
98039         
98040         inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
98041         inner = inner.replace('{{id}}', '{gridSummaryValue}');
98042         inner = inner.replace(this.nestedIdRe, '{id$1}');  
98043         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
98044         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
98045         inner = Ext.create('Ext.XTemplate', inner, {
98046             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
98047         });
98048         
98049         return inner.applyTemplate({
98050             columns: this.getPrintData(index)
98051         });
98052     },
98053     
98054     /**
98055      * Gets the value for the column from the attached data.
98056      * @param {Ext.grid.column.Column} column The header
98057      * @param {Object} data The current data
98058      * @return {String} The value to be rendered
98059      */
98060     getColumnValue: function(column, summaryData){
98061         var comp     = Ext.getCmp(column.id),
98062             value    = summaryData[column.id],
98063             renderer = comp.summaryRenderer;
98064
98065         if (renderer) {
98066             value = renderer.call(
98067                 comp.scope || this,
98068                 value,
98069                 summaryData,
98070                 column.dataIndex
98071             );
98072         }
98073         return value;
98074     },
98075     
98076     /**
98077      * Get the summary data for a field.
98078      * @private
98079      * @param {Ext.data.Store} store The store to get the data from
98080      * @param {String/Function} type The type of aggregation. If a function is specified it will
98081      * be passed to the stores aggregate function.
98082      * @param {String} field The field to aggregate on
98083      * @param {Boolean} group True to aggregate in grouped mode 
98084      * @return {Number/String/Object} See the return type for the store functions.
98085      */
98086     getSummary: function(store, type, field, group){
98087         if (type) {
98088             if (Ext.isFunction(type)) {
98089                 return store.aggregate(type, null, group);
98090             }
98091             
98092             switch (type) {
98093                 case 'count':
98094                     return store.count(group);
98095                 case 'min':
98096                     return store.min(field, group);
98097                 case 'max':
98098                     return store.max(field, group);
98099                 case 'sum':
98100                     return store.sum(field, group);
98101                 case 'average':
98102                     return store.average(field, group);
98103                 default:
98104                     return group ? {} : '';
98105                     
98106             }
98107         }
98108     }
98109     
98110 });
98111
98112 /**
98113  * @class Ext.grid.feature.Chunking
98114  * @extends Ext.grid.feature.Feature
98115  */
98116 Ext.define('Ext.grid.feature.Chunking', {
98117     extend: 'Ext.grid.feature.Feature',
98118     alias: 'feature.chunking',
98119     
98120     chunkSize: 20,
98121     rowHeight: Ext.isIE ? 27 : 26,
98122     visibleChunk: 0,
98123     hasFeatureEvent: false,
98124     attachEvents: function() {
98125         var grid = this.view.up('gridpanel'),
98126             scroller = grid.down('gridscroller[dock=right]');
98127         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
98128         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
98129     },
98130     
98131     onBodyScroll: function(e, t) {
98132         var view = this.view,
98133             top  = t.scrollTop,
98134             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
98135         if (nextChunk !== this.visibleChunk) {
98136         
98137             this.visibleChunk = nextChunk;
98138             view.refresh();
98139             view.el.dom.scrollTop = top;
98140             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
98141             view.el.dom.scrollTop = top;
98142         }
98143     },
98144     
98145     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
98146         var o = {
98147             fullWidth: orig.fullWidth,
98148             chunks: []
98149         },
98150         //headerCt = this.view.headerCt,
98151         //colums = headerCt.getColumnsForTpl(),
98152         recordCount = orig.rows.length,
98153         start = 0,
98154         i = 0,
98155         visibleChunk = this.visibleChunk,
98156         chunk,
98157         rows,
98158         chunkLength;
98159
98160         for (; start < recordCount; start+=this.chunkSize, i++) {
98161             if (start+this.chunkSize > recordCount) {
98162                 chunkLength = recordCount - start;
98163             } else {
98164                 chunkLength = this.chunkSize;
98165             }
98166             
98167             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
98168                 rows = orig.rows.slice(start, start+this.chunkSize);
98169             } else {
98170                 rows = [];
98171             }
98172             o.chunks.push({
98173                 rows: rows,
98174                 fullWidth: fullWidth,
98175                 chunkHeight: chunkLength * this.rowHeight
98176             });
98177         }
98178         
98179         
98180         return o;
98181     },
98182     
98183     getTableFragments: function() {
98184         return {
98185             openTableWrap: function() {
98186                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
98187             },
98188             closeTableWrap: function() {
98189                 return '</div></tpl>';
98190             }
98191         };
98192     }
98193 });
98194
98195 /**
98196  * @class Ext.grid.feature.Grouping
98197  * @extends Ext.grid.feature.Feature
98198  * 
98199  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
98200  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
98201  * underneath. The groups can also be expanded and collapsed.
98202  * 
98203  * ## Extra Events
98204  * This feature adds several extra events that will be fired on the grid to interact with the groups:
98205  *
98206  *  - {@link #groupclick}
98207  *  - {@link #groupdblclick}
98208  *  - {@link #groupcontextmenu}
98209  *  - {@link #groupexpand}
98210  *  - {@link #groupcollapse}
98211  * 
98212  * ## Menu Augmentation
98213  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
98214  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
98215  * by thew user is {@link #enableNoGroups}.
98216  * 
98217  * ## Controlling Group Text
98218  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
98219  * the default display.
98220  * 
98221  * ## Example Usage
98222  * 
98223  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
98224  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
98225  *         startCollapsed: true // start all groups collapsed
98226  *     });
98227  * 
98228  * @ftype grouping
98229  * @author Nicolas Ferrero
98230  */
98231 Ext.define('Ext.grid.feature.Grouping', {
98232     extend: 'Ext.grid.feature.Feature',
98233     alias: 'feature.grouping',
98234
98235     eventPrefix: 'group',
98236     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
98237
98238     constructor: function() {
98239         var me = this;
98240         
98241         me.collapsedState = {};
98242         me.callParent(arguments);
98243     },
98244     
98245     /**
98246      * @event groupclick
98247      * @param {Ext.view.Table} view
98248      * @param {HTMLElement} node
98249      * @param {String} group The name of the group
98250      * @param {Ext.EventObject} e
98251      */
98252
98253     /**
98254      * @event groupdblclick
98255      * @param {Ext.view.Table} view
98256      * @param {HTMLElement} node
98257      * @param {String} group The name of the group
98258      * @param {Ext.EventObject} e
98259      */
98260
98261     /**
98262      * @event groupcontextmenu
98263      * @param {Ext.view.Table} view
98264      * @param {HTMLElement} node
98265      * @param {String} group The name of the group
98266      * @param {Ext.EventObject} e
98267      */
98268
98269     /**
98270      * @event groupcollapse
98271      * @param {Ext.view.Table} view
98272      * @param {HTMLElement} node
98273      * @param {String} group The name of the group
98274      * @param {Ext.EventObject} e
98275      */
98276
98277     /**
98278      * @event groupexpand
98279      * @param {Ext.view.Table} view
98280      * @param {HTMLElement} node
98281      * @param {String} group The name of the group
98282      * @param {Ext.EventObject} e
98283      */
98284
98285     /**
98286      * @cfg {String} groupHeaderTpl
98287      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
98288      * Defaults to 'Group: {name}'
98289      */
98290     groupHeaderTpl: 'Group: {name}',
98291
98292     /**
98293      * @cfg {Number} depthToIndent
98294      * Number of pixels to indent per grouping level
98295      */
98296     depthToIndent: 17,
98297
98298     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
98299     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
98300
98301     /**
98302      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
98303      */
98304     groupByText : 'Group By This Field',
98305     /**
98306      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
98307      */
98308     showGroupsText : 'Show in Groups',
98309
98310     /**
98311      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
98312      */
98313     hideGroupedHeader : false,
98314
98315     /**
98316      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
98317      */
98318     startCollapsed : false,
98319
98320     /**
98321      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
98322      */
98323     enableGroupingMenu : true,
98324
98325     /**
98326      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
98327      */
98328     enableNoGroups : true,
98329     
98330     enable: function() {
98331         var me    = this,
98332             view  = me.view,
98333             store = view.store,
98334             groupToggleMenuItem;
98335             
98336         me.lastGroupField = me.getGroupField();
98337
98338         if (me.lastGroupIndex) {
98339             store.group(me.lastGroupIndex);
98340         }
98341         me.callParent();
98342         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
98343         groupToggleMenuItem.setChecked(true, true);
98344         me.refreshIf();
98345     },
98346
98347     disable: function() {
98348         var me    = this,
98349             view  = me.view,
98350             store = view.store,
98351             remote = store.remoteGroup,
98352             groupToggleMenuItem,
98353             lastGroup;
98354             
98355         lastGroup = store.groupers.first();
98356         if (lastGroup) {
98357             me.lastGroupIndex = lastGroup.property;
98358             me.block();
98359             store.clearGrouping();
98360             me.unblock();
98361         }
98362         
98363         me.callParent();
98364         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
98365         groupToggleMenuItem.setChecked(true, true);
98366         groupToggleMenuItem.setChecked(false, true);
98367         if (!remote) {
98368             view.refresh();
98369         }
98370     },
98371     
98372     refreshIf: function() {
98373         if (this.blockRefresh !== true) {
98374             this.view.refresh();
98375         }    
98376     },
98377
98378     getFeatureTpl: function(values, parent, x, xcount) {
98379         var me = this;
98380         
98381         return [
98382             '<tpl if="typeof rows !== \'undefined\'">',
98383                 // group row tpl
98384                 '<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>',
98385                 // this is the rowbody
98386                 '<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>',
98387             '</tpl>'
98388         ].join('');
98389     },
98390
98391     getFragmentTpl: function() {
98392         return {
98393             indentByDepth: this.indentByDepth,
98394             depthToIndent: this.depthToIndent
98395         };
98396     },
98397
98398     indentByDepth: function(values) {
98399         var depth = values.depth || 0;
98400         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
98401     },
98402
98403     // Containers holding these components are responsible for
98404     // destroying them, we are just deleting references.
98405     destroy: function() {
98406         var me = this;
98407         
98408         delete me.view;
98409         delete me.prunedHeader;
98410     },
98411
98412     // perhaps rename to afterViewRender
98413     attachEvents: function() {
98414         var me = this,
98415             view = me.view;
98416
98417         view.on({
98418             scope: me,
98419             groupclick: me.onGroupClick,
98420             rowfocus: me.onRowFocus
98421         });
98422         view.store.on('groupchange', me.onGroupChange, me);
98423
98424         me.pruneGroupedHeader();
98425
98426         if (me.enableGroupingMenu) {
98427             me.injectGroupingMenu();
98428         }
98429         me.lastGroupField = me.getGroupField();
98430         me.block();
98431         me.onGroupChange();
98432         me.unblock();
98433     },
98434     
98435     injectGroupingMenu: function() {
98436         var me       = this,
98437             view     = me.view,
98438             headerCt = view.headerCt;
98439         headerCt.showMenuBy = me.showMenuBy;
98440         headerCt.getMenuItems = me.getMenuItems();
98441     },
98442     
98443     showMenuBy: function(t, header) {
98444         var menu = this.getMenu(),
98445             groupMenuItem  = menu.down('#groupMenuItem'),
98446             groupableMth = header.groupable === false ?  'disable' : 'enable';
98447             
98448         groupMenuItem[groupableMth]();
98449         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
98450     },
98451     
98452     getMenuItems: function() {
98453         var me                 = this,
98454             groupByText        = me.groupByText,
98455             disabled           = me.disabled,
98456             showGroupsText     = me.showGroupsText,
98457             enableNoGroups     = me.enableNoGroups,
98458             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
98459             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
98460         
98461         // runs in the scope of headerCt
98462         return function() {
98463             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
98464             o.push('-', {
98465                 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
98466                 itemId: 'groupMenuItem',
98467                 text: groupByText,
98468                 handler: groupMenuItemClick
98469             });
98470             if (enableNoGroups) {
98471                 o.push({
98472                     itemId: 'groupToggleMenuItem',
98473                     text: showGroupsText,
98474                     checked: !disabled,
98475                     checkHandler: groupToggleMenuItemClick
98476                 });
98477             }
98478             return o;
98479         };
98480     },
98481
98482
98483     /**
98484      * Group by the header the user has clicked on.
98485      * @private
98486      */
98487     onGroupMenuItemClick: function(menuItem, e) {
98488         var me = this,
98489             menu = menuItem.parentMenu,
98490             hdr  = menu.activeHeader,
98491             view = me.view,
98492             store = view.store,
98493             remote = store.remoteGroup;
98494
98495         delete me.lastGroupIndex;
98496         me.block();
98497         me.enable();
98498         store.group(hdr.dataIndex);
98499         me.pruneGroupedHeader();
98500         me.unblock();
98501         if (!remote) {
98502             view.refresh();
98503         }  
98504     },
98505     
98506     block: function(){
98507         this.blockRefresh = this.view.blockRefresh = true;
98508     },
98509     
98510     unblock: function(){
98511         this.blockRefresh = this.view.blockRefresh = false;
98512     },
98513
98514     /**
98515      * Turn on and off grouping via the menu
98516      * @private
98517      */
98518     onGroupToggleMenuItemClick: function(menuItem, checked) {
98519         this[checked ? 'enable' : 'disable']();
98520     },
98521
98522     /**
98523      * Prunes the grouped header from the header container
98524      * @private
98525      */
98526     pruneGroupedHeader: function() {
98527         var me         = this,
98528             view       = me.view,
98529             store      = view.store,
98530             groupField = me.getGroupField(),
98531             headerCt   = view.headerCt,
98532             header     = headerCt.down('header[dataIndex=' + groupField + ']');
98533
98534         if (header) {
98535             if (me.prunedHeader) {
98536                 me.prunedHeader.show();
98537             }
98538             me.prunedHeader = header;
98539             header.hide();
98540         }
98541     },
98542
98543     getGroupField: function(){
98544         var group = this.view.store.groupers.first();
98545         if (group) {
98546             return group.property;    
98547         }
98548         return ''; 
98549     },
98550
98551     /**
98552      * When a row gains focus, expand the groups above it
98553      * @private
98554      */
98555     onRowFocus: function(rowIdx) {
98556         var node    = this.view.getNode(rowIdx),
98557             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
98558
98559         if (groupBd) {
98560             // for multiple level groups, should expand every groupBd
98561             // above
98562             this.expand(groupBd);
98563         }
98564     },
98565
98566     /**
98567      * Expand a group by the groupBody
98568      * @param {Ext.Element} groupBd
98569      * @private
98570      */
98571     expand: function(groupBd) {
98572         var me = this,
98573             view = me.view,
98574             grid = view.up('gridpanel'),
98575             groupBdDom = Ext.getDom(groupBd);
98576             
98577         me.collapsedState[groupBdDom.id] = false;
98578
98579         groupBd.removeCls(me.collapsedCls);
98580         groupBd.prev().removeCls(me.hdCollapsedCls);
98581
98582         grid.determineScrollbars();
98583         grid.invalidateScroller();
98584         view.fireEvent('groupexpand');
98585     },
98586
98587     /**
98588      * Collapse a group by the groupBody
98589      * @param {Ext.Element} groupBd
98590      * @private
98591      */
98592     collapse: function(groupBd) {
98593         var me = this,
98594             view = me.view,
98595             grid = view.up('gridpanel'),
98596             groupBdDom = Ext.getDom(groupBd);
98597             
98598         me.collapsedState[groupBdDom.id] = true;
98599
98600         groupBd.addCls(me.collapsedCls);
98601         groupBd.prev().addCls(me.hdCollapsedCls);
98602
98603         grid.determineScrollbars();
98604         grid.invalidateScroller();
98605         view.fireEvent('groupcollapse');
98606     },
98607     
98608     onGroupChange: function(){
98609         var me = this,
98610             field = me.getGroupField(),
98611             menuItem;
98612             
98613         if (me.hideGroupedHeader) {
98614             if (me.lastGroupField) {
98615                 menuItem = me.getMenuItem(me.lastGroupField);
98616                 if (menuItem) {
98617                     menuItem.setChecked(true);
98618                 }
98619             }
98620             if (field) {
98621                 menuItem = me.getMenuItem(field);
98622                 if (menuItem) {
98623                     menuItem.setChecked(false);
98624                 }
98625             }
98626         }
98627         if (me.blockRefresh !== true) {
98628             me.view.refresh();
98629         }
98630         me.lastGroupField = field;
98631     },
98632     
98633     /**
98634      * Gets the related menu item for a dataIndex
98635      * @private
98636      * @return {Ext.grid.header.Container} The header
98637      */
98638     getMenuItem: function(dataIndex){
98639         var view = this.view,
98640             header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
98641             menu = view.headerCt.getMenu();
98642             
98643         return menu.down('menuitem[headerId='+ header.id +']');
98644     },
98645
98646     /**
98647      * Toggle between expanded/collapsed state when clicking on
98648      * the group.
98649      * @private
98650      */
98651     onGroupClick: function(view, group, idx, foo, e) {
98652         var me = this,
98653             toggleCls = me.toggleCls,
98654             groupBd = Ext.fly(group.nextSibling, '_grouping');
98655
98656         if (groupBd.hasCls(me.collapsedCls)) {
98657             me.expand(groupBd);
98658         } else {
98659             me.collapse(groupBd);
98660         }
98661     },
98662
98663     // Injects isRow and closeRow into the metaRowTpl.
98664     getMetaRowTplFragments: function() {
98665         return {
98666             isRow: this.isRow,
98667             closeRow: this.closeRow
98668         };
98669     },
98670
98671     // injected into rowtpl and wrapped around metaRowTpl
98672     // becomes part of the standard tpl
98673     isRow: function() {
98674         return '<tpl if="typeof rows === \'undefined\'">';
98675     },
98676
98677     // injected into rowtpl and wrapped around metaRowTpl
98678     // becomes part of the standard tpl
98679     closeRow: function() {
98680         return '</tpl>';
98681     },
98682
98683     // isRow and closeRow are injected via getMetaRowTplFragments
98684     mutateMetaRowTpl: function(metaRowTpl) {
98685         metaRowTpl.unshift('{[this.isRow()]}');
98686         metaRowTpl.push('{[this.closeRow()]}');
98687     },
98688
98689     // injects an additional style attribute via tdAttrKey with the proper
98690     // amount of padding
98691     getAdditionalData: function(data, idx, record, orig) {
98692         var view = this.view,
98693             hCt  = view.headerCt,
98694             col  = hCt.items.getAt(0),
98695             o = {},
98696             tdAttrKey = col.id + '-tdAttr';
98697
98698         // maintain the current tdAttr that a user may ahve set.
98699         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
98700         o.collapsed = 'true';
98701         return o;
98702     },
98703
98704     // return matching preppedRecords
98705     getGroupRows: function(group, records, preppedRecords, fullWidth) {
98706         var me = this,
98707             children = group.children,
98708             rows = group.rows = [],
98709             view = me.view;
98710         group.viewId = view.id;
98711
98712         Ext.Array.each(records, function(record, idx) {
98713             if (Ext.Array.indexOf(children, record) != -1) {
98714                 rows.push(Ext.apply(preppedRecords[idx], {
98715                     depth: 1
98716                 }));
98717             }
98718         });
98719         delete group.children;
98720         group.fullWidth = fullWidth;
98721         if (me.collapsedState[view.id + '-gp-' + group.name]) {
98722             group.collapsedCls = me.collapsedCls;
98723             group.hdCollapsedCls = me.hdCollapsedCls;
98724         }
98725
98726         return group;
98727     },
98728
98729     // return the data in a grouped format.
98730     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
98731         var me    = this,
98732             store = me.view.store,
98733             groups;
98734             
98735         if (!me.disabled && store.isGrouped()) {
98736             groups = store.getGroups();
98737             Ext.Array.each(groups, function(group, idx){
98738                 me.getGroupRows(group, records, preppedRecords, fullWidth);
98739             }, me);
98740             return {
98741                 rows: groups,
98742                 fullWidth: fullWidth
98743             };
98744         }
98745         return o;
98746     },
98747     
98748     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
98749     // events that are fired on the view. Chose not to return the actual
98750     // group itself because of its expense and because developers can simply
98751     // grab the group via store.getGroups(groupName)
98752     getFireEventArgs: function(type, view, featureTarget, e) {
98753         var returnArray = [type, view, featureTarget],
98754             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
98755             groupBdId   = Ext.getDom(groupBd).id,
98756             prefix      = view.id + '-gp-',
98757             groupName   = groupBdId.substr(prefix.length);
98758         
98759         returnArray.push(groupName, e);
98760         
98761         return returnArray;
98762     }
98763 });
98764
98765 /**
98766  * @class Ext.grid.feature.GroupingSummary
98767  * @extends Ext.grid.feature.Grouping
98768  *
98769  * This feature adds an aggregate summary row at the bottom of each group that is provided
98770  * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
98771  *
98772  * ## Calculation
98773  *
98774  * The summary value needs to be calculated for each column in the grid. This is controlled
98775  * by the summaryType option specified on the column. There are several built in summary types,
98776  * which can be specified as a string on the column configuration. These call underlying methods
98777  * on the store:
98778  *
98779  *  - {@link Ext.data.Store#count count}
98780  *  - {@link Ext.data.Store#sum sum}
98781  *  - {@link Ext.data.Store#min min}
98782  *  - {@link Ext.data.Store#max max}
98783  *  - {@link Ext.data.Store#average average}
98784  *
98785  * Alternatively, the summaryType can be a function definition. If this is the case,
98786  * the function is called with an array of records to calculate the summary value.
98787  *
98788  * ## Rendering
98789  *
98790  * Similar to a column, the summary also supports a summaryRenderer function. This
98791  * summaryRenderer is called before displaying a value. The function is optional, if
98792  * not specified the default calculated value is shown. The summaryRenderer is called with:
98793  *
98794  *  - value {Object} - The calculated value.
98795  *  - summaryData {Object} - Contains all raw summary values for the row.
98796  *  - field {String} - The name of the field we are calculating
98797  *
98798  * ## Example Usage
98799  *
98800  *     @example
98801  *     Ext.define('TestResult', {
98802  *         extend: 'Ext.data.Model',
98803  *         fields: ['student', 'subject', {
98804  *             name: 'mark',
98805  *             type: 'int'
98806  *         }]
98807  *     });
98808  *
98809  *     Ext.create('Ext.grid.Panel', {
98810  *         width: 200,
98811  *         height: 240,
98812  *         renderTo: document.body,
98813  *         features: [{
98814  *             groupHeaderTpl: 'Subject: {name}',
98815  *             ftype: 'groupingsummary'
98816  *         }],
98817  *         store: {
98818  *             model: 'TestResult',
98819  *             groupField: 'subject',
98820  *             data: [{
98821  *                 student: 'Student 1',
98822  *                 subject: 'Math',
98823  *                 mark: 84
98824  *             },{
98825  *                 student: 'Student 1',
98826  *                 subject: 'Science',
98827  *                 mark: 72
98828  *             },{
98829  *                 student: 'Student 2',
98830  *                 subject: 'Math',
98831  *                 mark: 96
98832  *             },{
98833  *                 student: 'Student 2',
98834  *                 subject: 'Science',
98835  *                 mark: 68
98836  *             }]
98837  *         },
98838  *         columns: [{
98839  *             dataIndex: 'student',
98840  *             text: 'Name',
98841  *             summaryType: 'count',
98842  *             summaryRenderer: function(value){
98843  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
98844  *             }
98845  *         }, {
98846  *             dataIndex: 'mark',
98847  *             text: 'Mark',
98848  *             summaryType: 'average'
98849  *         }]
98850  *     });
98851  */
98852 Ext.define('Ext.grid.feature.GroupingSummary', {
98853
98854     /* Begin Definitions */
98855
98856     extend: 'Ext.grid.feature.Grouping',
98857
98858     alias: 'feature.groupingsummary',
98859
98860     mixins: {
98861         summary: 'Ext.grid.feature.AbstractSummary'
98862     },
98863
98864     /* End Definitions */
98865
98866
98867    /**
98868     * Modifies the row template to include the summary row.
98869     * @private
98870     * @return {String} The modified template
98871     */
98872    getFeatureTpl: function() {
98873         var tpl = this.callParent(arguments);
98874
98875         if (this.showSummaryRow) {
98876             // lop off the end </tpl> so we can attach it
98877             tpl = tpl.replace('</tpl>', '');
98878             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
98879         }
98880         return tpl;
98881     },
98882
98883     /**
98884      * Gets any fragments needed for the template.
98885      * @private
98886      * @return {Object} The fragments
98887      */
98888     getFragmentTpl: function() {
98889         var me = this,
98890             fragments = me.callParent();
98891
98892         Ext.apply(fragments, me.getSummaryFragments());
98893         if (me.showSummaryRow) {
98894             // this gets called before render, so we'll setup the data here.
98895             me.summaryGroups = me.view.store.getGroups();
98896             me.summaryData = me.generateSummaryData();
98897         }
98898         return fragments;
98899     },
98900
98901     /**
98902      * Gets the data for printing a template row
98903      * @private
98904      * @param {Number} index The index in the template
98905      * @return {Array} The template values
98906      */
98907     getPrintData: function(index){
98908         var me = this,
98909             columns = me.view.headerCt.getColumnsForTpl(),
98910             i = 0,
98911             length = columns.length,
98912             data = [],
98913             name = me.summaryGroups[index - 1].name,
98914             active = me.summaryData[name],
98915             column;
98916
98917         for (; i < length; ++i) {
98918             column = columns[i];
98919             column.gridSummaryValue = this.getColumnValue(column, active);
98920             data.push(column);
98921         }
98922         return data;
98923     },
98924
98925     /**
98926      * Generates all of the summary data to be used when processing the template
98927      * @private
98928      * @return {Object} The summary data
98929      */
98930     generateSummaryData: function(){
98931         var me = this,
98932             data = {},
98933             remoteData = {},
98934             store = me.view.store,
98935             groupField = this.getGroupField(),
98936             reader = store.proxy.reader,
98937             groups = me.summaryGroups,
98938             columns = me.view.headerCt.getColumnsForTpl(),
98939             remote,
98940             i,
98941             length,
98942             fieldData,
98943             root,
98944             key,
98945             comp;
98946
98947         for (i = 0, length = groups.length; i < length; ++i) {
98948             data[groups[i].name] = {};
98949         }
98950
98951         /**
98952          * @cfg {String} [remoteRoot=undefined]  The name of the property which contains the Array of
98953          * summary objects. It allows to use server-side calculated summaries.
98954          */
98955         if (me.remoteRoot && reader.rawData) {
98956             // reset reader root and rebuild extractors to extract summaries data
98957             root = reader.root;
98958             reader.root = me.remoteRoot;
98959             reader.buildExtractors(true);
98960             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
98961                  remoteData[value[groupField]] = value;
98962             });
98963             // restore initial reader configuration
98964             reader.root = root;
98965             reader.buildExtractors(true);
98966         }
98967
98968         for (i = 0, length = columns.length; i < length; ++i) {
98969             comp = Ext.getCmp(columns[i].id);
98970             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
98971
98972             for (key in fieldData) {
98973                 if (fieldData.hasOwnProperty(key)) {
98974                     data[key][comp.id] = fieldData[key];
98975                 }
98976             }
98977
98978             for (key in remoteData) {
98979                 if (remoteData.hasOwnProperty(key)) {
98980                     remote = remoteData[key][comp.dataIndex];
98981                     if (remote !== undefined && data[key] !== undefined) {
98982                         data[key][comp.id] = remote;
98983                     }
98984                 }
98985             }
98986         }
98987         return data;
98988     }
98989 });
98990
98991 /**
98992  * @class Ext.grid.feature.RowBody
98993  * @extends Ext.grid.feature.Feature
98994  *
98995  * The rowbody feature enhances the grid's markup to have an additional
98996  * tr -> td -> div which spans the entire width of the original row.
98997  *
98998  * This is useful to to associate additional information with a particular
98999  * record in a grid.
99000  *
99001  * Rowbodies are initially hidden unless you override getAdditionalData.
99002  *
99003  * Will expose additional events on the gridview with the prefix of 'rowbody'.
99004  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
99005  *
99006  * @ftype rowbody
99007  */
99008 Ext.define('Ext.grid.feature.RowBody', {
99009     extend: 'Ext.grid.feature.Feature',
99010     alias: 'feature.rowbody',
99011     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
99012     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
99013     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
99014     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
99015
99016     eventPrefix: 'rowbody',
99017     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
99018     
99019     getRowBody: function(values) {
99020         return [
99021             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
99022                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
99023                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
99024                 '</td>',
99025             '</tr>'
99026         ].join('');
99027     },
99028     
99029     // injects getRowBody into the metaRowTpl.
99030     getMetaRowTplFragments: function() {
99031         return {
99032             getRowBody: this.getRowBody,
99033             rowBodyTrCls: this.rowBodyTrCls,
99034             rowBodyTdCls: this.rowBodyTdCls,
99035             rowBodyDivCls: this.rowBodyDivCls
99036         };
99037     },
99038
99039     mutateMetaRowTpl: function(metaRowTpl) {
99040         metaRowTpl.push('{[this.getRowBody(values)]}');
99041     },
99042
99043     /**
99044      * Provide additional data to the prepareData call within the grid view.
99045      * The rowbody feature adds 3 additional variables into the grid view's template.
99046      * These are rowBodyCls, rowBodyColspan, and rowBody.
99047      * @param {Object} data The data for this particular record.
99048      * @param {Number} idx The row index for this record.
99049      * @param {Ext.data.Model} record The record instance
99050      * @param {Object} orig The original result from the prepareData call to massage.
99051      */
99052     getAdditionalData: function(data, idx, record, orig) {
99053         var headerCt = this.view.headerCt,
99054             colspan  = headerCt.getColumnCount();
99055
99056         return {
99057             rowBody: "",
99058             rowBodyCls: this.rowBodyCls,
99059             rowBodyColspan: colspan
99060         };
99061     }
99062 });
99063 /**
99064  * @class Ext.grid.feature.RowWrap
99065  * @extends Ext.grid.feature.Feature
99066  * @private
99067  */
99068 Ext.define('Ext.grid.feature.RowWrap', {
99069     extend: 'Ext.grid.feature.Feature',
99070     alias: 'feature.rowwrap',
99071
99072     // turn off feature events.
99073     hasFeatureEvent: false,
99074     
99075     mutateMetaRowTpl: function(metaRowTpl) {        
99076         // Remove "x-grid-row" from the first row, note this could be wrong
99077         // if some other feature unshifted things in front.
99078         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
99079         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
99080         // 2
99081         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
99082         // 1
99083         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
99084         
99085         // 3
99086         metaRowTpl.push('</table>');
99087         // 4
99088         metaRowTpl.push('</div></td></tr>');
99089     },
99090     
99091     embedColSpan: function() {
99092         return '{colspan}';
99093     },
99094     
99095     embedFullWidth: function() {
99096         return '{fullWidth}';
99097     },
99098     
99099     getAdditionalData: function(data, idx, record, orig) {
99100         var headerCt = this.view.headerCt,
99101             colspan  = headerCt.getColumnCount(),
99102             fullWidth = headerCt.getFullWidth(),
99103             items    = headerCt.query('gridcolumn'),
99104             itemsLn  = items.length,
99105             i = 0,
99106             o = {
99107                 colspan: colspan,
99108                 fullWidth: fullWidth
99109             },
99110             id,
99111             tdClsKey,
99112             colResizerCls;
99113
99114         for (; i < itemsLn; i++) {
99115             id = items[i].id;
99116             tdClsKey = id + '-tdCls';
99117             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
99118             // give the inner td's the resizer class
99119             // while maintaining anything a user may have injected via a custom
99120             // renderer
99121             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
99122             // TODO: Unhackify the initial rendering width's
99123             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
99124             if (orig[id+'-tdAttr']) {
99125                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
99126             }
99127             
99128         }
99129
99130         return o;
99131     },
99132     
99133     getMetaRowTplFragments: function() {
99134         return {
99135             embedFullWidth: this.embedFullWidth,
99136             embedColSpan: this.embedColSpan
99137         };
99138     }
99139     
99140 });
99141 /**
99142  * @class Ext.grid.feature.Summary
99143  * @extends Ext.grid.feature.AbstractSummary
99144  * 
99145  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
99146  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
99147  * calculation and rendering.
99148  * 
99149  * ## Calculation
99150  * The summary value needs to be calculated for each column in the grid. This is controlled
99151  * by the summaryType option specified on the column. There are several built in summary types,
99152  * which can be specified as a string on the column configuration. These call underlying methods
99153  * on the store:
99154  *
99155  *  - {@link Ext.data.Store#count count}
99156  *  - {@link Ext.data.Store#sum sum}
99157  *  - {@link Ext.data.Store#min min}
99158  *  - {@link Ext.data.Store#max max}
99159  *  - {@link Ext.data.Store#average average}
99160  *
99161  * Alternatively, the summaryType can be a function definition. If this is the case,
99162  * the function is called with an array of records to calculate the summary value.
99163  * 
99164  * ## Rendering
99165  * Similar to a column, the summary also supports a summaryRenderer function. This
99166  * summaryRenderer is called before displaying a value. The function is optional, if
99167  * not specified the default calculated value is shown. The summaryRenderer is called with:
99168  *
99169  *  - value {Object} - The calculated value.
99170  *  - summaryData {Object} - Contains all raw summary values for the row.
99171  *  - field {String} - The name of the field we are calculating
99172  * 
99173  * ## Example Usage
99174  *
99175  *     @example
99176  *     Ext.define('TestResult', {
99177  *         extend: 'Ext.data.Model',
99178  *         fields: ['student', {
99179  *             name: 'mark',
99180  *             type: 'int'
99181  *         }]
99182  *     });
99183  *     
99184  *     Ext.create('Ext.grid.Panel', {
99185  *         width: 200,
99186  *         height: 140,
99187  *         renderTo: document.body,
99188  *         features: [{
99189  *             ftype: 'summary'
99190  *         }],
99191  *         store: {
99192  *             model: 'TestResult',
99193  *             data: [{
99194  *                 student: 'Student 1',
99195  *                 mark: 84
99196  *             },{
99197  *                 student: 'Student 2',
99198  *                 mark: 72
99199  *             },{
99200  *                 student: 'Student 3',
99201  *                 mark: 96
99202  *             },{
99203  *                 student: 'Student 4',
99204  *                 mark: 68
99205  *             }]
99206  *         },
99207  *         columns: [{
99208  *             dataIndex: 'student',
99209  *             text: 'Name',
99210  *             summaryType: 'count',
99211  *             summaryRenderer: function(value, summaryData, dataIndex) {
99212  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
99213  *             }
99214  *         }, {
99215  *             dataIndex: 'mark',
99216  *             text: 'Mark',
99217  *             summaryType: 'average'
99218  *         }]
99219  *     });
99220  */
99221 Ext.define('Ext.grid.feature.Summary', {
99222     
99223     /* Begin Definitions */
99224     
99225     extend: 'Ext.grid.feature.AbstractSummary',
99226     
99227     alias: 'feature.summary',
99228     
99229     /* End Definitions */
99230     
99231     /**
99232      * Gets any fragments needed for the template.
99233      * @private
99234      * @return {Object} The fragments
99235      */
99236     getFragmentTpl: function() {
99237         // this gets called before render, so we'll setup the data here.
99238         this.summaryData = this.generateSummaryData(); 
99239         return this.getSummaryFragments();
99240     },
99241     
99242     /**
99243      * Overrides the closeRows method on the template so we can include our own custom
99244      * footer.
99245      * @private
99246      * @return {Object} The custom fragments
99247      */
99248     getTableFragments: function(){
99249         if (this.showSummaryRow) {
99250             return {
99251                 closeRows: this.closeRows
99252             };
99253         }
99254     },
99255     
99256     /**
99257      * Provide our own custom footer for the grid.
99258      * @private
99259      * @return {String} The custom footer
99260      */
99261     closeRows: function() {
99262         return '</tpl>{[this.printSummaryRow()]}';
99263     },
99264     
99265     /**
99266      * Gets the data for printing a template row
99267      * @private
99268      * @param {Number} index The index in the template
99269      * @return {Array} The template values
99270      */
99271     getPrintData: function(index){
99272         var me = this,
99273             columns = me.view.headerCt.getColumnsForTpl(),
99274             i = 0,
99275             length = columns.length,
99276             data = [],
99277             active = me.summaryData,
99278             column;
99279             
99280         for (; i < length; ++i) {
99281             column = columns[i];
99282             column.gridSummaryValue = this.getColumnValue(column, active);
99283             data.push(column);
99284         }
99285         return data;
99286     },
99287     
99288     /**
99289      * Generates all of the summary data to be used when processing the template
99290      * @private
99291      * @return {Object} The summary data
99292      */
99293     generateSummaryData: function(){
99294         var me = this,
99295             data = {},
99296             store = me.view.store,
99297             columns = me.view.headerCt.getColumnsForTpl(),
99298             i = 0,
99299             length = columns.length,
99300             fieldData,
99301             key,
99302             comp;
99303             
99304         for (i = 0, length = columns.length; i < length; ++i) {
99305             comp = Ext.getCmp(columns[i].id);
99306             data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
99307         }
99308         return data;
99309     }
99310 });
99311 /**
99312  * @class Ext.grid.header.DragZone
99313  * @extends Ext.dd.DragZone
99314  * @private
99315  */
99316 Ext.define('Ext.grid.header.DragZone', {
99317     extend: 'Ext.dd.DragZone',
99318     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
99319     maxProxyWidth: 120,
99320
99321     constructor: function(headerCt) {
99322         this.headerCt = headerCt;
99323         this.ddGroup =  this.getDDGroup();
99324         this.callParent([headerCt.el]);
99325         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
99326     },
99327
99328     getDDGroup: function() {
99329         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
99330     },
99331
99332     getDragData: function(e) {
99333         var header = e.getTarget('.'+this.colHeaderCls),
99334             headerCmp;
99335
99336         if (header) {
99337             headerCmp = Ext.getCmp(header.id);
99338             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
99339                 var ddel = document.createElement('div');
99340                 ddel.innerHTML = Ext.getCmp(header.id).text;
99341                 return {
99342                     ddel: ddel,
99343                     header: headerCmp
99344                 };
99345             }
99346         }
99347         return false;
99348     },
99349
99350     onBeforeDrag: function() {
99351         return !(this.headerCt.dragging || this.disabled);
99352     },
99353
99354     onInitDrag: function() {
99355         this.headerCt.dragging = true;
99356         this.callParent(arguments);
99357     },
99358
99359     onDragDrop: function() {
99360         this.headerCt.dragging = false;
99361         this.callParent(arguments);
99362     },
99363
99364     afterRepair: function() {
99365         this.callParent();
99366         this.headerCt.dragging = false;
99367     },
99368
99369     getRepairXY: function() {
99370         return this.dragData.header.el.getXY();
99371     },
99372     
99373     disable: function() {
99374         this.disabled = true;
99375     },
99376     
99377     enable: function() {
99378         this.disabled = false;
99379     }
99380 });
99381
99382 /**
99383  * @class Ext.grid.header.DropZone
99384  * @extends Ext.dd.DropZone
99385  * @private
99386  */
99387 Ext.define('Ext.grid.header.DropZone', {
99388     extend: 'Ext.dd.DropZone',
99389     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
99390     proxyOffsets: [-4, -9],
99391
99392     constructor: function(headerCt){
99393         this.headerCt = headerCt;
99394         this.ddGroup = this.getDDGroup();
99395         this.callParent([headerCt.el]);
99396     },
99397
99398     getDDGroup: function() {
99399         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
99400     },
99401
99402     getTargetFromEvent : function(e){
99403         return e.getTarget('.' + this.colHeaderCls);
99404     },
99405
99406     getTopIndicator: function() {
99407         if (!this.topIndicator) {
99408             this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
99409                 cls: "col-move-top",
99410                 html: "&#160;"
99411             }, true);
99412         }
99413         return this.topIndicator;
99414     },
99415
99416     getBottomIndicator: function() {
99417         if (!this.bottomIndicator) {
99418             this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
99419                 cls: "col-move-bottom",
99420                 html: "&#160;"
99421             }, true);
99422         }
99423         return this.bottomIndicator;
99424     },
99425
99426     getLocation: function(e, t) {
99427         var x      = e.getXY()[0],
99428             region = Ext.fly(t).getRegion(),
99429             pos, header;
99430
99431         if ((region.right - x) <= (region.right - region.left) / 2) {
99432             pos = "after";
99433         } else {
99434             pos = "before";
99435         }
99436         return {
99437             pos: pos,
99438             header: Ext.getCmp(t.id),
99439             node: t
99440         };
99441     },
99442
99443     positionIndicator: function(draggedHeader, node, e){
99444         var location = this.getLocation(e, node),
99445             header = location.header,
99446             pos    = location.pos,
99447             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
99448             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
99449             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
99450             topXY, bottomXY, headerCtEl, minX, maxX;
99451
99452         // Cannot drag beyond non-draggable start column
99453         if (!header.draggable && header.getIndex() == 0) {
99454             return false;
99455         }
99456
99457         this.lastLocation = location;
99458
99459         if ((draggedHeader !== header) &&
99460             ((pos === "before" && nextHd !== header) ||
99461             (pos === "after" && prevHd !== header)) &&
99462             !header.isDescendantOf(draggedHeader)) {
99463
99464             // As we move in between different DropZones that are in the same
99465             // group (such as the case when in a locked grid), invalidateDrop
99466             // on the other dropZones.
99467             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
99468                 ln = allDropZones.length,
99469                 i  = 0,
99470                 dropZone;
99471
99472             for (; i < ln; i++) {
99473                 dropZone = allDropZones[i];
99474                 if (dropZone !== this && dropZone.invalidateDrop) {
99475                     dropZone.invalidateDrop();
99476                 }
99477             }
99478
99479
99480             this.valid = true;
99481             topIndicator = this.getTopIndicator();
99482             bottomIndicator = this.getBottomIndicator();
99483             if (pos === 'before') {
99484                 topAnchor = 'tl';
99485                 bottomAnchor = 'bl';
99486             } else {
99487                 topAnchor = 'tr';
99488                 bottomAnchor = 'br';
99489             }
99490             topXY = header.el.getAnchorXY(topAnchor);
99491             bottomXY = header.el.getAnchorXY(bottomAnchor);
99492
99493             // constrain the indicators to the viewable section
99494             headerCtEl = this.headerCt.el;
99495             minX = headerCtEl.getLeft();
99496             maxX = headerCtEl.getRight();
99497
99498             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
99499             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
99500
99501             // adjust by offsets, this is to center the arrows so that they point
99502             // at the split point
99503             topXY[0] -= 4;
99504             topXY[1] -= 9;
99505             bottomXY[0] -= 4;
99506
99507             // position and show indicators
99508             topIndicator.setXY(topXY);
99509             bottomIndicator.setXY(bottomXY);
99510             topIndicator.show();
99511             bottomIndicator.show();
99512         // invalidate drop operation and hide indicators
99513         } else {
99514             this.invalidateDrop();
99515         }
99516     },
99517
99518     invalidateDrop: function() {
99519         this.valid = false;
99520         this.hideIndicators();
99521     },
99522
99523     onNodeOver: function(node, dragZone, e, data) {
99524         if (data.header.el.dom !== node) {
99525             this.positionIndicator(data.header, node, e);
99526         }
99527         return this.valid ? this.dropAllowed : this.dropNotAllowed;
99528     },
99529
99530     hideIndicators: function() {
99531         this.getTopIndicator().hide();
99532         this.getBottomIndicator().hide();
99533     },
99534
99535     onNodeOut: function() {
99536         this.hideIndicators();
99537     },
99538
99539     onNodeDrop: function(node, dragZone, e, data) {
99540         if (this.valid) {
99541             this.invalidateDrop();
99542             var hd = data.header,
99543                 lastLocation = this.lastLocation,
99544                 fromCt  = hd.ownerCt,
99545                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
99546                 toCt    = lastLocation.header.ownerCt,
99547                 toIdx   = toCt.items.indexOf(lastLocation.header),
99548                 headerCt = this.headerCt,
99549                 groupCt,
99550                 scrollerOwner;
99551
99552             if (lastLocation.pos === 'after') {
99553                 toIdx++;
99554             }
99555
99556             // If we are dragging in between two HeaderContainers that have had the lockable
99557             // mixin injected we will lock/unlock headers in between sections. Note that lockable
99558             // does NOT currently support grouped headers.
99559             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
99560                 scrollerOwner = fromCt.up('[scrollerOwner]');
99561                 scrollerOwner.lock(hd, toIdx);
99562             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
99563                 scrollerOwner = fromCt.up('[scrollerOwner]');
99564                 scrollerOwner.unlock(hd, toIdx);
99565             } else {
99566                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
99567                 // in between the same container.
99568                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
99569                     toIdx--;
99570                 }
99571
99572                 // Remove dragged header from where it was without destroying it or relaying its Container
99573                 if (fromCt !== toCt) {
99574                     fromCt.suspendLayout = true;
99575                     fromCt.remove(hd, false);
99576                     fromCt.suspendLayout = false;
99577                 }
99578
99579                 // Dragged the last header out of the fromCt group... The fromCt group must die
99580                 if (fromCt.isGroupHeader) {
99581                     if (!fromCt.items.getCount()) {
99582                         groupCt = fromCt.ownerCt;
99583                         groupCt.suspendLayout = true;
99584                         groupCt.remove(fromCt, false);
99585                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
99586                         groupCt.suspendLayout = false;
99587                     } else {
99588                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
99589                         fromCt.setWidth(fromCt.minWidth);
99590                     }
99591                 }
99592
99593                 // Move dragged header into its drop position
99594                 toCt.suspendLayout = true;
99595                 if (fromCt === toCt) {
99596                     toCt.move(fromIdx, toIdx);
99597                 } else {
99598                     toCt.insert(toIdx, hd);
99599                 }
99600                 toCt.suspendLayout = false;
99601
99602                 // Group headers acquire the aggregate width of their child headers
99603                 // Therefore a child header may not flex; it must contribute a fixed width.
99604                 // But we restore the flex value when moving back into the main header container
99605                 if (toCt.isGroupHeader) {
99606                     hd.savedFlex = hd.flex;
99607                     delete hd.flex;
99608                     hd.width = hd.getWidth();
99609                     // When there was previously a flex, we need to ensure we don't count for the
99610                     // border twice.
99611                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
99612                     toCt.setWidth(toCt.minWidth);
99613                 } else {
99614                     if (hd.savedFlex) {
99615                         hd.flex = hd.savedFlex;
99616                         delete hd.width;
99617                     }
99618                 }
99619
99620
99621                 // Refresh columns cache in case we remove an emptied group column
99622                 headerCt.purgeCache();
99623                 headerCt.doLayout();
99624                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
99625                 // Emptied group header can only be destroyed after the header and grid have been refreshed
99626                 if (!fromCt.items.getCount()) {
99627                     fromCt.destroy();
99628                 }
99629             }
99630         }
99631     }
99632 });
99633
99634 /**
99635  * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
99636  * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
99637  * in the {@link Ext.grid.column.Column column configuration}.
99638  *
99639  * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
99640  * {@link Ext.grid.plugin.RowEditing}.
99641  */
99642 Ext.define('Ext.grid.plugin.Editing', {
99643     alias: 'editing.editing',
99644
99645     requires: [
99646         'Ext.grid.column.Column',
99647         'Ext.util.KeyNav'
99648     ],
99649
99650     mixins: {
99651         observable: 'Ext.util.Observable'
99652     },
99653
99654     /**
99655      * @cfg {Number} clicksToEdit
99656      * The number of clicks on a grid required to display the editor.
99657      */
99658     clicksToEdit: 2,
99659
99660     // private
99661     defaultFieldXType: 'textfield',
99662
99663     // cell, row, form
99664     editStyle: '',
99665
99666     constructor: function(config) {
99667         var me = this;
99668         Ext.apply(me, config);
99669
99670         me.addEvents(
99671             // Doc'ed in separate editing plugins
99672             'beforeedit',
99673
99674             // Doc'ed in separate editing plugins
99675             'edit',
99676
99677             // Doc'ed in separate editing plugins
99678             'validateedit'
99679         );
99680         me.mixins.observable.constructor.call(me);
99681         // TODO: Deprecated, remove in 5.0
99682         me.relayEvents(me, ['afteredit'], 'after');
99683     },
99684
99685     // private
99686     init: function(grid) {
99687         var me = this;
99688
99689         me.grid = grid;
99690         me.view = grid.view;
99691         me.initEvents();
99692         me.mon(grid, 'reconfigure', me.onReconfigure, me);
99693         me.onReconfigure();
99694
99695         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
99696         // Marks the grid as editable, so that the SelectionModel
99697         // can make appropriate decisions during navigation
99698         grid.isEditable = true;
99699         grid.editingPlugin = grid.view.editingPlugin = me;
99700     },
99701
99702     /**
99703      * Fires after the grid is reconfigured
99704      * @private
99705      */
99706     onReconfigure: function(){
99707         this.initFieldAccessors(this.view.getGridColumns());
99708     },
99709
99710     /**
99711      * @private
99712      * AbstractComponent calls destroy on all its plugins at destroy time.
99713      */
99714     destroy: function() {
99715         var me = this,
99716             grid = me.grid,
99717             headerCt = grid.headerCt,
99718             events = grid.events;
99719
99720         Ext.destroy(me.keyNav);
99721         me.removeFieldAccessors(grid.getView().getGridColumns());
99722
99723         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
99724         me.clearListeners();
99725
99726         delete me.grid.editingPlugin;
99727         delete me.grid.view.editingPlugin;
99728         delete me.grid;
99729         delete me.view;
99730         delete me.editor;
99731         delete me.keyNav;
99732     },
99733
99734     // private
99735     getEditStyle: function() {
99736         return this.editStyle;
99737     },
99738
99739     // private
99740     initFieldAccessors: function(column) {
99741         var me = this;
99742
99743         if (Ext.isArray(column)) {
99744             Ext.Array.forEach(column, me.initFieldAccessors, me);
99745             return;
99746         }
99747
99748         // Augment the Header class to have a getEditor and setEditor method
99749         // Important: Only if the header does not have its own implementation.
99750         Ext.applyIf(column, {
99751             getEditor: function(record, defaultField) {
99752                 return me.getColumnField(this, defaultField);
99753             },
99754
99755             setEditor: function(field) {
99756                 me.setColumnField(this, field);
99757             }
99758         });
99759     },
99760
99761     // private
99762     removeFieldAccessors: function(column) {
99763         var me = this;
99764
99765         if (Ext.isArray(column)) {
99766             Ext.Array.forEach(column, me.removeFieldAccessors, me);
99767             return;
99768         }
99769
99770         delete column.getEditor;
99771         delete column.setEditor;
99772     },
99773
99774     // private
99775     // remaps to the public API of Ext.grid.column.Column.getEditor
99776     getColumnField: function(columnHeader, defaultField) {
99777         var field = columnHeader.field;
99778
99779         if (!field && columnHeader.editor) {
99780             field = columnHeader.editor;
99781             delete columnHeader.editor;
99782         }
99783
99784         if (!field && defaultField) {
99785             field = defaultField;
99786         }
99787
99788         if (field) {
99789             if (Ext.isString(field)) {
99790                 field = { xtype: field };
99791             }
99792             if (Ext.isObject(field) && !field.isFormField) {
99793                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
99794                 columnHeader.field = field;
99795             }
99796
99797             Ext.apply(field, {
99798                 name: columnHeader.dataIndex
99799             });
99800
99801             return field;
99802         }
99803     },
99804
99805     // private
99806     // remaps to the public API of Ext.grid.column.Column.setEditor
99807     setColumnField: function(column, field) {
99808         if (Ext.isObject(field) && !field.isFormField) {
99809             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
99810         }
99811         column.field = field;
99812     },
99813
99814     // private
99815     initEvents: function() {
99816         var me = this;
99817         me.initEditTriggers();
99818         me.initCancelTriggers();
99819     },
99820
99821     // @abstract
99822     initCancelTriggers: Ext.emptyFn,
99823
99824     // private
99825     initEditTriggers: function() {
99826         var me = this,
99827             view = me.view,
99828             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
99829
99830         // Start editing
99831         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
99832         view.on('render', function() {
99833             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
99834                 enter: me.onEnterKey,
99835                 esc: me.onEscKey,
99836                 scope: me
99837             });
99838         }, me, { single: true });
99839     },
99840
99841     // private
99842     onEnterKey: function(e) {
99843         var me = this,
99844             grid = me.grid,
99845             selModel = grid.getSelectionModel(),
99846             record,
99847             columnHeader = grid.headerCt.getHeaderAtIndex(0);
99848
99849         // Calculate editing start position from SelectionModel
99850         // CellSelectionModel
99851         if (selModel.getCurrentPosition) {
99852             pos = selModel.getCurrentPosition();
99853             record = grid.store.getAt(pos.row);
99854             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
99855         }
99856         // RowSelectionModel
99857         else {
99858             record = selModel.getLastSelected();
99859         }
99860         me.startEdit(record, columnHeader);
99861     },
99862
99863     // private
99864     onEscKey: function(e) {
99865         this.cancelEdit();
99866     },
99867
99868     // private
99869     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
99870         this.startEdit(record, view.getHeaderAtIndex(colIdx));
99871     },
99872
99873     /**
99874      * @private
99875      * @template
99876      * Template method called before editing begins.
99877      * @param {Object} context The current editing context
99878      * @return {Boolean} Return false to cancel the editing process
99879      */
99880     beforeEdit: Ext.emptyFn,
99881
99882     /**
99883      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
99884      * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
99885      * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
99886      */
99887     startEdit: function(record, columnHeader) {
99888         var me = this,
99889             context = me.getEditingContext(record, columnHeader);
99890
99891         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
99892             return false;
99893         }
99894
99895         me.context = context;
99896         me.editing = true;
99897     },
99898
99899     /**
99900      * @private
99901      * Collects all information necessary for any subclasses to perform their editing functions.
99902      * @param record
99903      * @param columnHeader
99904      * @returns {Object} The editing context based upon the passed record and column
99905      */
99906     getEditingContext: function(record, columnHeader) {
99907         var me = this,
99908             grid = me.grid,
99909             store = grid.store,
99910             rowIdx,
99911             colIdx,
99912             view = grid.getView(),
99913             value;
99914
99915         // If they'd passed numeric row, column indices, look them up.
99916         if (Ext.isNumber(record)) {
99917             rowIdx = record;
99918             record = store.getAt(rowIdx);
99919         } else {
99920             rowIdx = store.indexOf(record);
99921         }
99922         if (Ext.isNumber(columnHeader)) {
99923             colIdx = columnHeader;
99924             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
99925         } else {
99926             colIdx = columnHeader.getIndex();
99927         }
99928
99929         value = record.get(columnHeader.dataIndex);
99930         return {
99931             grid: grid,
99932             record: record,
99933             field: columnHeader.dataIndex,
99934             value: value,
99935             row: view.getNode(rowIdx),
99936             column: columnHeader,
99937             rowIdx: rowIdx,
99938             colIdx: colIdx
99939         };
99940     },
99941
99942     /**
99943      * Cancels any active edit that is in progress.
99944      */
99945     cancelEdit: function() {
99946         this.editing = false;
99947     },
99948
99949     /**
99950      * Completes the edit if there is an active edit in progress.
99951      */
99952     completeEdit: function() {
99953         var me = this;
99954
99955         if (me.editing && me.validateEdit()) {
99956             me.fireEvent('edit', me.context);
99957         }
99958
99959         delete me.context;
99960         me.editing = false;
99961     },
99962
99963     // @abstract
99964     validateEdit: function() {
99965         var me = this,
99966             context = me.context;
99967
99968         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
99969     }
99970 });
99971
99972 /**
99973  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
99974  * cell will be editable at a time. The field that will be used for the editor is defined at the
99975  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
99976  *
99977  * If an editor is not specified for a particular column then that cell will not be editable and it will
99978  * be skipped when activated via the mouse or the keyboard.
99979  *
99980  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
99981  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
99982  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
99983  *
99984  *     @example
99985  *     Ext.create('Ext.data.Store', {
99986  *         storeId:'simpsonsStore',
99987  *         fields:['name', 'email', 'phone'],
99988  *         data:{'items':[
99989  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
99990  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
99991  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
99992  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
99993  *         ]},
99994  *         proxy: {
99995  *             type: 'memory',
99996  *             reader: {
99997  *                 type: 'json',
99998  *                 root: 'items'
99999  *             }
100000  *         }
100001  *     });
100002  *
100003  *     Ext.create('Ext.grid.Panel', {
100004  *         title: 'Simpsons',
100005  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
100006  *         columns: [
100007  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
100008  *             {header: 'Email', dataIndex: 'email', flex:1,
100009  *                 editor: {
100010  *                     xtype: 'textfield',
100011  *                     allowBlank: false
100012  *                 }
100013  *             },
100014  *             {header: 'Phone', dataIndex: 'phone'}
100015  *         ],
100016  *         selType: 'cellmodel',
100017  *         plugins: [
100018  *             Ext.create('Ext.grid.plugin.CellEditing', {
100019  *                 clicksToEdit: 1
100020  *             })
100021  *         ],
100022  *         height: 200,
100023  *         width: 400,
100024  *         renderTo: Ext.getBody()
100025  *     });
100026  */
100027 Ext.define('Ext.grid.plugin.CellEditing', {
100028     alias: 'plugin.cellediting',
100029     extend: 'Ext.grid.plugin.Editing',
100030     requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
100031
100032     constructor: function() {
100033         /**
100034          * @event beforeedit
100035          * Fires before cell editing is triggered. Return false from event handler to stop the editing.
100036          *
100037          * @param {Object} e An edit event with the following properties:
100038          *
100039          * - grid - The grid
100040          * - record - The record being edited
100041          * - field - The field name being edited
100042          * - value - The value for the field being edited.
100043          * - row - The grid table row
100044          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
100045          * - rowIdx - The row index that is being edited
100046          * - colIdx - The column index that is being edited
100047          * - cancel - Set this to true to cancel the edit or return false from your handler.
100048          */
100049         /**
100050          * @event edit
100051          * Fires after a cell is edited. Usage example:
100052          *
100053          *     grid.on('edit', function(editor, e) {
100054          *         // commit the changes right after editing finished
100055          *         e.record.commit();
100056          *     };
100057          *
100058          * @param {Ext.grid.plugin.Editing} editor
100059          * @param {Object} e An edit event with the following properties:
100060          *
100061          * - grid - The grid
100062          * - record - The record that was edited
100063          * - field - The field name that was edited
100064          * - value - The value being set
100065          * - originalValue - The original value for the field, before the edit.
100066          * - row - The grid table row
100067          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
100068          * - rowIdx - The row index that was edited
100069          * - colIdx - The column index that was edited
100070          */
100071         /**
100072          * @event validateedit
100073          * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
100074          * cancel the change.
100075          *
100076          * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
100077          * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
100078          * example) and then setting the field's new value in the Record directly:
100079          *
100080          *     grid.on('validateedit', function(editor, e) {
100081          *       var myTargetRow = 6;
100082          *
100083          *       if (e.row == myTargetRow) {
100084          *         e.cancel = true;
100085          *         e.record.data[e.field] = e.value;
100086          *       }
100087          *     });
100088          *
100089          * @param {Ext.grid.plugin.Editing} editor
100090          * @param {Object} e An edit event with the following properties:
100091          *
100092          * - grid - The grid
100093          * - record - The record being edited
100094          * - field - The field name being edited
100095          * - value - The value being set
100096          * - originalValue - The original value for the field, before the edit.
100097          * - row - The grid table row
100098          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
100099          * - rowIdx - The row index that is being edited
100100          * - colIdx - The column index that is being edited
100101          * - cancel - Set this to true to cancel the edit or return false from your handler.
100102          */
100103         this.callParent(arguments);
100104         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
100105             return editor.editorId;
100106         });
100107         this.editTask = Ext.create('Ext.util.DelayedTask');
100108     },
100109     
100110     onReconfigure: function(){
100111         this.editors.clear();
100112         this.callParent();    
100113     },
100114
100115     /**
100116      * @private
100117      * AbstractComponent calls destroy on all its plugins at destroy time.
100118      */
100119     destroy: function() {
100120         var me = this;
100121         me.editTask.cancel();
100122         me.editors.each(Ext.destroy, Ext);
100123         me.editors.clear();
100124         me.callParent(arguments);
100125     },
100126     
100127     onBodyScroll: function() {
100128         var ed = this.getActiveEditor();
100129         if (ed && ed.field) {
100130             if (ed.field.triggerBlur) {
100131                 ed.field.triggerBlur();
100132             } else {
100133                 ed.field.blur();
100134             }
100135         }
100136     },
100137
100138     // private
100139     // Template method called from base class's initEvents
100140     initCancelTriggers: function() {
100141         var me   = this,
100142             grid = me.grid,
100143             view = grid.view;
100144             
100145         view.addElListener('mousewheel', me.cancelEdit, me);
100146         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
100147         me.mon(grid, {
100148             columnresize: me.cancelEdit,
100149             columnmove: me.cancelEdit,
100150             scope: me
100151         });
100152     },
100153
100154     /**
100155      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
100156      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
100157      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
100158      */
100159     startEdit: function(record, columnHeader) {
100160         var me = this,
100161             value = record.get(columnHeader.dataIndex),
100162             context = me.getEditingContext(record, columnHeader),
100163             ed;
100164
100165         record = context.record;
100166         columnHeader = context.column;
100167
100168         // Complete the edit now, before getting the editor's target
100169         // cell DOM element. Completing the edit causes a view refresh.
100170         me.completeEdit();
100171
100172         context.originalValue = context.value = value;
100173         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
100174             return false;
100175         }
100176         
100177         // See if the field is editable for the requested record
100178         if (columnHeader && !columnHeader.getEditor(record)) {
100179             return false;
100180         }
100181         
100182         ed = me.getEditor(record, columnHeader);
100183         if (ed) {
100184             me.context = context;
100185             me.setActiveEditor(ed);
100186             me.setActiveRecord(record);
100187             me.setActiveColumn(columnHeader);
100188
100189             // Defer, so we have some time between view scroll to sync up the editor
100190             me.editTask.delay(15, ed.startEdit, ed, [me.getCell(record, columnHeader), value]);
100191         } else {
100192             // BrowserBug: WebKit & IE refuse to focus the element, rather
100193             // it will focus it and then immediately focus the body. This
100194             // temporary hack works for Webkit and IE6. IE7 and 8 are still
100195             // broken
100196             me.grid.getView().getEl(columnHeader).focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
100197         }
100198     },
100199
100200     completeEdit: function() {
100201         var activeEd = this.getActiveEditor();
100202         if (activeEd) {
100203             activeEd.completeEdit();
100204         }
100205     },
100206
100207     // internal getters/setters
100208     setActiveEditor: function(ed) {
100209         this.activeEditor = ed;
100210     },
100211
100212     getActiveEditor: function() {
100213         return this.activeEditor;
100214     },
100215
100216     setActiveColumn: function(column) {
100217         this.activeColumn = column;
100218     },
100219
100220     getActiveColumn: function() {
100221         return this.activeColumn;
100222     },
100223
100224     setActiveRecord: function(record) {
100225         this.activeRecord = record;
100226     },
100227
100228     getActiveRecord: function() {
100229         return this.activeRecord;
100230     },
100231
100232     getEditor: function(record, column) {
100233         var me = this,
100234             editors = me.editors,
100235             editorId = column.getItemId(),
100236             editor = editors.getByKey(editorId);
100237
100238         if (editor) {
100239             return editor;
100240         } else {
100241             editor = column.getEditor(record);
100242             if (!editor) {
100243                 return false;
100244             }
100245
100246             // Allow them to specify a CellEditor in the Column
100247             if (!(editor instanceof Ext.grid.CellEditor)) {
100248                 editor = Ext.create('Ext.grid.CellEditor', {
100249                     editorId: editorId,
100250                     field: editor
100251                 });
100252             }
100253             editor.parentEl = me.grid.getEditorParent();
100254             // editor.parentEl should be set here.
100255             editor.on({
100256                 scope: me,
100257                 specialkey: me.onSpecialKey,
100258                 complete: me.onEditComplete,
100259                 canceledit: me.cancelEdit
100260             });
100261             editors.add(editor);
100262             return editor;
100263         }
100264     },
100265     
100266     // inherit docs
100267     setColumnField: function(column, field) {
100268         var ed = this.editors.getByKey(column.getItemId());
100269         Ext.destroy(ed, column.field);
100270         this.editors.removeAtKey(column.getItemId());
100271         this.callParent(arguments);
100272     },
100273
100274     /**
100275      * Gets the cell (td) for a particular record and column.
100276      * @param {Ext.data.Model} record
100277      * @param {Ext.grid.column.Column} column
100278      * @private
100279      */
100280     getCell: function(record, column) {
100281         return this.grid.getView().getCell(record, column);
100282     },
100283
100284     onSpecialKey: function(ed, field, e) {
100285         var grid = this.grid,
100286             sm;
100287         if (e.getKey() === e.TAB) {
100288             e.stopEvent();
100289             sm = grid.getSelectionModel();
100290             if (sm.onEditorTab) {
100291                 sm.onEditorTab(this, e);
100292             }
100293         }
100294     },
100295
100296     onEditComplete : function(ed, value, startValue) {
100297         var me = this,
100298             grid = me.grid,
100299             sm = grid.getSelectionModel(),
100300             activeColumn = me.getActiveColumn(),
100301             dataIndex;
100302
100303         if (activeColumn) {
100304             dataIndex = activeColumn.dataIndex;
100305
100306             me.setActiveEditor(null);
100307             me.setActiveColumn(null);
100308             me.setActiveRecord(null);
100309             delete sm.wasEditing;
100310     
100311             if (!me.validateEdit()) {
100312                 return;
100313             }
100314             // Only update the record if the new value is different than the
100315             // startValue, when the view refreshes its el will gain focus
100316             if (value !== startValue) {
100317                 me.context.record.set(dataIndex, value);
100318             // Restore focus back to the view's element.
100319             } else {
100320                 grid.getView().getEl(activeColumn).focus();
100321             }
100322             me.context.value = value;
100323             me.fireEvent('edit', me, me.context);
100324         }
100325     },
100326
100327     /**
100328      * Cancels any active editing.
100329      */
100330     cancelEdit: function() {
100331         var me = this,
100332             activeEd = me.getActiveEditor(),
100333             viewEl = me.grid.getView().getEl(me.getActiveColumn());
100334
100335         me.setActiveEditor(null);
100336         me.setActiveColumn(null);
100337         me.setActiveRecord(null);
100338         if (activeEd) {
100339             activeEd.cancelEdit();
100340             viewEl.focus();
100341         }
100342     },
100343
100344     /**
100345      * Starts editing by position (row/column)
100346      * @param {Object} position A position with keys of row and column.
100347      */
100348     startEditByPosition: function(position) {
100349         var me = this,
100350             grid = me.grid,
100351             sm = grid.getSelectionModel(),
100352             editRecord = grid.store.getAt(position.row),
100353             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
100354
100355         if (sm.selectByPosition) {
100356             sm.selectByPosition(position);
100357         }
100358         me.startEdit(editRecord, editColumnHeader);
100359     }
100360 });
100361 /**
100362  * This plugin provides drag and/or drop functionality for a GridView.
100363  *
100364  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
100365  * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
100366  * methods with the following properties:
100367  *
100368  * - `copy` : Boolean
100369  *
100370  *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
100371  *   the control key was pressed when the drag operation was begun.
100372  *
100373  * - `view` : GridView
100374  *
100375  *   The source GridView from which the drag originated.
100376  *
100377  * - `ddel` : HtmlElement
100378  *
100379  *   The drag proxy element which moves with the mouse
100380  *
100381  * - `item` : HtmlElement
100382  *
100383  *   The GridView node upon which the mousedown event was registered.
100384  *
100385  * - `records` : Array
100386  *
100387  *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
100388  *
100389  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
100390  * members of the same ddGroup which processes such data objects.
100391  *
100392  * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
100393  * beforedrop}` and `{@link #drop drop}`
100394  *
100395  *     @example
100396  *     Ext.create('Ext.data.Store', {
100397  *         storeId:'simpsonsStore',
100398  *         fields:['name'],
100399  *         data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
100400  *         proxy: {
100401  *             type: 'memory',
100402  *             reader: 'array'
100403  *         }
100404  *     });
100405  *
100406  *     Ext.create('Ext.grid.Panel', {
100407  *         store: 'simpsonsStore',
100408  *         columns: [
100409  *             {header: 'Name',  dataIndex: 'name', flex: true}
100410  *         ],
100411  *         viewConfig: {
100412  *             plugins: {
100413  *                 ptype: 'gridviewdragdrop',
100414  *                 dragText: 'Drag and drop to reorganize'
100415  *             }
100416  *         },
100417  *         height: 200,
100418  *         width: 400,
100419  *         renderTo: Ext.getBody()
100420  *     });
100421  */
100422 Ext.define('Ext.grid.plugin.DragDrop', {
100423     extend: 'Ext.AbstractPlugin',
100424     alias: 'plugin.gridviewdragdrop',
100425
100426     uses: [
100427         'Ext.view.DragZone',
100428         'Ext.grid.ViewDropZone'
100429     ],
100430
100431     /**
100432      * @event beforedrop
100433      * **This event is fired through the GridView. Add listeners to the GridView object**
100434      *
100435      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
100436      *
100437      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
100438      *
100439      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
100440      * back to the point from which the drag began.
100441      *
100442      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
100443      * was valid, and that the repair operation should not take place.
100444      *
100445      * Any other return value continues with the data transfer operation.
100446      *
100447      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
100448      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
100449      *
100450      * - copy : Boolean
100451      *
100452      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
100453      *   the control key was pressed when the drag operation was begun
100454      *
100455      * - view : GridView
100456      *
100457      *   The source GridView from which the drag originated.
100458      *
100459      * - ddel : HtmlElement
100460      *
100461      *   The drag proxy element which moves with the mouse
100462      *
100463      * - item : HtmlElement
100464      *
100465      *   The GridView node upon which the mousedown event was registered.
100466      *
100467      * - records : Array
100468      *
100469      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
100470      *
100471      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
100472      *
100473      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
100474      * of the node.
100475      *
100476      * @param {Function} dropFunction
100477      *
100478      * A function to call to complete the data transfer operation and either move or copy Model instances from the
100479      * source View's Store to the destination View's Store.
100480      *
100481      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
100482      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
100483      *
100484      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
100485      */
100486
100487     /**
100488      * @event drop
100489      * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
100490      * has been completed and the data has been moved or copied.
100491      *
100492      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
100493      *
100494      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
100495      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
100496      *
100497      * - copy : Boolean
100498      *
100499      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
100500      *   the control key was pressed when the drag operation was begun
100501      *
100502      * - view : GridView
100503      *
100504      *   The source GridView from which the drag originated.
100505      *
100506      * - ddel : HtmlElement
100507      *
100508      *   The drag proxy element which moves with the mouse
100509      *
100510      * - item : HtmlElement
100511      *
100512      *   The GridView node upon which the mousedown event was registered.
100513      *
100514      * - records : Array
100515      *
100516      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
100517      *
100518      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
100519      *
100520      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
100521      * of the node.
100522      */
100523
100524     dragText : '{0} selected row{1}',
100525
100526     /**
100527      * @cfg {String} ddGroup
100528      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
100529      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
100530      */
100531     ddGroup : "GridDD",
100532
100533     /**
100534      * @cfg {String} dragGroup
100535      * The ddGroup to which the DragZone will belong.
100536      *
100537      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
100538      * Drag/DropZones which are members of the same ddGroup.
100539      */
100540
100541     /**
100542      * @cfg {String} dropGroup
100543      * The ddGroup to which the DropZone will belong.
100544      *
100545      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
100546      * Drag/DropZones which are members of the same ddGroup.
100547      */
100548
100549     /**
100550      * @cfg {Boolean} enableDrop
100551      * False to disallow the View from accepting drop gestures.
100552      */
100553     enableDrop: true,
100554
100555     /**
100556      * @cfg {Boolean} enableDrag
100557      * False to disallow dragging items from the View.
100558      */
100559     enableDrag: true,
100560
100561     init : function(view) {
100562         view.on('render', this.onViewRender, this, {single: true});
100563     },
100564
100565     /**
100566      * @private
100567      * AbstractComponent calls destroy on all its plugins at destroy time.
100568      */
100569     destroy: function() {
100570         Ext.destroy(this.dragZone, this.dropZone);
100571     },
100572
100573     enable: function() {
100574         var me = this;
100575         if (me.dragZone) {
100576             me.dragZone.unlock();
100577         }
100578         if (me.dropZone) {
100579             me.dropZone.unlock();
100580         }
100581         me.callParent();
100582     },
100583
100584     disable: function() {
100585         var me = this;
100586         if (me.dragZone) {
100587             me.dragZone.lock();
100588         }
100589         if (me.dropZone) {
100590             me.dropZone.lock();
100591         }
100592         me.callParent();
100593     },
100594
100595     onViewRender : function(view) {
100596         var me = this;
100597
100598         if (me.enableDrag) {
100599             me.dragZone = Ext.create('Ext.view.DragZone', {
100600                 view: view,
100601                 ddGroup: me.dragGroup || me.ddGroup,
100602                 dragText: me.dragText
100603             });
100604         }
100605
100606         if (me.enableDrop) {
100607             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
100608                 view: view,
100609                 ddGroup: me.dropGroup || me.ddGroup
100610             });
100611         }
100612     }
100613 });
100614 /**
100615  * @class Ext.grid.plugin.HeaderReorderer
100616  * @extends Ext.util.Observable
100617  * @private
100618  */
100619 Ext.define('Ext.grid.plugin.HeaderReorderer', {
100620     extend: 'Ext.util.Observable',
100621     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
100622     alias: 'plugin.gridheaderreorderer',
100623
100624     init: function(headerCt) {
100625         this.headerCt = headerCt;
100626         headerCt.on('render', this.onHeaderCtRender, this);
100627     },
100628
100629     /**
100630      * @private
100631      * AbstractComponent calls destroy on all its plugins at destroy time.
100632      */
100633     destroy: function() {
100634         Ext.destroy(this.dragZone, this.dropZone);
100635     },
100636
100637     onHeaderCtRender: function() {
100638         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
100639         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
100640         if (this.disabled) {
100641             this.dragZone.disable();
100642         }
100643     },
100644     
100645     enable: function() {
100646         this.disabled = false;
100647         if (this.dragZone) {
100648             this.dragZone.enable();
100649         }
100650     },
100651     
100652     disable: function() {
100653         this.disabled = true;
100654         if (this.dragZone) {
100655             this.dragZone.disable();
100656         }
100657     }
100658 });
100659 /**
100660  * @class Ext.grid.plugin.HeaderResizer
100661  * @extends Ext.util.Observable
100662  *
100663  * Plugin to add header resizing functionality to a HeaderContainer.
100664  * Always resizing header to the left of the splitter you are resizing.
100665  */
100666 Ext.define('Ext.grid.plugin.HeaderResizer', {
100667     extend: 'Ext.util.Observable',
100668     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
100669     alias: 'plugin.gridheaderresizer',
100670
100671     disabled: false,
100672
100673     /**
100674      * @cfg {Boolean} dynamic
100675      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
100676      */
100677     configs: {
100678         dynamic: true
100679     },
100680
100681     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
100682
100683     minColWidth: 40,
100684     maxColWidth: 1000,
100685     wResizeCursor: 'col-resize',
100686     eResizeCursor: 'col-resize',
100687     // not using w and e resize bc we are only ever resizing one
100688     // column
100689     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
100690     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
100691
100692     init: function(headerCt) {
100693         this.headerCt = headerCt;
100694         headerCt.on('render', this.afterHeaderRender, this, {single: true});
100695     },
100696
100697     /**
100698      * @private
100699      * AbstractComponent calls destroy on all its plugins at destroy time.
100700      */
100701     destroy: function() {
100702         if (this.tracker) {
100703             this.tracker.destroy();
100704         }
100705     },
100706
100707     afterHeaderRender: function() {
100708         var headerCt = this.headerCt,
100709             el = headerCt.el;
100710
100711         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
100712
100713         this.tracker = Ext.create('Ext.dd.DragTracker', {
100714             disabled: this.disabled,
100715             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
100716             onStart: Ext.Function.bind(this.onStart, this),
100717             onDrag: Ext.Function.bind(this.onDrag, this),
100718             onEnd: Ext.Function.bind(this.onEnd, this),
100719             tolerance: 3,
100720             autoStart: 300,
100721             el: el
100722         });
100723     },
100724
100725     // As we mouse over individual headers, change the cursor to indicate
100726     // that resizing is available, and cache the resize target header for use
100727     // if/when they mousedown.
100728     onHeaderCtMouseMove: function(e, t) {
100729         if (this.headerCt.dragging) {
100730             if (this.activeHd) {
100731                 this.activeHd.el.dom.style.cursor = '';
100732                 delete this.activeHd;
100733             }
100734         } else {
100735             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
100736                 overHeader, resizeHeader;
100737
100738             if (headerEl){
100739                 overHeader = Ext.getCmp(headerEl.id);
100740
100741                 // On left edge, go back to the previous non-hidden header.
100742                 if (overHeader.isOnLeftEdge(e)) {
100743                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
100744
100745                 }
100746                 // Else, if on the right edge, we're resizing the column we are over
100747                 else if (overHeader.isOnRightEdge(e)) {
100748                     resizeHeader = overHeader;
100749                 }
100750                 // Between the edges: we are not resizing
100751                 else {
100752                     resizeHeader = null;
100753                 }
100754
100755                 // We *are* resizing
100756                 if (resizeHeader) {
100757                     // If we're attempting to resize a group header, that cannot be resized,
100758                     // so find its last visible leaf header; Group headers are sized
100759                     // by the size of their child headers.
100760                     if (resizeHeader.isGroupHeader) {
100761                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
100762                     }
100763
100764                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
100765                     // check whether the resizablwe property is set to false.
100766                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
100767                         this.activeHd = resizeHeader;
100768                         overHeader.el.dom.style.cursor = this.eResizeCursor;
100769                     }
100770                 // reset
100771                 } else {
100772                     overHeader.el.dom.style.cursor = '';
100773                     delete this.activeHd;
100774                 }
100775             }
100776         }
100777     },
100778
100779     // only start when there is an activeHd
100780     onBeforeStart : function(e){
100781         var t = e.getTarget();
100782         // cache the activeHd because it will be cleared.
100783         this.dragHd = this.activeHd;
100784
100785         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
100786             //this.headerCt.dragging = true;
100787             this.tracker.constrainTo = this.getConstrainRegion();
100788             return true;
100789         } else {
100790             this.headerCt.dragging = false;
100791             return false;
100792         }
100793     },
100794
100795     // get the region to constrain to, takes into account max and min col widths
100796     getConstrainRegion: function() {
100797         var dragHdEl = this.dragHd.el,
100798             region   = Ext.util.Region.getRegion(dragHdEl);
100799
100800         return region.adjust(
100801             0,
100802             this.maxColWidth - dragHdEl.getWidth(),
100803             0,
100804             this.minColWidth
100805         );
100806     },
100807
100808     // initialize the left and right hand side markers around
100809     // the header that we are resizing
100810     onStart: function(e){
100811         var me       = this,
100812             dragHd   = me.dragHd,
100813             dragHdEl = dragHd.el,
100814             width    = dragHdEl.getWidth(),
100815             headerCt = me.headerCt,
100816             t        = e.getTarget();
100817
100818         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
100819             headerCt.dragging = true;
100820         }
100821
100822         me.origWidth = width;
100823
100824         // setup marker proxies
100825         if (!me.dynamic) {
100826             var xy           = dragHdEl.getXY(),
100827                 gridSection  = headerCt.up('[scrollerOwner]'),
100828                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
100829                 firstSection = dragHct.up(),
100830                 lhsMarker    = gridSection.getLhsMarker(),
100831                 rhsMarker    = gridSection.getRhsMarker(),
100832                 el           = rhsMarker.parent(),
100833                 offsetLeft   = el.getLeft(true),
100834                 offsetTop    = el.getTop(true),
100835                 topLeft      = el.translatePoints(xy),
100836                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
100837                 top = topLeft.top - offsetTop;
100838
100839             lhsMarker.setTop(top);
100840             rhsMarker.setTop(top);
100841             lhsMarker.setHeight(markerHeight);
100842             rhsMarker.setHeight(markerHeight);
100843             lhsMarker.setLeft(topLeft.left - offsetLeft);
100844             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
100845         }
100846     },
100847
100848     // synchronize the rhsMarker with the mouse movement
100849     onDrag: function(e){
100850         if (!this.dynamic) {
100851             var xy          = this.tracker.getXY('point'),
100852                 gridSection = this.headerCt.up('[scrollerOwner]'),
100853                 rhsMarker   = gridSection.getRhsMarker(),
100854                 el          = rhsMarker.parent(),
100855                 topLeft     = el.translatePoints(xy),
100856                 offsetLeft  = el.getLeft(true);
100857
100858             rhsMarker.setLeft(topLeft.left - offsetLeft);
100859         // Resize as user interacts
100860         } else {
100861             this.doResize();
100862         }
100863     },
100864
100865     onEnd: function(e){
100866         this.headerCt.dragging = false;
100867         if (this.dragHd) {
100868             if (!this.dynamic) {
100869                 var dragHd      = this.dragHd,
100870                     gridSection = this.headerCt.up('[scrollerOwner]'),
100871                     lhsMarker   = gridSection.getLhsMarker(),
100872                     rhsMarker   = gridSection.getRhsMarker(),
100873                     currWidth   = dragHd.getWidth(),
100874                     offset      = this.tracker.getOffset('point'),
100875                     offscreen   = -9999;
100876
100877                 // hide markers
100878                 lhsMarker.setLeft(offscreen);
100879                 rhsMarker.setLeft(offscreen);
100880             }
100881             this.doResize();
100882         }
100883     },
100884
100885     doResize: function() {
100886         if (this.dragHd) {
100887             var dragHd = this.dragHd,
100888                 nextHd,
100889                 offset = this.tracker.getOffset('point');
100890
100891             // resize the dragHd
100892             if (dragHd.flex) {
100893                 delete dragHd.flex;
100894             }
100895
100896             this.headerCt.suspendLayout = true;
100897             dragHd.setWidth(this.origWidth + offset[0], false);
100898
100899             // In the case of forceFit, change the following Header width.
100900             // Then apply the two width changes by laying out the owning HeaderContainer
100901             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
100902             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
100903             if (this.headerCt.forceFit) {
100904                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
100905                 if (nextHd) {
100906                     delete nextHd.flex;
100907                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
100908                 }
100909             }
100910             this.headerCt.suspendLayout = false;
100911             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
100912         }
100913     },
100914
100915     disable: function() {
100916         this.disabled = true;
100917         if (this.tracker) {
100918             this.tracker.disable();
100919         }
100920     },
100921
100922     enable: function() {
100923         this.disabled = false;
100924         if (this.tracker) {
100925             this.tracker.enable();
100926         }
100927     }
100928 });
100929 /**
100930  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
100931  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
100932  * for editing. There is a button to save or cancel all changes for the edit.
100933  *
100934  * The field that will be used for the editor is defined at the
100935  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
100936  * If an editor is not specified for a particular column then that column won't be editable and the value of
100937  * the column will be displayed.
100938  *
100939  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
100940  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
100941  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
100942  *
100943  *     @example
100944  *     Ext.create('Ext.data.Store', {
100945  *         storeId:'simpsonsStore',
100946  *         fields:['name', 'email', 'phone'],
100947  *         data: [
100948  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
100949  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
100950  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
100951  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
100952  *         ]
100953  *     });
100954  *
100955  *     Ext.create('Ext.grid.Panel', {
100956  *         title: 'Simpsons',
100957  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
100958  *         columns: [
100959  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
100960  *             {header: 'Email', dataIndex: 'email', flex:1,
100961  *                 editor: {
100962  *                     xtype: 'textfield',
100963  *                     allowBlank: false
100964  *                 }
100965  *             },
100966  *             {header: 'Phone', dataIndex: 'phone'}
100967  *         ],
100968  *         selType: 'rowmodel',
100969  *         plugins: [
100970  *             Ext.create('Ext.grid.plugin.RowEditing', {
100971  *                 clicksToEdit: 1
100972  *             })
100973  *         ],
100974  *         height: 200,
100975  *         width: 400,
100976  *         renderTo: Ext.getBody()
100977  *     });
100978  */
100979 Ext.define('Ext.grid.plugin.RowEditing', {
100980     extend: 'Ext.grid.plugin.Editing',
100981     alias: 'plugin.rowediting',
100982
100983     requires: [
100984         'Ext.grid.RowEditor'
100985     ],
100986
100987     editStyle: 'row',
100988
100989     /**
100990      * @cfg {Boolean} autoCancel
100991      * True to automatically cancel any pending changes when the row editor begins editing a new row.
100992      * False to force the user to explicitly cancel the pending changes. Defaults to true.
100993      */
100994     autoCancel: true,
100995
100996     /**
100997      * @cfg {Number} clicksToMoveEditor
100998      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
100999      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
101000      */
101001
101002     /**
101003      * @cfg {Boolean} errorSummary
101004      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
101005      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
101006      */
101007     errorSummary: true,
101008
101009     /**
101010      * @event beforeedit
101011      * Fires before row editing is triggered.
101012      *
101013      * @param {Ext.grid.plugin.Editing} editor
101014      * @param {Object} e An edit event with the following properties:
101015      *
101016      * - grid - The grid this editor is on
101017      * - view - The grid view
101018      * - store - The grid store
101019      * - record - The record being edited
101020      * - row - The grid table row
101021      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
101022      * - rowIdx - The row index that is being edited
101023      * - colIdx - The column index that initiated the edit
101024      * - cancel - Set this to true to cancel the edit or return false from your handler.
101025      */
101026     
101027     /**
101028      * @event canceledit
101029      * Fires when the user has started editing a row but then cancelled the edit
101030      * @param {Object} grid The grid
101031      */
101032     
101033     /**
101034      * @event edit
101035      * Fires after a row is edited. Usage example:
101036      *
101037      *     grid.on('edit', function(editor, e) {
101038      *         // commit the changes right after editing finished
101039      *         e.record.commit();
101040      *     };
101041      *
101042      * @param {Ext.grid.plugin.Editing} editor
101043      * @param {Object} e An edit event with the following properties:
101044      *
101045      * - grid - The grid this editor is on
101046      * - view - The grid view
101047      * - store - The grid store
101048      * - record - The record being edited
101049      * - row - The grid table row
101050      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
101051      * - rowIdx - The row index that is being edited
101052      * - colIdx - The column index that initiated the edit
101053      */
101054     /**
101055      * @event validateedit
101056      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
101057      * edit event object has the following properties
101058      *
101059      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
101060      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
101061      * and then setting the field's new value in the Record directly:
101062      *
101063      *     grid.on('validateedit', function(editor, e) {
101064      *       var myTargetRow = 6;
101065      *
101066      *       if (e.rowIdx == myTargetRow) {
101067      *         e.cancel = true;
101068      *         e.record.data[e.field] = e.value;
101069      *       }
101070      *     });
101071      *
101072      * @param {Ext.grid.plugin.Editing} editor
101073      * @param {Object} e An edit event with the following properties:
101074      *
101075      * - grid - The grid this editor is on
101076      * - view - The grid view
101077      * - store - The grid store
101078      * - record - The record being edited
101079      * - row - The grid table row
101080      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
101081      * - rowIdx - The row index that is being edited
101082      * - colIdx - The column index that initiated the edit
101083      * - cancel - Set this to true to cancel the edit or return false from your handler.
101084      */
101085
101086     constructor: function() {
101087         var me = this;
101088         me.callParent(arguments);
101089
101090         if (!me.clicksToMoveEditor) {
101091             me.clicksToMoveEditor = me.clicksToEdit;
101092         }
101093
101094         me.autoCancel = !!me.autoCancel;
101095     },
101096
101097     /**
101098      * @private
101099      * AbstractComponent calls destroy on all its plugins at destroy time.
101100      */
101101     destroy: function() {
101102         var me = this;
101103         Ext.destroy(me.editor);
101104         me.callParent(arguments);
101105     },
101106
101107     /**
101108      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
101109      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
101110      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
101111      */
101112     startEdit: function(record, columnHeader) {
101113         var me = this,
101114             editor = me.getEditor();
101115
101116         if (me.callParent(arguments) === false) {
101117             return false;
101118         }
101119
101120         // Fire off our editor
101121         if (editor.beforeEdit() !== false) {
101122             editor.startEdit(me.context.record, me.context.column);
101123         }
101124     },
101125
101126     // private
101127     cancelEdit: function() {
101128         var me = this;
101129
101130         if (me.editing) {
101131             me.getEditor().cancelEdit();
101132             me.callParent(arguments);
101133             
101134             me.fireEvent('canceledit', me.context);
101135         }
101136     },
101137
101138     // private
101139     completeEdit: function() {
101140         var me = this;
101141
101142         if (me.editing && me.validateEdit()) {
101143             me.editing = false;
101144             me.fireEvent('edit', me.context);
101145         }
101146     },
101147
101148     // private
101149     validateEdit: function() {
101150         var me             = this,
101151             editor         = me.editor,
101152             context        = me.context,
101153             record         = context.record,
101154             newValues      = {},
101155             originalValues = {},
101156             name;
101157
101158         editor.items.each(function(item) {
101159             name = item.name;
101160
101161             newValues[name]      = item.getValue();
101162             originalValues[name] = record.get(name);
101163         });
101164
101165         Ext.apply(context, {
101166             newValues      : newValues,
101167             originalValues : originalValues
101168         });
101169
101170         return me.callParent(arguments) && me.getEditor().completeEdit();
101171     },
101172
101173     // private
101174     getEditor: function() {
101175         var me = this;
101176
101177         if (!me.editor) {
101178             me.editor = me.initEditor();
101179         }
101180         return me.editor;
101181     },
101182
101183     // private
101184     initEditor: function() {
101185         var me = this,
101186             grid = me.grid,
101187             view = me.view,
101188             headerCt = grid.headerCt;
101189
101190         return Ext.create('Ext.grid.RowEditor', {
101191             autoCancel: me.autoCancel,
101192             errorSummary: me.errorSummary,
101193             fields: headerCt.getGridColumns(),
101194             hidden: true,
101195
101196             // keep a reference..
101197             editingPlugin: me,
101198             renderTo: view.el
101199         });
101200     },
101201
101202     // private
101203     initEditTriggers: function() {
101204         var me = this,
101205             grid = me.grid,
101206             view = me.view,
101207             headerCt = grid.headerCt,
101208             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
101209
101210         me.callParent(arguments);
101211
101212         if (me.clicksToMoveEditor !== me.clicksToEdit) {
101213             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
101214         }
101215
101216         view.on('render', function() {
101217             // Column events
101218             me.mon(headerCt, {
101219                 add: me.onColumnAdd,
101220                 remove: me.onColumnRemove,
101221                 columnresize: me.onColumnResize,
101222                 columnhide: me.onColumnHide,
101223                 columnshow: me.onColumnShow,
101224                 columnmove: me.onColumnMove,
101225                 scope: me
101226             });
101227         }, me, { single: true });
101228     },
101229
101230     startEditByClick: function() {
101231         var me = this;
101232         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
101233             me.callParent(arguments);
101234         }
101235     },
101236
101237     moveEditorByClick: function() {
101238         var me = this;
101239         if (me.editing) {
101240             me.superclass.startEditByClick.apply(me, arguments);
101241         }
101242     },
101243
101244     // private
101245     onColumnAdd: function(ct, column) {
101246         if (column.isHeader) {
101247             var me = this,
101248                 editor;
101249
101250             me.initFieldAccessors(column);
101251             editor = me.getEditor();
101252
101253             if (editor && editor.onColumnAdd) {
101254                 editor.onColumnAdd(column);
101255             }
101256         }
101257     },
101258
101259     // private
101260     onColumnRemove: function(ct, column) {
101261         if (column.isHeader) {
101262             var me = this,
101263                 editor = me.getEditor();
101264
101265             if (editor && editor.onColumnRemove) {
101266                 editor.onColumnRemove(column);
101267             }
101268             me.removeFieldAccessors(column);
101269         }
101270     },
101271
101272     // private
101273     onColumnResize: function(ct, column, width) {
101274         if (column.isHeader) {
101275             var me = this,
101276                 editor = me.getEditor();
101277
101278             if (editor && editor.onColumnResize) {
101279                 editor.onColumnResize(column, width);
101280             }
101281         }
101282     },
101283
101284     // private
101285     onColumnHide: function(ct, column) {
101286         // no isHeader check here since its already a columnhide event.
101287         var me = this,
101288             editor = me.getEditor();
101289
101290         if (editor && editor.onColumnHide) {
101291             editor.onColumnHide(column);
101292         }
101293     },
101294
101295     // private
101296     onColumnShow: function(ct, column) {
101297         // no isHeader check here since its already a columnshow event.
101298         var me = this,
101299             editor = me.getEditor();
101300
101301         if (editor && editor.onColumnShow) {
101302             editor.onColumnShow(column);
101303         }
101304     },
101305
101306     // private
101307     onColumnMove: function(ct, column, fromIdx, toIdx) {
101308         // no isHeader check here since its already a columnmove event.
101309         var me = this,
101310             editor = me.getEditor();
101311
101312         if (editor && editor.onColumnMove) {
101313             editor.onColumnMove(column, fromIdx, toIdx);
101314         }
101315     },
101316
101317     // private
101318     setColumnField: function(column, field) {
101319         var me = this;
101320         me.callParent(arguments);
101321         me.getEditor().setField(column.field, column);
101322     }
101323 });
101324
101325 /**
101326  * @class Ext.grid.property.Grid
101327  * @extends Ext.grid.Panel
101328  *
101329  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
101330  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
101331  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
101332  *
101333  *     @example
101334  *     Ext.create('Ext.grid.property.Grid', {
101335  *         title: 'Properties Grid',
101336  *         width: 300,
101337  *         renderTo: Ext.getBody(),
101338  *         source: {
101339  *             "(name)": "My Object",
101340  *             "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
101341  *             "Available": false,
101342  *             "Version": .01,
101343  *             "Description": "A test object"
101344  *         }
101345  *     });
101346  */
101347 Ext.define('Ext.grid.property.Grid', {
101348
101349     extend: 'Ext.grid.Panel',
101350
101351     alias: 'widget.propertygrid',
101352
101353     alternateClassName: 'Ext.grid.PropertyGrid',
101354
101355     uses: [
101356        'Ext.grid.plugin.CellEditing',
101357        'Ext.grid.property.Store',
101358        'Ext.grid.property.HeaderContainer',
101359        'Ext.XTemplate',
101360        'Ext.grid.CellEditor',
101361        'Ext.form.field.Date',
101362        'Ext.form.field.Text',
101363        'Ext.form.field.Number'
101364     ],
101365
101366    /**
101367     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
101368     * If specified, the display name will be shown in the name column instead of the property name.
101369     */
101370
101371     /**
101372     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
101373     */
101374
101375     /**
101376     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
101377     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
101378     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
101379     * associated with a custom input control by specifying a custom editor.  The name of the editor
101380     * type should correspond with the name of the property that will use the editor.  Example usage:
101381     * <pre><code>
101382 var grid = new Ext.grid.property.Grid({
101383
101384     // Custom editors for certain property names
101385     customEditors: {
101386         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
101387     },
101388
101389     // Displayed name for property names in the source
101390     propertyNames: {
101391         evtStart: 'Start Time'
101392     },
101393
101394     // Data object containing properties to edit
101395     source: {
101396         evtStart: '10:00 AM'
101397     }
101398 });
101399 </code></pre>
101400     */
101401
101402     /**
101403     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
101404     */
101405
101406     /**
101407     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
101408     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
101409     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
101410     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
101411     * that it will render.  Example usage:
101412     * <pre><code>
101413 var grid = Ext.create('Ext.grid.property.Grid', {
101414     customRenderers: {
101415         Available: function(v){
101416             if (v) {
101417                 return '<span style="color: green;">Yes</span>';
101418             } else {
101419                 return '<span style="color: red;">No</span>';
101420             }
101421         }
101422     },
101423     source: {
101424         Available: true
101425     }
101426 });
101427 </code></pre>
101428     */
101429
101430     /**
101431      * @cfg {String} valueField
101432      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
101433      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
101434      */
101435     valueField: 'value',
101436
101437     /**
101438      * @cfg {String} nameField
101439      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
101440      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
101441      */
101442     nameField: 'name',
101443
101444     /**
101445      * @cfg {Number} nameColumnWidth
101446      * Optional. Specify the width for the name column. The value column will take any remaining space. Defaults to <tt>115</tt>.
101447      */
101448
101449     // private config overrides
101450     enableColumnMove: false,
101451     columnLines: true,
101452     stripeRows: false,
101453     trackMouseOver: false,
101454     clicksToEdit: 1,
101455     enableHdMenu: false,
101456
101457     // private
101458     initComponent : function(){
101459         var me = this;
101460
101461         me.addCls(Ext.baseCSSPrefix + 'property-grid');
101462         me.plugins = me.plugins || [];
101463
101464         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
101465         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
101466             clicksToEdit: me.clicksToEdit,
101467
101468             // Inject a startEdit which always edits the value column
101469             startEdit: function(record, column) {
101470                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
101471                 return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
101472             }
101473         }));
101474
101475         me.selModel = {
101476             selType: 'cellmodel',
101477             onCellSelect: function(position) {
101478                 if (position.column != 1) {
101479                     position.column = 1;
101480                 }
101481                 return this.self.prototype.onCellSelect.call(this, position);
101482             }
101483         };
101484         me.customRenderers = me.customRenderers || {};
101485         me.customEditors = me.customEditors || {};
101486
101487         // Create a property.Store from the source object unless configured with a store
101488         if (!me.store) {
101489             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
101490         }
101491
101492         me.store.sort('name', 'ASC');
101493         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
101494
101495         me.addEvents(
101496             /**
101497              * @event beforepropertychange
101498              * Fires before a property value changes.  Handlers can return false to cancel the property change
101499              * (this will internally call {@link Ext.data.Model#reject} on the property's record).
101500              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
101501              * as the {@link #source} config property).
101502              * @param {String} recordId The record's id in the data store
101503              * @param {Object} value The current edited property value
101504              * @param {Object} oldValue The original property value prior to editing
101505              */
101506             'beforepropertychange',
101507             /**
101508              * @event propertychange
101509              * Fires after a property value has changed.
101510              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
101511              * as the {@link #source} config property).
101512              * @param {String} recordId The record's id in the data store
101513              * @param {Object} value The current edited property value
101514              * @param {Object} oldValue The original property value prior to editing
101515              */
101516             'propertychange'
101517         );
101518         me.callParent();
101519
101520         // Inject a custom implementation of walkCells which only goes up or down
101521         me.getView().walkCells = this.walkCells;
101522
101523         // Set up our default editor set for the 4 atomic data types
101524         me.editors = {
101525             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
101526             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
101527             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
101528             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
101529                 editable: false,
101530                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
101531             })})
101532         };
101533
101534         // Track changes to the data so we can fire our events.
101535         me.store.on('update', me.onUpdate, me);
101536     },
101537
101538     // private
101539     onUpdate : function(store, record, operation) {
101540         var me = this,
101541             v, oldValue;
101542
101543         if (operation == Ext.data.Model.EDIT) {
101544             v = record.get(me.valueField);
101545             oldValue = record.modified.value;
101546             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
101547                 if (me.source) {
101548                     me.source[record.getId()] = v;
101549                 }
101550                 record.commit();
101551                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
101552             } else {
101553                 record.reject();
101554             }
101555         }
101556     },
101557
101558     // Custom implementation of walkCells which only goes up and down.
101559     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
101560         if (direction == 'left') {
101561             direction = 'up';
101562         } else if (direction == 'right') {
101563             direction = 'down';
101564         }
101565         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
101566         if (!pos.column) {
101567             pos.column = 1;
101568         }
101569         return pos;
101570     },
101571
101572     // private
101573     // returns the correct editor type for the property type, or a custom one keyed by the property name
101574     getCellEditor : function(record, column) {
101575         var me = this,
101576             propName = record.get(me.nameField),
101577             val = record.get(me.valueField),
101578             editor = me.customEditors[propName];
101579
101580         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
101581         // If it's not even a Field, just a config object, instantiate it before wrapping it.
101582         if (editor) {
101583             if (!(editor instanceof Ext.grid.CellEditor)) {
101584                 if (!(editor instanceof Ext.form.field.Base)) {
101585                     editor = Ext.ComponentManager.create(editor, 'textfield');
101586                 }
101587                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
101588             }
101589         } else if (Ext.isDate(val)) {
101590             editor = me.editors.date;
101591         } else if (Ext.isNumber(val)) {
101592             editor = me.editors.number;
101593         } else if (Ext.isBoolean(val)) {
101594             editor = me.editors['boolean'];
101595         } else {
101596             editor = me.editors.string;
101597         }
101598
101599         // Give the editor a unique ID because the CellEditing plugin caches them
101600         editor.editorId = propName;
101601         return editor;
101602     },
101603
101604     beforeDestroy: function() {
101605         var me = this;
101606         me.callParent();
101607         me.destroyEditors(me.editors);
101608         me.destroyEditors(me.customEditors);
101609         delete me.source;
101610     },
101611
101612     destroyEditors: function (editors) {
101613         for (var ed in editors) {
101614             if (editors.hasOwnProperty(ed)) {
101615                 Ext.destroy(editors[ed]);
101616             }
101617         }
101618     },
101619
101620     /**
101621      * Sets the source data object containing the property data.  The data object can contain one or more name/value
101622      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
101623      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
101624      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
101625      * existing data.  See also the {@link #source} config value.  Example usage:
101626      * <pre><code>
101627 grid.setSource({
101628     "(name)": "My Object",
101629     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
101630     "Available": false,  // boolean type
101631     "Version": .01,      // decimal type
101632     "Description": "A test object"
101633 });
101634 </code></pre>
101635      * @param {Object} source The data object
101636      */
101637     setSource: function(source) {
101638         this.source = source;
101639         this.propStore.setSource(source);
101640     },
101641
101642     /**
101643      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
101644      * format of the data object.
101645      * @return {Object} The data object
101646      */
101647     getSource: function() {
101648         return this.propStore.getSource();
101649     },
101650
101651     /**
101652      * Sets the value of a property.
101653      * @param {String} prop The name of the property to set
101654      * @param {Object} value The value to test
101655      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
101656      */
101657     setProperty: function(prop, value, create) {
101658         this.propStore.setValue(prop, value, create);
101659     },
101660
101661     /**
101662      * Removes a property from the grid.
101663      * @param {String} prop The name of the property to remove
101664      */
101665     removeProperty: function(prop) {
101666         this.propStore.remove(prop);
101667     }
101668
101669     /**
101670      * @cfg store
101671      * @hide
101672      */
101673     /**
101674      * @cfg colModel
101675      * @hide
101676      */
101677     /**
101678      * @cfg cm
101679      * @hide
101680      */
101681     /**
101682      * @cfg columns
101683      * @hide
101684      */
101685 });
101686 /**
101687  * @class Ext.grid.property.HeaderContainer
101688  * @extends Ext.grid.header.Container
101689  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
101690  */
101691 Ext.define('Ext.grid.property.HeaderContainer', {
101692
101693     extend: 'Ext.grid.header.Container',
101694
101695     alternateClassName: 'Ext.grid.PropertyColumnModel',
101696     
101697     nameWidth: 115,
101698
101699     // private - strings used for locale support
101700     nameText : 'Name',
101701     valueText : 'Value',
101702     dateFormat : 'm/j/Y',
101703     trueText: 'true',
101704     falseText: 'false',
101705
101706     // private
101707     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
101708
101709     /**
101710      * Creates new HeaderContainer.
101711      * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
101712      * @param {Object} source The source data config object
101713      */
101714     constructor : function(grid, store) {
101715         var me = this;
101716         
101717         me.grid = grid;
101718         me.store = store;
101719         me.callParent([{
101720             items: [{
101721                 header: me.nameText,
101722                 width: grid.nameColumnWidth || me.nameWidth,
101723                 sortable: true,
101724                 dataIndex: grid.nameField,
101725                 renderer: Ext.Function.bind(me.renderProp, me),
101726                 itemId: grid.nameField,
101727                 menuDisabled :true,
101728                 tdCls: me.nameColumnCls
101729             }, {
101730                 header: me.valueText,
101731                 renderer: Ext.Function.bind(me.renderCell, me),
101732                 getEditor: Ext.Function.bind(me.getCellEditor, me),
101733                 flex: 1,
101734                 fixed: true,
101735                 dataIndex: grid.valueField,
101736                 itemId: grid.valueField,
101737                 menuDisabled: true
101738             }]
101739         }]);
101740     },
101741     
101742     getCellEditor: function(record){
101743         return this.grid.getCellEditor(record, this);
101744     },
101745
101746     // private
101747     // Render a property name cell
101748     renderProp : function(v) {
101749         return this.getPropertyName(v);
101750     },
101751
101752     // private
101753     // Render a property value cell
101754     renderCell : function(val, meta, rec) {
101755         var me = this,
101756             renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
101757             result = val;
101758
101759         if (renderer) {
101760             return renderer.apply(me, arguments);
101761         }
101762         if (Ext.isDate(val)) {
101763             result = me.renderDate(val);
101764         } else if (Ext.isBoolean(val)) {
101765             result = me.renderBool(val);
101766         }
101767         return Ext.util.Format.htmlEncode(result);
101768     },
101769
101770     // private
101771     renderDate : Ext.util.Format.date,
101772
101773     // private
101774     renderBool : function(bVal) {
101775         return this[bVal ? 'trueText' : 'falseText'];
101776     },
101777
101778     // private
101779     // Renders custom property names instead of raw names if defined in the Grid
101780     getPropertyName : function(name) {
101781         var pn = this.grid.propertyNames;
101782         return pn && pn[name] ? pn[name] : name;
101783     }
101784 });
101785 /**
101786  * @class Ext.grid.property.Property
101787  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
101788  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
101789  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
101790  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
101791  * can also be created explicitly as shown below.  Example usage:
101792  * <pre><code>
101793 var rec = new Ext.grid.property.Property({
101794     name: 'birthday',
101795     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
101796 });
101797 // Add record to an already populated grid
101798 grid.store.addSorted(rec);
101799 </code></pre>
101800  * @constructor
101801  * @param {Object} config A data object in the format:<pre><code>
101802 {
101803     name: [name],
101804     value: [value]
101805 }</code></pre>
101806  * The specified value's type
101807  * will be read automatically by the grid to determine the type of editor to use when displaying it.
101808  */
101809 Ext.define('Ext.grid.property.Property', {
101810     extend: 'Ext.data.Model',
101811
101812     alternateClassName: 'Ext.PropGridProperty',
101813
101814     fields: [{
101815         name: 'name',
101816         type: 'string'
101817     }, {
101818         name: 'value'
101819     }],
101820     idProperty: 'name'
101821 });
101822 /**
101823  * @class Ext.grid.property.Store
101824  * @extends Ext.data.Store
101825  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
101826  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
101827  * used by the {@link Ext.data.Store} base class.
101828  */
101829 Ext.define('Ext.grid.property.Store', {
101830
101831     extend: 'Ext.data.Store',
101832
101833     alternateClassName: 'Ext.grid.PropertyStore',
101834
101835     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
101836
101837     /**
101838      * Creates new property store.
101839      * @param {Ext.grid.Panel} grid The grid this store will be bound to
101840      * @param {Object} source The source data config object
101841      */
101842     constructor : function(grid, source){
101843         var me = this;
101844         
101845         me.grid = grid;
101846         me.source = source;
101847         me.callParent([{
101848             data: source,
101849             model: Ext.grid.property.Property,
101850             proxy: me.getProxy()
101851         }]);
101852     },
101853
101854     // Return a singleton, customized Proxy object which configures itself with a custom Reader
101855     getProxy: function() {
101856         if (!this.proxy) {
101857             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
101858                 model: Ext.grid.property.Property,
101859                 reader: this.getReader()
101860             });
101861         }
101862         return this.proxy;
101863     },
101864
101865     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
101866     getReader: function() {
101867         if (!this.reader) {
101868             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
101869                 model: Ext.grid.property.Property,
101870
101871                 buildExtractors: Ext.emptyFn,
101872
101873                 read: function(dataObject) {
101874                     return this.readRecords(dataObject);
101875                 },
101876
101877                 readRecords: function(dataObject) {
101878                     var val,
101879                         propName,
101880                         result = {
101881                             records: [],
101882                             success: true
101883                         };
101884
101885                     for (propName in dataObject) {
101886                         if (dataObject.hasOwnProperty(propName)) {
101887                             val = dataObject[propName];
101888                             if (this.isEditableValue(val)) {
101889                                 result.records.push(new Ext.grid.property.Property({
101890                                     name: propName,
101891                                     value: val
101892                                 }, propName));
101893                             }
101894                         }
101895                     }
101896                     result.total = result.count = result.records.length;
101897                     return Ext.create('Ext.data.ResultSet', result);
101898                 },
101899
101900                 // private
101901                 isEditableValue: function(val){
101902                     return Ext.isPrimitive(val) || Ext.isDate(val);
101903                 }
101904             });
101905         }
101906         return this.reader;
101907     },
101908
101909     // protected - should only be called by the grid.  Use grid.setSource instead.
101910     setSource : function(dataObject) {
101911         var me = this;
101912
101913         me.source = dataObject;
101914         me.suspendEvents();
101915         me.removeAll();
101916         me.proxy.data = dataObject;
101917         me.load();
101918         me.resumeEvents();
101919         me.fireEvent('datachanged', me);
101920     },
101921
101922     // private
101923     getProperty : function(row) {
101924        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
101925     },
101926
101927     // private
101928     setValue : function(prop, value, create){
101929         var me = this,
101930             rec = me.getRec(prop);
101931             
101932         if (rec) {
101933             rec.set('value', value);
101934             me.source[prop] = value;
101935         } else if (create) {
101936             // only create if specified.
101937             me.source[prop] = value;
101938             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
101939             me.add(rec);
101940         }
101941     },
101942
101943     // private
101944     remove : function(prop) {
101945         var rec = this.getRec(prop);
101946         if (rec) {
101947             this.callParent([rec]);
101948             delete this.source[prop];
101949         }
101950     },
101951
101952     // private
101953     getRec : function(prop) {
101954         return this.getById(prop);
101955     },
101956
101957     // protected - should only be called by the grid.  Use grid.getSource instead.
101958     getSource : function() {
101959         return this.source;
101960     }
101961 });
101962 /**
101963  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
101964  * Component size.
101965  * @class Ext.layout.component.Body
101966  * @extends Ext.layout.component.Component
101967  * @private
101968  */
101969
101970 Ext.define('Ext.layout.component.Body', {
101971
101972     /* Begin Definitions */
101973
101974     alias: ['layout.body'],
101975
101976     extend: 'Ext.layout.component.Component',
101977
101978     uses: ['Ext.layout.container.Container'],
101979
101980     /* End Definitions */
101981
101982     type: 'body',
101983     
101984     onLayout: function(width, height) {
101985         var me = this,
101986             owner = me.owner;
101987
101988         // Size the Component's encapsulating element according to the dimensions
101989         me.setTargetSize(width, height);
101990
101991         // Size the Component's body element according to the content box of the encapsulating element
101992         me.setBodySize.apply(me, arguments);
101993
101994         // We need to bind to the owner whenever we do not have a user set height or width.
101995         if (owner && owner.layout && owner.layout.isLayout) {
101996             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
101997                 owner.layout.bindToOwnerCtComponent = true;
101998             }
101999             else {
102000                 owner.layout.bindToOwnerCtComponent = false;
102001             }
102002         }
102003         
102004         me.callParent(arguments);
102005     },
102006
102007     /**
102008      * @private
102009      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
102010      */
102011     setBodySize: function(width, height) {
102012         var me = this,
102013             owner = me.owner,
102014             frameSize = owner.frameSize,
102015             isNumber = Ext.isNumber;
102016
102017         if (isNumber(width)) {
102018             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
102019         }
102020         if (isNumber(height)) {
102021             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
102022         }
102023
102024         me.setElementSize(owner.body, width, height);
102025     }
102026 });
102027 /**
102028  * Component layout for Ext.form.FieldSet components
102029  * @class Ext.layout.component.FieldSet
102030  * @extends Ext.layout.component.Body
102031  * @private
102032  */
102033 Ext.define('Ext.layout.component.FieldSet', {
102034     extend: 'Ext.layout.component.Body',
102035     alias: ['layout.fieldset'],
102036
102037     type: 'fieldset',
102038
102039     doContainerLayout: function() {
102040         // Prevent layout/rendering of children if the fieldset is collapsed
102041         if (!this.owner.collapsed) {
102042             this.callParent();
102043         }
102044     }
102045 });
102046 /**
102047  * Component layout for tabs
102048  * @class Ext.layout.component.Tab
102049  * @extends Ext.layout.component.Button
102050  * @private
102051  */
102052 Ext.define('Ext.layout.component.Tab', {
102053
102054     alias: ['layout.tab'],
102055
102056     extend: 'Ext.layout.component.Button',
102057
102058     //type: 'button',
102059
102060     beforeLayout: function() {
102061         var me = this, dirty = me.lastClosable !== me.owner.closable;
102062
102063         if (dirty) {
102064             delete me.adjWidth;
102065         }
102066
102067         return this.callParent(arguments) || dirty;
102068     },
102069
102070     onLayout: function () {
102071         var me = this;
102072
102073         me.callParent(arguments);
102074
102075         me.lastClosable = me.owner.closable;
102076     }
102077 });
102078 /**
102079  * @private
102080  * @class Ext.layout.component.field.File
102081  * @extends Ext.layout.component.field.Field
102082  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
102083  * the file picker trigger button.
102084  * @private
102085  */
102086
102087 Ext.define('Ext.layout.component.field.File', {
102088     alias: ['layout.filefield'],
102089     extend: 'Ext.layout.component.field.Field',
102090
102091     type: 'filefield',
102092
102093     sizeBodyContents: function(width, height) {
102094         var me = this,
102095             owner = me.owner;
102096
102097         if (!owner.buttonOnly) {
102098             // Decrease the field's width by the width of the button and the configured buttonMargin.
102099             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
102100             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
102101         }
102102     }
102103 });
102104 /**
102105  * @class Ext.layout.component.field.Slider
102106  * @extends Ext.layout.component.field.Field
102107  * @private
102108  */
102109
102110 Ext.define('Ext.layout.component.field.Slider', {
102111
102112     /* Begin Definitions */
102113
102114     alias: ['layout.sliderfield'],
102115
102116     extend: 'Ext.layout.component.field.Field',
102117
102118     /* End Definitions */
102119
102120     type: 'sliderfield',
102121
102122     sizeBodyContents: function(width, height) {
102123         var owner = this.owner,
102124             thumbs = owner.thumbs,
102125             length = thumbs.length,
102126             inputEl = owner.inputEl,
102127             innerEl = owner.innerEl,
102128             endEl = owner.endEl,
102129             i = 0;
102130
102131         /*
102132          * If we happen to be animating during a resize, the position of the thumb will likely be off
102133          * when the animation stops. As such, just stop any animations before syncing the thumbs.
102134          */
102135         for(; i < length; ++i) {
102136             thumbs[i].el.stopAnimation();
102137         }
102138         
102139         if (owner.vertical) {
102140             inputEl.setHeight(height);
102141             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
102142         }
102143         else {
102144             inputEl.setWidth(width);
102145             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
102146         }
102147         owner.syncThumbs();
102148     }
102149 });
102150
102151 /**
102152  * @class Ext.layout.container.Absolute
102153  * @extends Ext.layout.container.Anchor
102154  *
102155  * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
102156  * ability for x/y positioning using the standard x and y component config options.
102157  *
102158  * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
102159  * configuration property.  See {@link Ext.container.Container#layout} for additional details.
102160  *
102161  *     @example
102162  *     Ext.create('Ext.form.Panel', {
102163  *         title: 'Absolute Layout',
102164  *         width: 300,
102165  *         height: 275,
102166  *         layout:'absolute',
102167  *         layoutConfig: {
102168  *             // layout-specific configs go here
102169  *             //itemCls: 'x-abs-layout-item',
102170  *         },
102171  *         url:'save-form.php',
102172  *         defaultType: 'textfield',
102173  *         items: [{
102174  *             x: 10,
102175  *             y: 10,
102176  *             xtype:'label',
102177  *             text: 'Send To:'
102178  *         },{
102179  *             x: 80,
102180  *             y: 10,
102181  *             name: 'to',
102182  *             anchor:'90%'  // anchor width by percentage
102183  *         },{
102184  *             x: 10,
102185  *             y: 40,
102186  *             xtype:'label',
102187  *             text: 'Subject:'
102188  *         },{
102189  *             x: 80,
102190  *             y: 40,
102191  *             name: 'subject',
102192  *             anchor: '90%'  // anchor width by percentage
102193  *         },{
102194  *             x:0,
102195  *             y: 80,
102196  *             xtype: 'textareafield',
102197  *             name: 'msg',
102198  *             anchor: '100% 100%'  // anchor width and height
102199  *         }],
102200  *         renderTo: Ext.getBody()
102201  *     });
102202  */
102203 Ext.define('Ext.layout.container.Absolute', {
102204
102205     /* Begin Definitions */
102206
102207     alias: 'layout.absolute',
102208     extend: 'Ext.layout.container.Anchor',
102209     alternateClassName: 'Ext.layout.AbsoluteLayout',
102210
102211     /* End Definitions */
102212
102213     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
102214
102215     type: 'absolute',
102216
102217     onLayout: function() {
102218         var me = this,
102219             target = me.getTarget(),
102220             targetIsBody = target.dom === document.body;
102221
102222         // Do not set position: relative; when the absolute layout target is the body
102223         if (!targetIsBody) {
102224             target.position();
102225         }
102226         me.paddingLeft = target.getPadding('l');
102227         me.paddingTop = target.getPadding('t');
102228         me.callParent(arguments);
102229     },
102230
102231     // private
102232     adjustWidthAnchor: function(value, comp) {
102233         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
102234         return value ? value - comp.getPosition(true)[0] : value;
102235     },
102236
102237     // private
102238     adjustHeightAnchor: function(value, comp) {
102239         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
102240         return value ? value - comp.getPosition(true)[1] : value;
102241     }
102242 });
102243 /**
102244  * @class Ext.layout.container.Accordion
102245  * @extends Ext.layout.container.VBox
102246  *
102247  * This is a layout that manages multiple Panels in an expandable accordion style such that only
102248  * **one Panel can be expanded at any given time**. Each Panel has built-in support for expanding and collapsing.
102249  *
102250  * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
102251  *
102252  *     @example
102253  *     Ext.create('Ext.panel.Panel', {
102254  *         title: 'Accordion Layout',
102255  *         width: 300,
102256  *         height: 300,
102257  *         layout:'accordion',
102258  *         defaults: {
102259  *             // applied to each contained panel
102260  *             bodyStyle: 'padding:15px'
102261  *         },
102262  *         layoutConfig: {
102263  *             // layout-specific configs go here
102264  *             titleCollapse: false,
102265  *             animate: true,
102266  *             activeOnTop: true
102267  *         },
102268  *         items: [{
102269  *             title: 'Panel 1',
102270  *             html: 'Panel content!'
102271  *         },{
102272  *             title: 'Panel 2',
102273  *             html: 'Panel content!'
102274  *         },{
102275  *             title: 'Panel 3',
102276  *             html: 'Panel content!'
102277  *         }],
102278  *         renderTo: Ext.getBody()
102279  *     });
102280  */
102281 Ext.define('Ext.layout.container.Accordion', {
102282     extend: 'Ext.layout.container.VBox',
102283     alias: ['layout.accordion'],
102284     alternateClassName: 'Ext.layout.AccordionLayout',
102285
102286     itemCls: Ext.baseCSSPrefix + 'box-item ' + Ext.baseCSSPrefix + 'accordion-item',
102287
102288     align: 'stretch',
102289
102290     /**
102291      * @cfg {Boolean} fill
102292      * True to adjust the active item's height to fill the available space in the container, false to use the
102293      * item's current height, or auto height if not explicitly set.
102294      */
102295     fill : true,
102296
102297     /**
102298      * @cfg {Boolean} autoWidth
102299      * Child Panels have their width actively managed to fit within the accordion's width.
102300      * @deprecated This config is ignored in ExtJS 4
102301      */
102302     autoWidth : true,
102303
102304     /**
102305      * @cfg {Boolean} titleCollapse
102306      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
102307      * expand/collapse only when the toggle tool button is clicked.  When set to false,
102308      * {@link #hideCollapseTool} should be false also.
102309      */
102310     titleCollapse : true,
102311
102312     /**
102313      * @cfg {Boolean} hideCollapseTool
102314      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
102315      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
102316      */
102317     hideCollapseTool : false,
102318
102319     /**
102320      * @cfg {Boolean} collapseFirst
102321      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
102322      * in the contained Panels' title bars, false to render it last.
102323      */
102324     collapseFirst : false,
102325
102326     /**
102327      * @cfg {Boolean} animate
102328      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
102329      * close directly with no animation. Note: The layout performs animated collapsing
102330      * and expanding, <i>not</i> the child Panels.
102331      */
102332     animate : true,
102333     /**
102334      * @cfg {Boolean} activeOnTop
102335      * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
102336      *
102337      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
102338      * false to keep the panels in the rendered order.
102339      */
102340     activeOnTop : false,
102341     /**
102342      * @cfg {Boolean} multi
102343      * Set to <code>true</code> to enable multiple accordion items to be open at once.
102344      */
102345     multi: false,
102346
102347     constructor: function() {
102348         var me = this;
102349
102350         me.callParent(arguments);
102351
102352         // animate flag must be false during initial render phase so we don't get animations.
102353         me.initialAnimate = me.animate;
102354         me.animate = false;
102355
102356         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
102357         if (me.fill === false) {
102358             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
102359         }
102360     },
102361
102362     // Cannot lay out a fitting accordion before we have been allocated a height.
102363     // So during render phase, layout will not be performed.
102364     beforeLayout: function() {
102365         var me = this;
102366
102367         me.callParent(arguments);
102368         if (me.fill) {
102369             if (!(me.owner.el.dom.style.height || me.getLayoutTargetSize().height)) {
102370                 return false;
102371             }
102372         } else {
102373             me.owner.componentLayout.monitorChildren = false;
102374             me.autoSize = true;
102375             me.owner.setAutoScroll(true);
102376         }
102377     },
102378
102379     renderItems : function(items, target) {
102380         var me = this,
102381             ln = items.length,
102382             i = 0,
102383             comp,
102384             targetSize = me.getLayoutTargetSize(),
102385             renderedPanels = [];
102386
102387         for (; i < ln; i++) {
102388             comp = items[i];
102389             if (!comp.rendered) {
102390                 renderedPanels.push(comp);
102391
102392                 // Set up initial properties for Panels in an accordion.
102393                 if (me.collapseFirst) {
102394                     comp.collapseFirst = me.collapseFirst;
102395                 }
102396                 if (me.hideCollapseTool) {
102397                     comp.hideCollapseTool = me.hideCollapseTool;
102398                     comp.titleCollapse = true;
102399                 }
102400                 else if (me.titleCollapse) {
102401                     comp.titleCollapse = me.titleCollapse;
102402                 }
102403
102404                 delete comp.hideHeader;
102405                 comp.collapsible = true;
102406                 comp.title = comp.title || '&#160;';
102407
102408                 // Set initial sizes
102409                 comp.width = targetSize.width;
102410                 if (me.fill) {
102411                     delete comp.height;
102412                     delete comp.flex;
102413
102414                     // If there is an expanded item, all others must be rendered collapsed.
102415                     if (me.expandedItem !== undefined) {
102416                         comp.collapsed = true;
102417                     }
102418                     // Otherwise expand the first item with collapsed explicitly configured as false
102419                     else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
102420                         comp.flex = 1;
102421                         me.expandedItem = i;
102422                     } else {
102423                         comp.collapsed = true;
102424                     }
102425                     // If we are fitting, then intercept expand/collapse requests.
102426                     me.owner.mon(comp, {
102427                         show: me.onComponentShow,
102428                         beforeexpand: me.onComponentExpand,
102429                         beforecollapse: me.onComponentCollapse,
102430                         scope: me
102431                     });
102432                 } else {
102433                     delete comp.flex;
102434                     comp.animCollapse = me.initialAnimate;
102435                     comp.autoHeight = true;
102436                     comp.autoScroll = false;
102437                 }
102438                 comp.border = comp.collapsed;
102439             }
102440         }
102441
102442         // If no collapsed:false Panels found, make the first one expanded.
102443         if (ln && me.expandedItem === undefined) {
102444             me.expandedItem = 0;
102445             comp = items[0];
102446             comp.collapsed = comp.border = false;
102447             if (me.fill) {
102448                 comp.flex = 1;
102449             }
102450         }
102451
102452         // Render all Panels.
102453         me.callParent(arguments);
102454
102455         // Postprocess rendered Panels.
102456         ln = renderedPanels.length;
102457         for (i = 0; i < ln; i++) {
102458             comp = renderedPanels[i];
102459
102460             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
102461             delete comp.width;
102462
102463             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
102464             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
102465         }
102466     },
102467
102468     onLayout: function() {
102469         var me = this;
102470
102471
102472         if (me.fill) {
102473             me.callParent(arguments);
102474         } else {
102475             var targetSize = me.getLayoutTargetSize(),
102476                 items = me.getVisibleItems(),
102477                 len = items.length,
102478                 i = 0, comp;
102479
102480             for (; i < len; i++) {
102481                 comp = items[i];
102482                 if (comp.collapsed) {
102483                     items[i].setWidth(targetSize.width);
102484                 } else {
102485                     items[i].setSize(null, null);
102486                 }
102487             }
102488         }
102489         me.updatePanelClasses();
102490
102491         return me;
102492     },
102493
102494     updatePanelClasses: function() {
102495         var children = this.getLayoutItems(),
102496             ln = children.length,
102497             siblingCollapsed = true,
102498             i, child;
102499
102500         for (i = 0; i < ln; i++) {
102501             child = children[i];
102502
102503             // Fix for EXTJSIV-3724. Windows only.
102504             // Collapsing the Psnel's el to a size which only allows a single hesder to be visible, scrolls the header out of view.
102505             if (Ext.isWindows) {
102506                 child.el.dom.scrollTop = 0;
102507             }
102508
102509             if (siblingCollapsed) {
102510                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
102511             }
102512             else {
102513                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
102514             }
102515
102516             if (i + 1 == ln && child.collapsed) {
102517                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
102518             }
102519             else {
102520                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
102521             }
102522             siblingCollapsed = child.collapsed;
102523         }
102524     },
102525     
102526     animCallback: function(){
102527         Ext.Array.forEach(this.toCollapse, function(comp){
102528             comp.fireEvent('collapse', comp);
102529         });
102530         
102531         Ext.Array.forEach(this.toExpand, function(comp){
102532             comp.fireEvent('expand', comp);
102533         });    
102534     },
102535     
102536     setupEvents: function(){
102537         this.toCollapse = [];
102538         this.toExpand = [];    
102539     },
102540
102541     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
102542     // their headers.
102543     // The expanded Component receives the only flex value, and so gets all remaining space.
102544     onComponentExpand: function(toExpand) {
102545         var me = this,
102546             it = me.owner.items.items,
102547             len = it.length,
102548             i = 0,
102549             comp;
102550
102551         me.setupEvents();
102552         for (; i < len; i++) {
102553             comp = it[i];
102554             if (comp === toExpand && comp.collapsed) {
102555                 me.setExpanded(comp);
102556             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
102557                 me.setCollapsed(comp);
102558             }
102559         }
102560
102561         me.animate = me.initialAnimate;
102562         if (me.activeOnTop) {
102563             // insert will trigger a layout
102564             me.owner.insert(0, toExpand); 
102565         } else {
102566             me.layout();
102567         }
102568         me.animate = false;
102569         return false;
102570     },
102571
102572     onComponentCollapse: function(comp) {
102573         var me = this,
102574             toExpand = comp.next() || comp.prev(),
102575             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
102576
102577         me.setupEvents();
102578         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
102579         // then ask the box layout to collapse it to its header.
102580         if (me.multi) {
102581             me.setCollapsed(comp);
102582
102583             // If the collapsing Panel is the only expanded one, expand the following Component.
102584             // All this is handling fill: true, so there must be at least one expanded,
102585             if (expanded.length === 1 && expanded[0] === comp) {
102586                 me.setExpanded(toExpand);
102587             }
102588
102589             me.animate = me.initialAnimate;
102590             me.layout();
102591             me.animate = false;
102592         }
102593         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
102594         else if (toExpand) {
102595             me.onComponentExpand(toExpand);
102596         }
102597         return false;
102598     },
102599
102600     onComponentShow: function(comp) {
102601         // Showing a Component means that you want to see it, so expand it.
102602         this.onComponentExpand(comp);
102603     },
102604
102605     setCollapsed: function(comp) {
102606         var otherDocks = comp.getDockedItems(),
102607             dockItem,
102608             len = otherDocks.length,
102609             i = 0;
102610
102611         // Hide all docked items except the header
102612         comp.hiddenDocked = [];
102613         for (; i < len; i++) {
102614             dockItem = otherDocks[i];
102615             if ((dockItem !== comp.header) && !dockItem.hidden) {
102616                 dockItem.hidden = true;
102617                 comp.hiddenDocked.push(dockItem);
102618             }
102619         }
102620         comp.addCls(comp.collapsedCls);
102621         comp.header.addCls(comp.collapsedHeaderCls);
102622         comp.height = comp.header.getHeight();
102623         comp.el.setHeight(comp.height);
102624         comp.collapsed = true;
102625         delete comp.flex;
102626         if (this.initialAnimate) {
102627             this.toCollapse.push(comp);
102628         } else {
102629             comp.fireEvent('collapse', comp);
102630         }
102631         if (comp.collapseTool) {
102632             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
102633         }
102634     },
102635
102636     setExpanded: function(comp) {
102637         var otherDocks = comp.hiddenDocked,
102638             len = otherDocks ? otherDocks.length : 0,
102639             i = 0;
102640
102641         // Show temporarily hidden docked items
102642         for (; i < len; i++) {
102643             otherDocks[i].show();
102644         }
102645
102646         // If it was an initial native collapse which hides the body
102647         if (!comp.body.isVisible()) {
102648             comp.body.show();
102649         }
102650         delete comp.collapsed;
102651         delete comp.height;
102652         delete comp.componentLayout.lastComponentSize;
102653         comp.suspendLayout = false;
102654         comp.flex = 1;
102655         comp.removeCls(comp.collapsedCls);
102656         comp.header.removeCls(comp.collapsedHeaderCls);
102657          if (this.initialAnimate) {
102658             this.toExpand.push(comp);
102659         } else {
102660             comp.fireEvent('expand', comp);
102661         }
102662         if (comp.collapseTool) {
102663             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
102664         }
102665         comp.setAutoScroll(comp.initialConfig.autoScroll);
102666     }
102667 });
102668 /**
102669  * This class functions between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
102670  * layout to resize both immediate siblings.
102671  *
102672  * By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
102673  * `{@link Ext.Component#maintainFlex maintainFlex}: true` which will cause it not to receive a new size explicitly, but to be resized
102674  * by the layout.
102675  *
102676  * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
102677  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
102678  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
102679  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.
102680  */
102681 Ext.define('Ext.resizer.Splitter', {
102682     extend: 'Ext.Component',
102683     requires: ['Ext.XTemplate'],
102684     uses: ['Ext.resizer.SplitterTracker'],
102685     alias: 'widget.splitter',
102686
102687     renderTpl: [
102688         '<tpl if="collapsible===true">',
102689             '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
102690                     Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&nbsp;</div>',
102691         '</tpl>'
102692     ],
102693
102694     baseCls: Ext.baseCSSPrefix + 'splitter',
102695     collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
102696
102697     /**
102698      * @cfg {Boolean} collapsible
102699      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
102700      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
102701      */
102702     collapsible: false,
102703
102704     /**
102705      * @cfg {Boolean} performCollapse
102706      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
102707      * state of the {@link #collapseTarget}.</p>
102708      */
102709
102710     /**
102711      * @cfg {Boolean} collapseOnDblClick
102712      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
102713      */
102714     collapseOnDblClick: true,
102715
102716     /**
102717      * @cfg {Number} defaultSplitMin
102718      * Provides a default minimum width or height for the two components
102719      * that the splitter is between.
102720      */
102721     defaultSplitMin: 40,
102722
102723     /**
102724      * @cfg {Number} defaultSplitMax
102725      * Provides a default maximum width or height for the two components
102726      * that the splitter is between.
102727      */
102728     defaultSplitMax: 1000,
102729
102730     /**
102731      * @cfg {String} collapsedCls
102732      * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
102733      */
102734
102735     width: 5,
102736     height: 5,
102737
102738     /**
102739      * @cfg {String/Ext.panel.Panel} collapseTarget
102740      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
102741      * <p>Or the immediate sibling Panel to collapse.</p>
102742      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
102743      * <p><b>Note that only Panels may be collapsed.</b></p>
102744      */
102745     collapseTarget: 'next',
102746
102747     /**
102748      * @property orientation
102749      * @type String
102750      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
102751      * when used in a vbox layout.
102752      */
102753
102754     onRender: function() {
102755         var me = this,
102756             target = me.getCollapseTarget(),
102757             collapseDir = me.getCollapseDirection();
102758
102759         Ext.applyIf(me.renderData, {
102760             collapseDir: collapseDir,
102761             collapsible: me.collapsible || target.collapsible
102762         });
102763
102764         me.addChildEls('collapseEl');
102765
102766         this.callParent(arguments);
102767
102768         // Add listeners on the mini-collapse tool unless performCollapse is set to false
102769         if (me.performCollapse !== false) {
102770             if (me.renderData.collapsible) {
102771                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
102772             }
102773             if (me.collapseOnDblClick) {
102774                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
102775             }
102776         }
102777
102778         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
102779         me.mon(target, 'collapse', me.onTargetCollapse, me);
102780         me.mon(target, 'expand', me.onTargetExpand, me);
102781
102782         me.el.addCls(me.baseCls + '-' + me.orientation);
102783         me.el.unselectable();
102784
102785         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
102786             el: me.el
102787         });
102788
102789         // Relay the most important events to our owner (could open wider later):
102790         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
102791     },
102792
102793     getCollapseDirection: function() {
102794         var me = this,
102795             idx,
102796             type = me.ownerCt.layout.type;
102797
102798         // Avoid duplication of string tests.
102799         // Create a two bit truth table of the configuration of the Splitter:
102800         // Collapse Target | orientation
102801         //        0              0             = next, horizontal
102802         //        0              1             = next, vertical
102803         //        1              0             = prev, horizontal
102804         //        1              1             = prev, vertical
102805         if (me.collapseTarget.isComponent) {
102806             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
102807         } else {
102808             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
102809         }
102810
102811         // Read the data out the truth table
102812         me.orientation = ['horizontal', 'vertical'][idx & 1];
102813         return ['bottom', 'right', 'top', 'left'][idx];
102814     },
102815
102816     getCollapseTarget: function() {
102817         var me = this;
102818
102819         return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
102820     },
102821
102822     onTargetCollapse: function(target) {
102823         this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
102824     },
102825
102826     onTargetExpand: function(target) {
102827         this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
102828     },
102829
102830     toggleTargetCmp: function(e, t) {
102831         var cmp = this.getCollapseTarget();
102832
102833         if (cmp.isVisible()) {
102834             // restore
102835             if (cmp.collapsed) {
102836                 cmp.expand(cmp.animCollapse);
102837             // collapse
102838             } else {
102839                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
102840             }
102841         }
102842     },
102843
102844     /*
102845      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
102846      */
102847     setSize: function() {
102848         var me = this;
102849         me.callParent(arguments);
102850         if (Ext.isIE) {
102851             me.el.repaint();
102852         }
102853     }
102854 });
102855
102856 /**
102857  * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
102858  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
102859  *
102860  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
102861  * config, and should generally not need to be created directly via the new keyword.
102862  *
102863  *     @example
102864  *     Ext.create('Ext.panel.Panel', {
102865  *         width: 500,
102866  *         height: 400,
102867  *         title: 'Border Layout',
102868  *         layout: 'border',
102869  *         items: [{
102870  *             title: 'South Region is resizable',
102871  *             region: 'south',     // position for region
102872  *             xtype: 'panel',
102873  *             height: 100,
102874  *             split: true,         // enable resizing
102875  *             margins: '0 5 5 5'
102876  *         },{
102877  *             // xtype: 'panel' implied by default
102878  *             title: 'West Region is collapsible',
102879  *             region:'west',
102880  *             xtype: 'panel',
102881  *             margins: '5 0 0 5',
102882  *             width: 200,
102883  *             collapsible: true,   // make collapsible
102884  *             id: 'west-region-container',
102885  *             layout: 'fit'
102886  *         },{
102887  *             title: 'Center Region',
102888  *             region: 'center',     // center region is required, no width/height specified
102889  *             xtype: 'panel',
102890  *             layout: 'fit',
102891  *             margins: '5 5 0 0'
102892  *         }],
102893  *         renderTo: Ext.getBody()
102894  *     });
102895  *
102896  * # Notes
102897  *
102898  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
102899  *   The child item in the center region will always be resized to fill the remaining space
102900  *   not used by the other regions in the layout.
102901  *
102902  * - Any child items with a region of `west` or `east` may be configured with either an initial
102903  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
102904  *   **string** (Which is simply divided by 100 and used as a flex value).
102905  *   The 'center' region has a flex value of `1`.
102906  *
102907  * - Any child items with a region of `north` or `south` may be configured with either an initial
102908  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
102909  *   **string** (Which is simply divided by 100 and used as a flex value).
102910  *   The 'center' region has a flex value of `1`.
102911  *
102912  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
102913  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
102914  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
102915  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
102916  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
102917  *   the west region:
102918  *
102919  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
102920  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
102921  *       wrc.{@link Ext.container.Container#add add}({
102922  *           title: 'Added Panel',
102923  *           html: 'Some content'
102924  *       });
102925  *
102926  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
102927  */
102928 Ext.define('Ext.layout.container.Border', {
102929
102930     alias: ['layout.border'],
102931     extend: 'Ext.layout.container.Container',
102932     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
102933     alternateClassName: 'Ext.layout.BorderLayout',
102934
102935     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
102936
102937     itemCls: Ext.baseCSSPrefix + 'border-item',
102938
102939     bindToOwnerCtContainer: true,
102940
102941     percentageRe: /(\d+)%/,
102942
102943     slideDirection: {
102944         north: 't',
102945         south: 'b',
102946         west: 'l',
102947         east: 'r'
102948     },
102949
102950     constructor: function(config) {
102951         this.initialConfig = config;
102952         this.callParent(arguments);
102953     },
102954
102955     onLayout: function() {
102956         var me = this;
102957         if (!me.borderLayoutInitialized) {
102958             me.initializeBorderLayout();
102959         }
102960
102961         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
102962         me.fixHeightConstraints();
102963         me.shadowLayout.onLayout();
102964         if (me.embeddedContainer) {
102965             me.embeddedContainer.layout.onLayout();
102966         }
102967
102968         // If the panel was originally configured with collapsed: true, it will have
102969         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
102970         if (!me.initialCollapsedComplete) {
102971             Ext.iterate(me.regions, function(name, region){
102972                 if (region.borderCollapse) {
102973                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
102974                 }
102975             });
102976             me.initialCollapsedComplete = true;
102977         }
102978     },
102979
102980     isValidParent : function(item, target, position) {
102981         if (!this.borderLayoutInitialized) {
102982             this.initializeBorderLayout();
102983         }
102984
102985         // Delegate this operation to the shadow "V" or "H" box layout.
102986         return this.shadowLayout.isValidParent(item, target, position);
102987     },
102988
102989     beforeLayout: function() {
102990         if (!this.borderLayoutInitialized) {
102991             this.initializeBorderLayout();
102992         }
102993
102994         // Delegate this operation to the shadow "V" or "H" box layout.
102995         this.shadowLayout.beforeLayout();
102996
102997         // note: don't call base because that does a renderItems again
102998     },
102999
103000     renderItems: function(items, target) {
103001         //<debug>
103002         Ext.Error.raise('This should not be called');
103003         //</debug>
103004     },
103005
103006     renderItem: function(item) {
103007         //<debug>
103008         Ext.Error.raise('This should not be called');
103009         //</debug>
103010     },
103011
103012     renderChildren: function() {
103013         if (!this.borderLayoutInitialized) {
103014             this.initializeBorderLayout();
103015         }
103016
103017         this.shadowLayout.renderChildren();
103018     },
103019
103020     /*
103021      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
103022      * We must not include child items which are floated over the layout (are primed with a slide out animation)
103023      */
103024     getVisibleItems: function() {
103025         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
103026     },
103027
103028     initializeBorderLayout: function() {
103029         var me = this,
103030             i = 0,
103031             items = me.getLayoutItems(),
103032             ln = items.length,
103033             regions = (me.regions = {}),
103034             vBoxItems = [],
103035             hBoxItems = [],
103036             horizontalFlex = 0,
103037             verticalFlex = 0,
103038             comp, percentage;
103039
103040         // Map of Splitters for each region
103041         me.splitters = {};
103042
103043         // Map of regions
103044         for (; i < ln; i++) {
103045             comp = items[i];
103046             regions[comp.region] = comp;
103047
103048             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
103049             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
103050
103051                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
103052                 comp.borderCollapse = comp.collapsed;
103053                 comp.collapsed = false;
103054
103055                 comp.on({
103056                     beforecollapse: me.onBeforeRegionCollapse,
103057                     beforeexpand: me.onBeforeRegionExpand,
103058                     destroy: me.onRegionDestroy,
103059                     scope: me
103060                 });
103061                 me.setupState(comp);
103062             }
103063         }
103064         //<debug>
103065         if (!regions.center) {
103066             Ext.Error.raise("You must specify a center region when defining a BorderLayout.");
103067         }
103068         //</debug>
103069         comp = regions.center;
103070         if (!comp.flex) {
103071             comp.flex = 1;
103072         }
103073         delete comp.width;
103074         comp.maintainFlex = true;
103075
103076         // Begin the VBox and HBox item list.
103077         comp = regions.west;
103078         if (comp) {
103079             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
103080             hBoxItems.push(comp);
103081             if (comp.split) {
103082                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
103083             }
103084             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
103085             if (percentage) {
103086                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
103087                 delete comp.width;
103088             }
103089         }
103090         comp = regions.north;
103091         if (comp) {
103092             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
103093             vBoxItems.push(comp);
103094             if (comp.split) {
103095                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
103096             }
103097             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
103098             if (percentage) {
103099                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
103100                 delete comp.height;
103101             }
103102         }
103103
103104         // Decide into which Collection the center region goes.
103105         if (regions.north || regions.south) {
103106             if (regions.east || regions.west) {
103107
103108                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
103109                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
103110                     xtype: 'container',
103111                     region: 'center',
103112                     id: me.owner.id + '-embedded-center',
103113                     cls: Ext.baseCSSPrefix + 'border-item',
103114                     flex: regions.center.flex,
103115                     maintainFlex: true,
103116                     layout: {
103117                         type: 'hbox',
103118                         align: 'stretch',
103119                         getVisibleItems: me.getVisibleItems
103120                     }
103121                 }));
103122                 hBoxItems.push(regions.center);
103123             }
103124             // No east or west: the original center goes straight into the vbox
103125             else {
103126                 vBoxItems.push(regions.center);
103127             }
103128         }
103129         // If we have no north or south, then the center is part of the HBox items
103130         else {
103131             hBoxItems.push(regions.center);
103132         }
103133
103134         // Finish off the VBox and HBox item list.
103135         comp = regions.south;
103136         if (comp) {
103137             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
103138             if (comp.split) {
103139                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
103140             }
103141             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
103142             if (percentage) {
103143                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
103144                 delete comp.height;
103145             }
103146             vBoxItems.push(comp);
103147         }
103148         comp = regions.east;
103149         if (comp) {
103150             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
103151             if (comp.split) {
103152                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
103153             }
103154             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
103155             if (percentage) {
103156                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
103157                 delete comp.width;
103158             }
103159             hBoxItems.push(comp);
103160         }
103161
103162         // Create the injected "items" collections for the Containers.
103163         // If we have north or south, then the shadow Container will be a VBox.
103164         // If there are also east or west regions, its center will be a shadow HBox.
103165         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
103166         if (regions.north || regions.south) {
103167
103168             me.shadowContainer = Ext.create('Ext.container.Container', {
103169                 ownerCt: me.owner,
103170                 el: me.getTarget(),
103171                 layout: Ext.applyIf({
103172                     type: 'vbox',
103173                     align: 'stretch',
103174                     getVisibleItems: me.getVisibleItems
103175                 }, me.initialConfig)
103176             });
103177             me.createItems(me.shadowContainer, vBoxItems);
103178
103179             // Allow the Splitters to orientate themselves
103180             if (me.splitters.north) {
103181                 me.splitters.north.ownerCt = me.shadowContainer;
103182             }
103183             if (me.splitters.south) {
103184                 me.splitters.south.ownerCt = me.shadowContainer;
103185             }
103186
103187             // Inject items into the HBox Container if there is one - if there was an east or west.
103188             if (me.embeddedContainer) {
103189                 me.embeddedContainer.ownerCt = me.shadowContainer;
103190                 me.createItems(me.embeddedContainer, hBoxItems);
103191
103192                 // Allow the Splitters to orientate themselves
103193                 if (me.splitters.east) {
103194                     me.splitters.east.ownerCt = me.embeddedContainer;
103195                 }
103196                 if (me.splitters.west) {
103197                     me.splitters.west.ownerCt = me.embeddedContainer;
103198                 }
103199
103200                 // These spliiters need to be constrained by components one-level below
103201                 // the component in their vobx. We update the min/maxHeight on the helper
103202                 // (embeddedContainer) prior to starting the split/drag. This has to be
103203                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
103204                 // dynamically.
103205                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
103206                     if (splitter) {
103207                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
103208                     }
103209                 });
103210
103211                 // The east or west region wanted a percentage
103212                 if (horizontalFlex) {
103213                     regions.center.flex -= horizontalFlex;
103214                 }
103215                 // The north or south region wanted a percentage
103216                 if (verticalFlex) {
103217                     me.embeddedContainer.flex -= verticalFlex;
103218                 }
103219             } else {
103220                 // The north or south region wanted a percentage
103221                 if (verticalFlex) {
103222                     regions.center.flex -= verticalFlex;
103223                 }
103224             }
103225         }
103226         // If we have no north or south, then there's only one Container, and it's
103227         // an HBox, or, if only a center region was specified, a Fit.
103228         else {
103229             me.shadowContainer = Ext.create('Ext.container.Container', {
103230                 ownerCt: me.owner,
103231                 el: me.getTarget(),
103232                 layout: Ext.applyIf({
103233                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
103234                     align: 'stretch'
103235                 }, me.initialConfig)
103236             });
103237             me.createItems(me.shadowContainer, hBoxItems);
103238
103239             // Allow the Splitters to orientate themselves
103240             if (me.splitters.east) {
103241                 me.splitters.east.ownerCt = me.shadowContainer;
103242             }
103243             if (me.splitters.west) {
103244                 me.splitters.west.ownerCt = me.shadowContainer;
103245             }
103246
103247             // The east or west region wanted a percentage
103248             if (horizontalFlex) {
103249                 regions.center.flex -= verticalFlex;
103250             }
103251         }
103252
103253         // Create upward links from the region Components to their shadow ownerCts
103254         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
103255             items[i].shadowOwnerCt = me.shadowContainer;
103256         }
103257         if (me.embeddedContainer) {
103258             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
103259                 items[i].shadowOwnerCt = me.embeddedContainer;
103260             }
103261         }
103262
103263         // This is the layout that we delegate all operations to
103264         me.shadowLayout = me.shadowContainer.getLayout();
103265
103266         me.borderLayoutInitialized = true;
103267     },
103268
103269     setupState: function(comp){
103270         var getState = comp.getState;
103271         comp.getState = function(){
103272             // call the original getState
103273             var state = getState.call(comp) || {},
103274                 region = comp.region;
103275
103276             state.collapsed = !!comp.collapsed;
103277             if (region == 'west' || region == 'east') {
103278                 state.width = comp.getWidth();
103279             } else {
103280                 state.height = comp.getHeight();
103281             }
103282             return state;
103283         };
103284         comp.addStateEvents(['collapse', 'expand', 'resize']);
103285     },
103286
103287     /**
103288      * Create the items collection for our shadow/embedded containers
103289      * @private
103290      */
103291     createItems: function(container, items){
103292         // Have to inject an items Collection *after* construction.
103293         // The child items of the shadow layout must retain their original, user-defined ownerCt
103294         delete container.items;
103295         container.initItems();
103296         container.items.addAll(items);
103297     },
103298
103299     // Private
103300     // Create a splitter for a child of the layout.
103301     createSplitter: function(comp) {
103302         var me = this,
103303             interceptCollapse = (comp.collapseMode != 'header'),
103304             resizer;
103305
103306         resizer = Ext.create('Ext.resizer.Splitter', {
103307             hidden: !!comp.hidden,
103308             collapseTarget: comp,
103309             performCollapse: !interceptCollapse,
103310             listeners: interceptCollapse ? {
103311                 click: {
103312                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
103313                     element: 'collapseEl'
103314                 }
103315             } : null
103316         });
103317
103318         // Mini collapse means that the splitter is the placeholder Component
103319         if (comp.collapseMode == 'mini') {
103320             comp.placeholder = resizer;
103321             resizer.collapsedCls = comp.collapsedCls;
103322         }
103323
103324         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
103325         comp.on({
103326             hide: me.onRegionVisibilityChange,
103327             show: me.onRegionVisibilityChange,
103328             scope: me
103329         });
103330         return resizer;
103331     },
103332
103333     // Private
103334     // Propagates the min/maxHeight values from the inner hbox items to its container.
103335     fixHeightConstraints: function () {
103336         var me = this,
103337             ct = me.embeddedContainer,
103338             maxHeight = 1e99, minHeight = -1;
103339
103340         if (!ct) {
103341             return;
103342         }
103343
103344         ct.items.each(function (item) {
103345             if (Ext.isNumber(item.maxHeight)) {
103346                 maxHeight = Math.max(maxHeight, item.maxHeight);
103347             }
103348             if (Ext.isNumber(item.minHeight)) {
103349                 minHeight = Math.max(minHeight, item.minHeight);
103350             }
103351         });
103352
103353         ct.maxHeight = maxHeight;
103354         ct.minHeight = minHeight;
103355     },
103356
103357     // Hide/show a region's associated splitter when the region is hidden/shown
103358     onRegionVisibilityChange: function(comp){
103359         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
103360         this.layout();
103361     },
103362
103363     // Called when a splitter mini-collapse tool is clicked on.
103364     // The listener is only added if this layout is controlling collapsing,
103365     // not if the component's collapseMode is 'mini' or 'header'.
103366     onSplitterCollapseClick: function(comp) {
103367         if (comp.collapsed) {
103368             this.onPlaceHolderToolClick(null, null, null, {client: comp});
103369         } else {
103370             comp.collapse();
103371         }
103372     },
103373
103374     /**
103375      * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
103376      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
103377      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
103378      *
103379      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
103380      * collapsed.**
103381      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
103382      * Ext.panel.Panel#placeholder placeholder}.
103383      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
103384      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
103385      */
103386     getPlaceholder: function(comp) {
103387         var me = this,
103388             placeholder = comp.placeholder,
103389             shadowContainer = comp.shadowOwnerCt,
103390             shadowLayout = shadowContainer.layout,
103391             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
103392             horiz = (comp.region == 'north' || comp.region == 'south');
103393
103394         // No placeholder if the collapse mode is not the Border layout default
103395         if (comp.collapseMode == 'header') {
103396             return;
103397         }
103398
103399         // Provide a replacement Container with an expand tool
103400         if (!placeholder) {
103401             if (comp.collapseMode == 'mini') {
103402                 placeholder = Ext.create('Ext.resizer.Splitter', {
103403                     id: 'collapse-placeholder-' + comp.id,
103404                     collapseTarget: comp,
103405                     performCollapse: false,
103406                     listeners: {
103407                         click: {
103408                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
103409                             element: 'collapseEl'
103410                         }
103411                     }
103412                 });
103413                 placeholder.addCls(placeholder.collapsedCls);
103414             } else {
103415                 placeholder = {
103416                     id: 'collapse-placeholder-' + comp.id,
103417                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
103418                     xtype: 'header',
103419                     orientation: horiz ? 'horizontal' : 'vertical',
103420                     title: comp.title,
103421                     textCls: comp.headerTextCls,
103422                     iconCls: comp.iconCls,
103423                     baseCls: comp.baseCls + '-header',
103424                     ui: comp.ui,
103425                     indicateDrag: comp.draggable,
103426                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
103427                     listeners: comp.floatable ? {
103428                         click: {
103429                             fn: function(e) {
103430                                 me.floatCollapsedPanel(e, comp);
103431                             },
103432                             element: 'el'
103433                         }
103434                     } : null
103435                 };
103436                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
103437                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
103438                     placeholder.width = 25;
103439                 }
103440                 if (!comp.hideCollapseTool) {
103441                     placeholder[horiz ? 'tools' : 'items'] = [{
103442                         xtype: 'tool',
103443                         client: comp,
103444                         type: 'expand-' + oppositeDirection,
103445                         handler: me.onPlaceHolderToolClick,
103446                         scope: me
103447                     }];
103448                 }
103449             }
103450             placeholder = me.owner.createComponent(placeholder);
103451             if (comp.isXType('panel')) {
103452                 comp.on({
103453                     titlechange: me.onRegionTitleChange,
103454                     iconchange: me.onRegionIconChange,
103455                     scope: me
103456                 });
103457             }
103458         }
103459
103460         // The collapsed Component holds a reference to its placeholder and vice versa
103461         comp.placeholder = placeholder;
103462         placeholder.comp = comp;
103463
103464         return placeholder;
103465     },
103466
103467     /**
103468      * @private
103469      * Update the placeholder title when panel title has been set or changed.
103470      */
103471     onRegionTitleChange: function(comp, newTitle) {
103472         comp.placeholder.setTitle(newTitle);
103473     },
103474
103475     /**
103476      * @private
103477      * Update the placeholder iconCls when panel iconCls has been set or changed.
103478      */
103479     onRegionIconChange: function(comp, newIconCls) {
103480         comp.placeholder.setIconCls(newIconCls);
103481     },
103482
103483     /**
103484      * @private
103485      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
103486      * when configured with a flex, calls this method on its ownerCt's layout.
103487      * @param {Ext.Component} child The child Component to calculate the box for
103488      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
103489      */
103490     calculateChildBox: function(comp) {
103491         var me = this;
103492         if (me.shadowContainer.items.contains(comp)) {
103493             return me.shadowContainer.layout.calculateChildBox(comp);
103494         }
103495         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
103496             return me.embeddedContainer.layout.calculateChildBox(comp);
103497         }
103498     },
103499
103500     /**
103501      * @private
103502      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
103503      * with a placeholder Header orientated in the appropriate dimension.
103504      * @param comp The Panel being collapsed.
103505      * @param direction
103506      * @param animate
103507      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
103508      */
103509     onBeforeRegionCollapse: function(comp, direction, animate) {
103510         if (comp.collapsedChangingLayout) {
103511             //<debug warn>
103512             if (Ext.global.console && Ext.global.console.warn) {
103513                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
103514             }
103515             //</debug>
103516             return false;
103517         }
103518         comp.collapsedChangingLayout = true;
103519         var me = this,
103520             compEl = comp.el,
103521             width,
103522             miniCollapse = comp.collapseMode == 'mini',
103523             shadowContainer = comp.shadowOwnerCt,
103524             shadowLayout = shadowContainer.layout,
103525             placeholder = comp.placeholder,
103526             sl = me.owner.suspendLayout,
103527             scsl = shadowContainer.suspendLayout,
103528             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
103529
103530         // Do not trigger a layout during transition to collapsed Component
103531         me.owner.suspendLayout = true;
103532         shadowContainer.suspendLayout = true;
103533
103534         // Prevent upward notifications from downstream layouts
103535         shadowLayout.layoutBusy = true;
103536         if (shadowContainer.componentLayout) {
103537             shadowContainer.componentLayout.layoutBusy = true;
103538         }
103539         me.shadowContainer.layout.layoutBusy = true;
103540         me.layoutBusy = true;
103541         me.owner.componentLayout.layoutBusy = true;
103542
103543         // Provide a replacement Container with an expand tool
103544         if (!placeholder) {
103545             placeholder = me.getPlaceholder(comp);
103546         }
103547
103548         // placeholder already in place; show it.
103549         if (placeholder.shadowOwnerCt === shadowContainer) {
103550             placeholder.show();
103551         }
103552         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
103553         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
103554         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
103555         else {
103556             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
103557             placeholder.shadowOwnerCt = shadowContainer;
103558             placeholder.ownerCt = me.owner;
103559         }
103560
103561         // Flag the collapsing Component as hidden and show the placeholder.
103562         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
103563         // We hide or slideOut the Component's element
103564         comp.hidden = true;
103565
103566         if (!placeholder.rendered) {
103567             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
103568
103569             // The inserted placeholder does not have the proper size, so copy the width
103570             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
103571             // without recursive layouts. This is only an issue initially. After this time,
103572             // placeholder will have the correct width/height set by the layout (which has
103573             // already happened when we get here initially).
103574             if (comp.region == 'north' || comp.region == 'south') {
103575                 placeholder.setCalculatedSize(comp.getWidth());
103576             } else {
103577                 placeholder.setCalculatedSize(undefined, comp.getHeight());
103578             }
103579         }
103580
103581         // Jobs to be done after the collapse has been done
103582         function afterCollapse() {
103583             // Reinstate automatic laying out.
103584             me.owner.suspendLayout = sl;
103585             shadowContainer.suspendLayout = scsl;
103586             delete shadowLayout.layoutBusy;
103587             if (shadowContainer.componentLayout) {
103588                 delete shadowContainer.componentLayout.layoutBusy;
103589             }
103590             delete me.shadowContainer.layout.layoutBusy;
103591             delete me.layoutBusy;
103592             delete me.owner.componentLayout.layoutBusy;
103593             delete comp.collapsedChangingLayout;
103594
103595             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
103596             comp.collapsed = true;
103597             comp.fireEvent('collapse', comp);
103598         }
103599
103600         /*
103601          * Set everything to the new positions. Note that we
103602          * only want to animate the collapse if it wasn't configured
103603          * initially with collapsed: true
103604          */
103605         if (comp.animCollapse && me.initialCollapsedComplete) {
103606             shadowLayout.layout();
103607             compEl.dom.style.zIndex = 100;
103608
103609             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
103610             if (!miniCollapse) {
103611                 placeholder.el.hide();
103612             }
103613             compEl.slideOut(me.slideDirection[comp.region], {
103614                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
103615                 listeners: {
103616                     afteranimate: function() {
103617                         compEl.show().setLeftTop(-10000, -10000);
103618                         compEl.dom.style.zIndex = '';
103619
103620                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
103621                        if (!miniCollapse) {
103622                             placeholder.el.slideIn(me.slideDirection[comp.region], {
103623                                 easing: 'linear',
103624                                 duration: 100
103625                             });
103626                         }
103627                         afterCollapse();
103628                     }
103629                 }
103630             });
103631         } else {
103632             compEl.setLeftTop(-10000, -10000);
103633             shadowLayout.layout();
103634             afterCollapse();
103635         }
103636
103637         return false;
103638     },
103639
103640     // Hijack the expand operation to remove the placeholder and slide the region back in.
103641     onBeforeRegionExpand: function(comp, animate) {
103642         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
103643         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
103644         return false;
103645     },
103646
103647     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
103648     onPlaceHolderToolClick: function(e, target, owner, tool) {
103649         var me = this,
103650             comp = tool.client,
103651
103652             // Hide the placeholder unless it was the Component's preexisting splitter
103653             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
103654             compEl = comp.el,
103655             toCompBox,
103656             placeholder = comp.placeholder,
103657             placeholderEl = placeholder.el,
103658             shadowContainer = comp.shadowOwnerCt,
103659             shadowLayout = shadowContainer.layout,
103660             curSize,
103661             sl = me.owner.suspendLayout,
103662             scsl = shadowContainer.suspendLayout,
103663             isFloating;
103664
103665         if (comp.collapsedChangingLayout) {
103666             //<debug warn>
103667             if (Ext.global.console && Ext.global.console.warn) {
103668                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
103669             }
103670             //</debug>
103671             return false;
103672         }
103673         if (tool.shouldFireBeforeexpand !== false && comp.fireEvent('beforeexpand', comp, true) === false) {
103674             return false;
103675         }
103676         comp.collapsedChangingLayout = true;
103677         // If the slide in is still going, stop it.
103678         // This will either leave the Component in its fully floated state (which is processed below)
103679         // or in its collapsed state. Either way, we expand it..
103680         if (comp.getActiveAnimation()) {
103681             comp.stopAnimation();
103682         }
103683
103684         // If the Component is fully floated when they click the placeholder Tool,
103685         // it will be primed with a slide out animation object... so delete that
103686         // and remove the mouseout listeners
103687         if (comp.slideOutAnim) {
103688             // Remove mouse leave monitors
103689             compEl.un(comp.panelMouseMon);
103690             placeholderEl.un(comp.placeholderMouseMon);
103691
103692             delete comp.slideOutAnim;
103693             delete comp.panelMouseMon;
103694             delete comp.placeholderMouseMon;
103695
103696             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
103697             isFloating = true;
103698         }
103699
103700         // Do not trigger a layout during transition to expanded Component
103701         me.owner.suspendLayout = true;
103702         shadowContainer.suspendLayout = true;
103703
103704         // Prevent upward notifications from downstream layouts
103705         shadowLayout.layoutBusy = true;
103706         if (shadowContainer.componentLayout) {
103707             shadowContainer.componentLayout.layoutBusy = true;
103708         }
103709         me.shadowContainer.layout.layoutBusy = true;
103710         me.layoutBusy = true;
103711         me.owner.componentLayout.layoutBusy = true;
103712
103713         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
103714         // Find where the shadow Box layout plans to put the expanding Component.
103715         comp.hidden = false;
103716         comp.collapsed = false;
103717         if (hidePlaceholder) {
103718             placeholder.hidden = true;
103719         }
103720         toCompBox = shadowLayout.calculateChildBox(comp);
103721
103722         // Show the collapse tool in case it was hidden by the slide-in
103723         if (comp.collapseTool) {
103724             comp.collapseTool.show();
103725         }
103726
103727         // If we're going to animate, we need to hide the component before moving it back into position
103728         if (comp.animCollapse && !isFloating) {
103729             compEl.setStyle('visibility', 'hidden');
103730         }
103731         compEl.setLeftTop(toCompBox.left, toCompBox.top);
103732
103733         // Equalize the size of the expanding Component prior to animation
103734         // in case the layout area has changed size during the time it was collapsed.
103735         curSize = comp.getSize();
103736         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
103737             me.setItemSize(comp, toCompBox.width, toCompBox.height);
103738         }
103739
103740         // Jobs to be done after the expand has been done
103741         function afterExpand() {
103742             // Reinstate automatic laying out.
103743             me.owner.suspendLayout = sl;
103744             shadowContainer.suspendLayout = scsl;
103745             delete shadowLayout.layoutBusy;
103746             if (shadowContainer.componentLayout) {
103747                 delete shadowContainer.componentLayout.layoutBusy;
103748             }
103749             delete me.shadowContainer.layout.layoutBusy;
103750             delete me.layoutBusy;
103751             delete me.owner.componentLayout.layoutBusy;
103752             delete comp.collapsedChangingLayout;
103753
103754             // In case it was floated out and they clicked the re-expand tool
103755             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
103756
103757             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
103758             comp.fireEvent('expand', comp);
103759         }
103760
103761         // Hide the placeholder
103762         if (hidePlaceholder) {
103763             placeholder.el.hide();
103764         }
103765
103766         // Slide the expanding Component to its new position.
103767         // When that is done, layout the layout.
103768         if (comp.animCollapse && !isFloating) {
103769             compEl.dom.style.zIndex = 100;
103770             compEl.slideIn(me.slideDirection[comp.region], {
103771                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
103772                 listeners: {
103773                     afteranimate: function() {
103774                         compEl.dom.style.zIndex = '';
103775                         comp.hidden = false;
103776                         shadowLayout.onLayout();
103777                         afterExpand();
103778                     }
103779                 }
103780             });
103781         } else {
103782             shadowLayout.onLayout();
103783             afterExpand();
103784         }
103785     },
103786
103787     floatCollapsedPanel: function(e, comp) {
103788
103789         if (comp.floatable === false) {
103790             return;
103791         }
103792
103793         var me = this,
103794             compEl = comp.el,
103795             placeholder = comp.placeholder,
103796             placeholderEl = placeholder.el,
103797             shadowContainer = comp.shadowOwnerCt,
103798             shadowLayout = shadowContainer.layout,
103799             placeholderBox = shadowLayout.getChildBox(placeholder),
103800             scsl = shadowContainer.suspendLayout,
103801             curSize, toCompBox, compAnim;
103802
103803         // Ignore clicks on tools.
103804         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
103805             return;
103806         }
103807
103808         // It's *being* animated, ignore the click.
103809         // Possible future enhancement: Stop and *reverse* the current active Fx.
103810         if (compEl.getActiveAnimation()) {
103811             return;
103812         }
103813
103814         // If the Component is already fully floated when they click the placeholder,
103815         // it will be primed with a slide out animation object... so slide it out.
103816         if (comp.slideOutAnim) {
103817             me.slideOutFloatedComponent(comp);
103818             return;
103819         }
103820
103821         // Function to be called when the mouse leaves the floated Panel
103822         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
103823         function onMouseLeaveFloated(e) {
103824             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
103825
103826             // If mouse is not within slide Region, slide it out
103827             if (!slideRegion.contains(e.getPoint())) {
103828                 me.slideOutFloatedComponent(comp);
103829             }
103830         }
103831
103832         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
103833         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
103834
103835         // Do not trigger a layout during slide out of the Component
103836         shadowContainer.suspendLayout = true;
103837
103838         // Prevent upward notifications from downstream layouts
103839         me.layoutBusy = true;
103840         me.owner.componentLayout.layoutBusy = true;
103841
103842         // The collapse tool is hidden while slid.
103843         // It is re-shown on expand.
103844         if (comp.collapseTool) {
103845             comp.collapseTool.hide();
103846         }
103847
103848         // Set flags so that the layout will calculate the boxes for what we want
103849         comp.hidden = false;
103850         comp.collapsed = false;
103851         placeholder.hidden = true;
103852
103853         // Recalculate new arrangement of the Component being floated.
103854         toCompBox = shadowLayout.calculateChildBox(comp);
103855         placeholder.hidden = false;
103856
103857         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
103858         if (comp.region == 'north' || comp.region == 'west') {
103859             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
103860         } else {
103861             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
103862         }
103863         compEl.setStyle('visibility', 'hidden');
103864         compEl.setLeftTop(toCompBox.left, toCompBox.top);
103865
103866         // Equalize the size of the expanding Component prior to animation
103867         // in case the layout area has changed size during the time it was collapsed.
103868         curSize = comp.getSize();
103869         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
103870             me.setItemSize(comp, toCompBox.width, toCompBox.height);
103871         }
103872
103873         // This animation slides the collapsed Component's el out to just beyond its placeholder
103874         compAnim = {
103875             listeners: {
103876                 afteranimate: function() {
103877                     shadowContainer.suspendLayout = scsl;
103878                     delete me.layoutBusy;
103879                     delete me.owner.componentLayout.layoutBusy;
103880
103881                     // Prime the Component with an Anim config object to slide it back out
103882                     compAnim.listeners = {
103883                         afterAnimate: function() {
103884                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
103885
103886                             // Reinstate the correct, current state after slide out animation finishes
103887                             comp.hidden = true;
103888                             comp.collapsed = true;
103889                             delete comp.slideOutAnim;
103890                             delete comp.panelMouseMon;
103891                             delete comp.placeholderMouseMon;
103892                         }
103893                     };
103894                     comp.slideOutAnim = compAnim;
103895                 }
103896             },
103897             duration: 500
103898         };
103899
103900         // Give the element the correct class which places it at a high z-index
103901         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
103902
103903         // Begin the slide in
103904         compEl.slideIn(me.slideDirection[comp.region], compAnim);
103905
103906         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
103907         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
103908
103909     },
103910
103911     slideOutFloatedComponent: function(comp) {
103912         var compEl = comp.el,
103913             slideOutAnim;
103914
103915         // Remove mouse leave monitors
103916         compEl.un(comp.panelMouseMon);
103917         comp.placeholder.el.un(comp.placeholderMouseMon);
103918
103919         // Slide the Component out
103920         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
103921
103922         delete comp.slideOutAnim;
103923         delete comp.panelMouseMon;
103924         delete comp.placeholderMouseMon;
103925     },
103926
103927     /*
103928      * @private
103929      * Ensure any collapsed placeholder Component is destroyed along with its region.
103930      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
103931      */
103932     onRegionDestroy: function(comp) {
103933         var placeholder = comp.placeholder;
103934         if (placeholder) {
103935             delete placeholder.ownerCt;
103936             placeholder.destroy();
103937         }
103938     },
103939
103940     /*
103941      * @private
103942      * Ensure any shadow Containers are destroyed.
103943      * Ensure we don't keep references to Components.
103944      */
103945     onDestroy: function() {
103946         var me = this,
103947             shadowContainer = me.shadowContainer,
103948             embeddedContainer = me.embeddedContainer;
103949
103950         if (shadowContainer) {
103951             delete shadowContainer.ownerCt;
103952             Ext.destroy(shadowContainer);
103953         }
103954
103955         if (embeddedContainer) {
103956             delete embeddedContainer.ownerCt;
103957             Ext.destroy(embeddedContainer);
103958         }
103959         delete me.regions;
103960         delete me.splitters;
103961         delete me.shadowContainer;
103962         delete me.embeddedContainer;
103963         me.callParent(arguments);
103964     }
103965 });
103966
103967 /**
103968  * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
103969  * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
103970  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
103971  * and should generally not need to be created directly via the new keyword.
103972  *
103973  * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
103974  * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
103975  * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
103976  * so that functionality must be provided by the developer.
103977  *
103978  * To change the active card of a container, call the setActiveItem method of its layout:
103979  *
103980  *     Ext.create('Ext.panel.Panel', {
103981  *         layout: 'card',
103982  *         items: [
103983  *             { html: 'Card 1' },
103984  *             { html: 'Card 2' }
103985  *         ],
103986  *         renderTo: Ext.getBody()
103987  *     });
103988  *
103989  *     p.getLayout().setActiveItem(1);
103990  *
103991  * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
103992  * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
103993  * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
103994  * completely different implementation.  For serious implementations, a better approach would be to extend
103995  * CardLayout to provide the custom functionality needed.
103996  *
103997  *     @example
103998  *     var navigate = function(panel, direction){
103999  *         // This routine could contain business logic required to manage the navigation steps.
104000  *         // It would call setActiveItem as needed, manage navigation button state, handle any
104001  *         // branching logic that might be required, handle alternate actions like cancellation
104002  *         // or finalization, etc.  A complete wizard implementation could get pretty
104003  *         // sophisticated depending on the complexity required, and should probably be
104004  *         // done as a subclass of CardLayout in a real-world implementation.
104005  *         var layout = panel.getLayout();
104006  *         layout[direction]();
104007  *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
104008  *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
104009  *     };
104010  *
104011  *     Ext.create('Ext.panel.Panel', {
104012  *         title: 'Example Wizard',
104013  *         width: 300,
104014  *         height: 200,
104015  *         layout: 'card',
104016  *         bodyStyle: 'padding:15px',
104017  *         defaults: {
104018  *             // applied to each contained panel
104019  *             border: false
104020  *         },
104021  *         // just an example of one possible navigation scheme, using buttons
104022  *         bbar: [
104023  *             {
104024  *                 id: 'move-prev',
104025  *                 text: 'Back',
104026  *                 handler: function(btn) {
104027  *                     navigate(btn.up("panel"), "prev");
104028  *                 },
104029  *                 disabled: true
104030  *             },
104031  *             '->', // greedy spacer so that the buttons are aligned to each side
104032  *             {
104033  *                 id: 'move-next',
104034  *                 text: 'Next',
104035  *                 handler: function(btn) {
104036  *                     navigate(btn.up("panel"), "next");
104037  *                 }
104038  *             }
104039  *         ],
104040  *         // the panels (or "cards") within the layout
104041  *         items: [{
104042  *             id: 'card-0',
104043  *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
104044  *         },{
104045  *             id: 'card-1',
104046  *             html: '<p>Step 2 of 3</p>'
104047  *         },{
104048  *             id: 'card-2',
104049  *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
104050  *         }],
104051  *         renderTo: Ext.getBody()
104052  *     });
104053  */
104054 Ext.define('Ext.layout.container.Card', {
104055
104056     /* Begin Definitions */
104057
104058     alias: ['layout.card'],
104059     alternateClassName: 'Ext.layout.CardLayout',
104060
104061     extend: 'Ext.layout.container.AbstractCard',
104062
104063     /* End Definitions */
104064
104065     /**
104066      * Makes the given card active.
104067      *
104068      *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
104069      *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
104070      *     var panel = Ext.create('Ext.panel.Panel', {
104071      *         layout: 'card',
104072      *         activeItem: 0,
104073      *         items: [card1, card2]
104074      *     });
104075      *     // These are all equivalent
104076      *     panel.getLayout().setActiveItem(card2);
104077      *     panel.getLayout().setActiveItem('card-2');
104078      *     panel.getLayout().setActiveItem(1);
104079      *
104080      * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
104081      * {@link Ext.Component#itemId itemId}, or index of component.
104082      * @return {Ext.Component} the activated component or false when nothing activated.
104083      * False is returned also when trying to activate an already active card.
104084      */
104085     setActiveItem: function(newCard) {
104086         var me = this,
104087             owner = me.owner,
104088             oldCard = me.activeItem,
104089             newIndex;
104090
104091         newCard = me.parseActiveItem(newCard);
104092         newIndex = owner.items.indexOf(newCard);
104093
104094         // If the card is not a child of the owner, then add it
104095         if (newIndex == -1) {
104096             newIndex = owner.items.items.length;
104097             owner.add(newCard);
104098         }
104099
104100         // Is this a valid, different card?
104101         if (newCard && oldCard != newCard) {
104102             // If the card has not been rendered yet, now is the time to do so.
104103             if (!newCard.rendered) {
104104                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
104105                 me.configureItem(newCard, 0);
104106             }
104107
104108             me.activeItem = newCard;
104109
104110             // Fire the beforeactivate and beforedeactivate events on the cards
104111             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
104112                 return false;
104113             }
104114             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
104115                 return false;
104116             }
104117
104118             // If the card hasnt been sized yet, do it now
104119             if (me.sizeAllCards) {
104120                 // onLayout calls setItemBox
104121                 me.onLayout();
104122             }
104123             else {
104124                 me.setItemBox(newCard, me.getTargetBox());
104125             }
104126
104127             me.owner.suspendLayout = true;
104128
104129             if (oldCard) {
104130                 if (me.hideInactive) {
104131                     oldCard.hide();
104132                 }
104133                 oldCard.fireEvent('deactivate', oldCard, newCard);
104134             }
104135
104136             // Make sure the new card is shown
104137             me.owner.suspendLayout = false;
104138             if (newCard.hidden) {
104139                 newCard.show();
104140             } else {
104141                 me.onLayout();
104142             }
104143
104144             newCard.fireEvent('activate', newCard, oldCard);
104145
104146             return newCard;
104147         }
104148         return false;
104149     },
104150
104151     configureItem: function(item) {
104152         // Card layout only controls dimensions which IT has controlled.
104153         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
104154         item.layoutManagedHeight = 0;
104155         item.layoutManagedWidth = 0;
104156
104157         this.callParent(arguments);
104158     }});
104159 /**
104160  * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
104161  * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
104162  * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
104163  * and should generally not need to be created directly via the new keyword.
104164  *
104165  * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
104166  * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
104167  * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
104168  * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
104169  *
104170  * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
104171  * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
104172  *
104173  * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
104174  * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
104175  * are skipped, but their widths are subtracted from the overall container width.
104176  *
104177  * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
104178  * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
104179  * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
104180  * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
104181  * together, otherwise your layout may not render as expected.
104182  *
104183  *     @example
104184  *     // All columns are percentages -- they must add up to 1
104185  *     Ext.create('Ext.panel.Panel', {
104186  *         title: 'Column Layout - Percentage Only',
104187  *         width: 350,
104188  *         height: 250,
104189  *         layout:'column',
104190  *         items: [{
104191  *             title: 'Column 1',
104192  *             columnWidth: .25
104193  *         },{
104194  *             title: 'Column 2',
104195  *             columnWidth: .55
104196  *         },{
104197  *             title: 'Column 3',
104198  *             columnWidth: .20
104199  *         }],
104200  *         renderTo: Ext.getBody()
104201  *     });
104202  *
104203  *     // Mix of width and columnWidth -- all columnWidth values must add up
104204  *     // to 1. The first column will take up exactly 120px, and the last two
104205  *     // columns will fill the remaining container width.
104206  *
104207  *     Ext.create('Ext.Panel', {
104208  *         title: 'Column Layout - Mixed',
104209  *         width: 350,
104210  *         height: 250,
104211  *         layout:'column',
104212  *         items: [{
104213  *             title: 'Column 1',
104214  *             width: 120
104215  *         },{
104216  *             title: 'Column 2',
104217  *             columnWidth: .7
104218  *         },{
104219  *             title: 'Column 3',
104220  *             columnWidth: .3
104221  *         }],
104222  *         renderTo: Ext.getBody()
104223  *     });
104224  */
104225 Ext.define('Ext.layout.container.Column', {
104226
104227     extend: 'Ext.layout.container.Auto',
104228     alias: ['layout.column'],
104229     alternateClassName: 'Ext.layout.ColumnLayout',
104230
104231     type: 'column',
104232
104233     itemCls: Ext.baseCSSPrefix + 'column',
104234
104235     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
104236
104237     scrollOffset: 0,
104238
104239     bindToOwnerCtComponent: false,
104240
104241     getRenderTarget : function() {
104242         if (!this.innerCt) {
104243
104244             // the innerCt prevents wrapping and shuffling while
104245             // the container is resizing
104246             this.innerCt = this.getTarget().createChild({
104247                 cls: Ext.baseCSSPrefix + 'column-inner'
104248             });
104249
104250             // Column layout uses natural HTML flow to arrange the child items.
104251             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
104252             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
104253             this.clearEl = this.innerCt.createChild({
104254                 cls: Ext.baseCSSPrefix + 'clear',
104255                 role: 'presentation'
104256             });
104257         }
104258         return this.innerCt;
104259     },
104260
104261     // private
104262     onLayout : function() {
104263         var me = this,
104264             target = me.getTarget(),
104265             items = me.getLayoutItems(),
104266             len = items.length,
104267             item,
104268             i,
104269             parallelMargins = [],
104270             itemParallelMargins,
104271             size,
104272             availableWidth,
104273             columnWidth;
104274
104275         size = me.getLayoutTargetSize();
104276         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
104277             return;
104278         }
104279
104280         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
104281         // If, after the layout, it is detected that there is vertical overflow,
104282         // we will recurse back through here. Do not adjust overflow style at that time.
104283         if (me.adjustmentPass) {
104284             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
104285                 size.width = me.adjustedWidth;
104286             }
104287         } else {
104288             i = target.getStyle('overflow');
104289             if (i && i != 'hidden') {
104290                 me.autoScroll = true;
104291                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
104292                     target.setStyle('overflow', 'hidden');
104293                     size = me.getLayoutTargetSize();
104294                 }
104295             }
104296         }
104297
104298         availableWidth = size.width - me.scrollOffset;
104299         me.innerCt.setWidth(availableWidth);
104300
104301         // some columns can be percentages while others are fixed
104302         // so we need to make 2 passes
104303         for (i = 0; i < len; i++) {
104304             item = items[i];
104305             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
104306             if (!item.columnWidth) {
104307                 availableWidth -= (item.getWidth() + itemParallelMargins);
104308             }
104309         }
104310
104311         availableWidth = availableWidth < 0 ? 0 : availableWidth;
104312         for (i = 0; i < len; i++) {
104313             item = items[i];
104314             if (item.columnWidth) {
104315                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
104316                 me.setItemSize(item, columnWidth, item.height);
104317             } else {
104318                 me.layoutItem(item);
104319             }
104320         }
104321
104322         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
104323         if (!me.adjustmentPass && me.autoScroll) {
104324
104325             // If there's a vertical overflow, relay with scrollbars
104326             target.setStyle('overflow', 'auto');
104327             me.adjustmentPass = (target.dom.scrollHeight > size.height);
104328             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
104329                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
104330             } else {
104331                 target.setStyle('overflow', 'auto');
104332             }
104333
104334             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
104335             if (me.adjustmentPass) {
104336                 me.onLayout();
104337             }
104338         }
104339         delete me.adjustmentPass;
104340     },
104341
104342     configureItem: function(item) {
104343         this.callParent(arguments);
104344
104345         if (item.columnWidth) {
104346             item.layoutManagedWidth = 1;
104347         }
104348     }
104349 });
104350 /**
104351  * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
104352  * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
104353  * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
104354  * need to be created directly via the new keyword.
104355  *
104356  * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
104357  * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
104358  * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
104359  * added to a TableLayout can supply the following table-specific config properties:
104360  *
104361  *   - **rowspan** Applied to the table cell containing the item.
104362  *   - **colspan** Applied to the table cell containing the item.
104363  *   - **cellId** An id applied to the table cell containing the item.
104364  *   - **cellCls** A CSS class name added to the table cell containing the item.
104365  *
104366  * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
104367  * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
104368  * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
104369  * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
104370  * layoutConfig and start adding panels in their natural order from left to right, top to bottom. The layout will
104371  * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
104372  * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
104373  * end up with missing and/or extra cells! Example usage:
104374  *
104375  *     @example
104376  *     Ext.create('Ext.panel.Panel', {
104377  *         title: 'Table Layout',
104378  *         width: 300,
104379  *         height: 150,
104380  *         layout: {
104381  *             type: 'table',
104382  *             // The total column count must be specified here
104383  *             columns: 3
104384  *         },
104385  *         defaults: {
104386  *             // applied to each contained panel
104387  *             bodyStyle: 'padding:20px'
104388  *         },
104389  *         items: [{
104390  *             html: 'Cell A content',
104391  *             rowspan: 2
104392  *         },{
104393  *             html: 'Cell B content',
104394  *             colspan: 2
104395  *         },{
104396  *             html: 'Cell C content',
104397  *             cellCls: 'highlight'
104398  *         },{
104399  *             html: 'Cell D content'
104400  *         }],
104401  *         renderTo: Ext.getBody()
104402  *     });
104403  */
104404 Ext.define('Ext.layout.container.Table', {
104405
104406     /* Begin Definitions */
104407
104408     alias: ['layout.table'],
104409     extend: 'Ext.layout.container.Auto',
104410     alternateClassName: 'Ext.layout.TableLayout',
104411
104412     /* End Definitions */
104413
104414     /**
104415      * @cfg {Number} columns
104416      * The total number of columns to create in the table for this layout. If not specified, all Components added to
104417      * this layout will be rendered into a single row using one column per Component.
104418      */
104419
104420     // private
104421     monitorResize:false,
104422
104423     type: 'table',
104424
104425     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
104426     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
104427     autoSize: true,
104428
104429     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
104430
104431     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
104432     tableCls: Ext.baseCSSPrefix + 'table-layout',
104433     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
104434
104435     /**
104436      * @cfg {Object} tableAttrs
104437      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
104438      * create the layout's `<table>` element. Example:
104439      *
104440      *     {
104441      *         xtype: 'panel',
104442      *         layout: {
104443      *             type: 'table',
104444      *             columns: 3,
104445      *             tableAttrs: {
104446      *                 style: {
104447      *                     width: '100%'
104448      *                 }
104449      *             }
104450      *         }
104451      *     }
104452      */
104453     tableAttrs:null,
104454
104455     /**
104456      * @cfg {Object} trAttrs
104457      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
104458      * create the layout's <tr> elements.
104459      */
104460
104461     /**
104462      * @cfg {Object} tdAttrs
104463      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
104464      * create the layout's <td> elements.
104465      */
104466
104467     /**
104468      * @private
104469      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
104470      * location in the table structure.
104471      */
104472     renderItems: function(items) {
104473         var tbody = this.getTable().tBodies[0],
104474             rows = tbody.rows,
104475             i = 0,
104476             len = items.length,
104477             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
104478
104479         // Calculate the correct cell structure for the current items
104480         cells = this.calculateCells(items);
104481
104482         // Loop over each cell and compare to the current cells in the table, inserting/
104483         // removing/moving cells as needed, and making sure each item is rendered into
104484         // the correct cell.
104485         for (; i < len; i++) {
104486             curCell = cells[i];
104487             rowIdx = curCell.rowIdx;
104488             cellIdx = curCell.cellIdx;
104489             item = items[i];
104490
104491             // If no row present, create and insert one
104492             trEl = rows[rowIdx];
104493             if (!trEl) {
104494                 trEl = tbody.insertRow(rowIdx);
104495                 if (this.trAttrs) {
104496                     trEl.set(this.trAttrs);
104497                 }
104498             }
104499
104500             // If no cell present, create and insert one
104501             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
104502             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
104503                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
104504                 itemCt.setWidth(null);
104505             }
104506
104507             // Render or move the component into the cell
104508             if (!item.rendered) {
104509                 this.renderItem(item, itemCt, 0);
104510             }
104511             else if (!this.isValidParent(item, itemCt, 0)) {
104512                 this.moveItem(item, itemCt, 0);
104513             }
104514
104515             // Set the cell properties
104516             if (this.tdAttrs) {
104517                 tdEl.set(this.tdAttrs);
104518             }
104519             tdEl.set({
104520                 colSpan: item.colspan || 1,
104521                 rowSpan: item.rowspan || 1,
104522                 id: item.cellId || '',
104523                 cls: this.cellCls + ' ' + (item.cellCls || '')
104524             });
104525
104526             // If at the end of a row, remove any extra cells
104527             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
104528                 cellIdx++;
104529                 while (trEl.cells[cellIdx]) {
104530                     trEl.deleteCell(cellIdx);
104531                 }
104532             }
104533         }
104534
104535         // Delete any extra rows
104536         rowIdx++;
104537         while (tbody.rows[rowIdx]) {
104538             tbody.deleteRow(rowIdx);
104539         }
104540     },
104541
104542     afterLayout: function() {
104543         this.callParent();
104544
104545         if (this.needsDivWrap()) {
104546             // set wrapper div width to match layed out item - see docs below
104547             Ext.Array.forEach(this.getLayoutItems(), function(item) {
104548                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
104549             });
104550         }
104551     },
104552
104553     /**
104554      * @private
104555      * Determine the row and cell indexes for each component, taking into consideration
104556      * the number of columns and each item's configured colspan/rowspan values.
104557      * @param {Array} items The layout components
104558      * @return {Object[]} List of row and cell indexes for each of the components
104559      */
104560     calculateCells: function(items) {
104561         var cells = [],
104562             rowIdx = 0,
104563             colIdx = 0,
104564             cellIdx = 0,
104565             totalCols = this.columns || Infinity,
104566             rowspans = [], //rolling list of active rowspans for each column
104567             i = 0, j,
104568             len = items.length,
104569             item;
104570
104571         for (; i < len; i++) {
104572             item = items[i];
104573
104574             // Find the first available row/col slot not taken up by a spanning cell
104575             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
104576                 if (colIdx >= totalCols) {
104577                     // move down to next row
104578                     colIdx = 0;
104579                     cellIdx = 0;
104580                     rowIdx++;
104581
104582                     // decrement all rowspans
104583                     for (j = 0; j < totalCols; j++) {
104584                         if (rowspans[j] > 0) {
104585                             rowspans[j]--;
104586                         }
104587                     }
104588                 } else {
104589                     colIdx++;
104590                 }
104591             }
104592
104593             // Add the cell info to the list
104594             cells.push({
104595                 rowIdx: rowIdx,
104596                 cellIdx: cellIdx
104597             });
104598
104599             // Increment
104600             for (j = item.colspan || 1; j; --j) {
104601                 rowspans[colIdx] = item.rowspan || 1;
104602                 ++colIdx;
104603             }
104604             ++cellIdx;
104605         }
104606
104607         return cells;
104608     },
104609
104610     /**
104611      * @private
104612      * Return the layout's table element, creating it if necessary.
104613      */
104614     getTable: function() {
104615         var table = this.table;
104616         if (!table) {
104617             table = this.table = this.getTarget().createChild(
104618                 Ext.apply({
104619                     tag: 'table',
104620                     role: 'presentation',
104621                     cls: this.tableCls,
104622                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
104623                     cn: {tag: 'tbody'}
104624                 }, this.tableAttrs),
104625                 null, true
104626             );
104627         }
104628         return table;
104629     },
104630
104631     /**
104632      * @private
104633      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
104634      * will include that padding in the size of the cell, making it always larger than the
104635      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
104636      * and then set that div's width to match the item rendered within it afterLayout. This method
104637      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
104638      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
104639      */
104640     needsDivWrap: function() {
104641         return Ext.isOpera10_5;
104642     }
104643 });
104644 /**
104645  * A base class for all menu items that require menu-related functionality such as click handling,
104646  * sub-menus, icons, etc.
104647  *
104648  *     @example
104649  *     Ext.create('Ext.menu.Menu', {
104650  *         width: 100,
104651  *         height: 100,
104652  *         floating: false,  // usually you want this set to True (default)
104653  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
104654  *         items: [{
104655  *             text: 'icon item',
104656  *             iconCls: 'add16'
104657  *         },{
104658  *             text: 'text item'
104659  *         },{
104660  *             text: 'plain item',
104661  *             plain: true
104662  *         }]
104663  *     });
104664  */
104665 Ext.define('Ext.menu.Item', {
104666     extend: 'Ext.Component',
104667     alias: 'widget.menuitem',
104668     alternateClassName: 'Ext.menu.TextItem',
104669
104670     /**
104671      * @property {Boolean} activated
104672      * Whether or not this item is currently activated
104673      */
104674
104675     /**
104676      * @property {Ext.menu.Menu} parentMenu
104677      * The parent Menu of this item.
104678      */
104679
104680     /**
104681      * @cfg {String} activeCls
104682      * The CSS class added to the menu item when the item is activated (focused/mouseover).
104683      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
104684      */
104685     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
104686
104687     /**
104688      * @cfg {String} ariaRole @hide
104689      */
104690     ariaRole: 'menuitem',
104691
104692     /**
104693      * @cfg {Boolean} canActivate
104694      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
104695      */
104696     canActivate: true,
104697
104698     /**
104699      * @cfg {Number} clickHideDelay
104700      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
104701      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
104702      */
104703     clickHideDelay: 1,
104704
104705     /**
104706      * @cfg {Boolean} destroyMenu
104707      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
104708      */
104709     destroyMenu: true,
104710
104711     /**
104712      * @cfg {String} disabledCls
104713      * The CSS class added to the menu item when the item is disabled.
104714      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
104715      */
104716     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
104717
104718     /**
104719      * @cfg {String} href
104720      * The href attribute to use for the underlying anchor link. Defaults to `#`.
104721      * @markdown
104722      */
104723
104724      /**
104725       * @cfg {String} hrefTarget
104726       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
104727       * @markdown
104728       */
104729
104730     /**
104731      * @cfg {Boolean} hideOnClick
104732      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
104733      * @markdown
104734      */
104735     hideOnClick: true,
104736
104737     /**
104738      * @cfg {String} icon
104739      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
104740      * @markdown
104741      */
104742
104743     /**
104744      * @cfg {String} iconCls
104745      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
104746      * @markdown
104747      */
104748
104749     isMenuItem: true,
104750
104751     /**
104752      * @cfg {Mixed} menu
104753      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
104754      * which will act as a sub-menu to this item.
104755      * @markdown
104756      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
104757      */
104758
104759     /**
104760      * @cfg {String} menuAlign
104761      * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
104762      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
104763      * @markdown
104764      */
104765     menuAlign: 'tl-tr?',
104766
104767     /**
104768      * @cfg {Number} menuExpandDelay
104769      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
104770      * @markdown
104771      */
104772     menuExpandDelay: 200,
104773
104774     /**
104775      * @cfg {Number} menuHideDelay
104776      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
104777      * @markdown
104778      */
104779     menuHideDelay: 200,
104780
104781     /**
104782      * @cfg {Boolean} plain
104783      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
104784      * @markdown
104785      */
104786
104787     renderTpl: [
104788         '<tpl if="plain">',
104789             '{text}',
104790         '</tpl>',
104791         '<tpl if="!plain">',
104792             '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
104793                 '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
104794                 '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
104795                 '<tpl if="menu">',
104796                     '<img id="{id}-arrowEl" src="{blank}" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
104797                 '</tpl>',
104798             '</a>',
104799         '</tpl>'
104800     ],
104801
104802     maskOnDisable: false,
104803
104804     /**
104805      * @cfg {String} text
104806      * The text/html to display in this item. Defaults to `undefined`.
104807      * @markdown
104808      */
104809
104810     activate: function() {
104811         var me = this;
104812
104813         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
104814             me.el.addCls(me.activeCls);
104815             me.focus();
104816             me.activated = true;
104817             me.fireEvent('activate', me);
104818         }
104819     },
104820
104821     blur: function() {
104822         this.$focused = false;
104823         this.callParent(arguments);
104824     },
104825
104826     deactivate: function() {
104827         var me = this;
104828
104829         if (me.activated) {
104830             me.el.removeCls(me.activeCls);
104831             me.blur();
104832             me.hideMenu();
104833             me.activated = false;
104834             me.fireEvent('deactivate', me);
104835         }
104836     },
104837
104838     deferExpandMenu: function() {
104839         var me = this;
104840
104841         if (!me.menu.rendered || !me.menu.isVisible()) {
104842             me.parentMenu.activeChild = me.menu;
104843             me.menu.parentItem = me;
104844             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
104845             me.menu.showBy(me, me.menuAlign);
104846         }
104847     },
104848
104849     deferHideMenu: function() {
104850         if (this.menu.isVisible()) {
104851             this.menu.hide();
104852         }
104853     },
104854
104855     deferHideParentMenus: function() {
104856         Ext.menu.Manager.hideAll();
104857     },
104858
104859     expandMenu: function(delay) {
104860         var me = this;
104861
104862         if (me.menu) {
104863             clearTimeout(me.hideMenuTimer);
104864             if (delay === 0) {
104865                 me.deferExpandMenu();
104866             } else {
104867                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
104868             }
104869         }
104870     },
104871
104872     focus: function() {
104873         this.$focused = true;
104874         this.callParent(arguments);
104875     },
104876
104877     getRefItems: function(deep){
104878         var menu = this.menu,
104879             items;
104880
104881         if (menu) {
104882             items = menu.getRefItems(deep);
104883             items.unshift(menu);
104884         }
104885         return items || [];
104886     },
104887
104888     hideMenu: function(delay) {
104889         var me = this;
104890
104891         if (me.menu) {
104892             clearTimeout(me.expandMenuTimer);
104893             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
104894         }
104895     },
104896
104897     initComponent: function() {
104898         var me = this,
104899             prefix = Ext.baseCSSPrefix,
104900             cls = [prefix + 'menu-item'];
104901
104902         me.addEvents(
104903             /**
104904              * @event activate
104905              * Fires when this item is activated
104906              * @param {Ext.menu.Item} item The activated item
104907              */
104908             'activate',
104909
104910             /**
104911              * @event click
104912              * Fires when this item is clicked
104913              * @param {Ext.menu.Item} item The item that was clicked
104914              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
104915              */
104916             'click',
104917
104918             /**
104919              * @event deactivate
104920              * Fires when this tiem is deactivated
104921              * @param {Ext.menu.Item} item The deactivated item
104922              */
104923             'deactivate'
104924         );
104925
104926         if (me.plain) {
104927             cls.push(prefix + 'menu-item-plain');
104928         }
104929
104930         if (me.cls) {
104931             cls.push(me.cls);
104932         }
104933
104934         me.cls = cls.join(' ');
104935
104936         if (me.menu) {
104937             me.menu = Ext.menu.Manager.get(me.menu);
104938         }
104939
104940         me.callParent(arguments);
104941     },
104942
104943     onClick: function(e) {
104944         var me = this;
104945
104946         if (!me.href) {
104947             e.stopEvent();
104948         }
104949
104950         if (me.disabled) {
104951             return;
104952         }
104953
104954         if (me.hideOnClick) {
104955             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
104956         }
104957
104958         Ext.callback(me.handler, me.scope || me, [me, e]);
104959         me.fireEvent('click', me, e);
104960
104961         if (!me.hideOnClick) {
104962             me.focus();
104963         }
104964     },
104965
104966     onDestroy: function() {
104967         var me = this;
104968
104969         clearTimeout(me.expandMenuTimer);
104970         clearTimeout(me.hideMenuTimer);
104971         clearTimeout(me.deferHideParentMenusTimer);
104972
104973         if (me.menu) {
104974             delete me.menu.parentItem;
104975             delete me.menu.parentMenu;
104976             delete me.menu.ownerCt;
104977             if (me.destroyMenu !== false) {
104978                 me.menu.destroy();
104979             }
104980         }
104981         me.callParent(arguments);
104982     },
104983
104984     onRender: function(ct, pos) {
104985         var me = this,
104986             blank = Ext.BLANK_IMAGE_URL;
104987
104988         Ext.applyIf(me.renderData, {
104989             href: me.href || '#',
104990             hrefTarget: me.hrefTarget,
104991             icon: me.icon || blank,
104992             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
104993             menu: Ext.isDefined(me.menu),
104994             plain: me.plain,
104995             text: me.text,
104996             blank: blank
104997         });
104998
104999         me.addChildEls('itemEl', 'iconEl', 'textEl', 'arrowEl');
105000
105001         me.callParent(arguments);
105002     },
105003
105004     /**
105005      * Sets the {@link #click} handler of this item
105006      * @param {Function} fn The handler function
105007      * @param {Object} scope (optional) The scope of the handler function
105008      */
105009     setHandler: function(fn, scope) {
105010         this.handler = fn || null;
105011         this.scope = scope;
105012     },
105013
105014     /**
105015      * Sets the {@link #iconCls} of this item
105016      * @param {String} iconCls The CSS class to set to {@link #iconCls}
105017      */
105018     setIconCls: function(iconCls) {
105019         var me = this;
105020
105021         if (me.iconEl) {
105022             if (me.iconCls) {
105023                 me.iconEl.removeCls(me.iconCls);
105024             }
105025
105026             if (iconCls) {
105027                 me.iconEl.addCls(iconCls);
105028             }
105029         }
105030
105031         me.iconCls = iconCls;
105032     },
105033
105034     /**
105035      * Sets the {@link #text} of this item
105036      * @param {String} text The {@link #text}
105037      */
105038     setText: function(text) {
105039         var me = this,
105040             el = me.textEl || me.el;
105041
105042         me.text = text;
105043
105044         if (me.rendered) {
105045             el.update(text || '');
105046             // cannot just call doComponentLayout due to stretchmax
105047             me.ownerCt.redoComponentLayout();
105048         }
105049     }
105050 });
105051
105052 /**
105053  * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
105054  *
105055  *     @example
105056  *     Ext.create('Ext.menu.Menu', {
105057  *         width: 100,
105058  *         height: 110,
105059  *         floating: false,  // usually you want this set to True (default)
105060  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
105061  *         items: [{
105062  *             xtype: 'menucheckitem',
105063  *             text: 'select all'
105064  *         },{
105065  *             xtype: 'menucheckitem',
105066  *             text: 'select specific',
105067  *         },{
105068  *             iconCls: 'add16',
105069  *             text: 'icon item'
105070  *         },{
105071  *             text: 'regular item'
105072  *         }]
105073  *     });
105074  */
105075 Ext.define('Ext.menu.CheckItem', {
105076     extend: 'Ext.menu.Item',
105077     alias: 'widget.menucheckitem',
105078
105079     /**
105080      * @cfg {String} checkedCls
105081      * The CSS class used by {@link #cls} to show the checked state.
105082      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
105083      */
105084     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
105085     /**
105086      * @cfg {String} uncheckedCls
105087      * The CSS class used by {@link #cls} to show the unchecked state.
105088      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
105089      */
105090     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
105091     /**
105092      * @cfg {String} groupCls
105093      * The CSS class applied to this item's icon image to denote being a part of a radio group.
105094      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
105095      * Any specified {@link #iconCls} overrides this.
105096      */
105097     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
105098
105099     /**
105100      * @cfg {Boolean} hideOnClick
105101      * Whether to not to hide the owning menu when this item is clicked.
105102      * Defaults to `false` for checkbox items, and to `true` for radio group items.
105103      */
105104     hideOnClick: false,
105105
105106     afterRender: function() {
105107         var me = this;
105108         this.callParent();
105109         me.checked = !me.checked;
105110         me.setChecked(!me.checked, true);
105111     },
105112
105113     initComponent: function() {
105114         var me = this;
105115         me.addEvents(
105116             /**
105117              * @event beforecheckchange
105118              * Fires before a change event. Return false to cancel.
105119              * @param {Ext.menu.CheckItem} this
105120              * @param {Boolean} checked
105121              */
105122             'beforecheckchange',
105123
105124             /**
105125              * @event checkchange
105126              * Fires after a change event.
105127              * @param {Ext.menu.CheckItem} this
105128              * @param {Boolean} checked
105129              */
105130             'checkchange'
105131         );
105132
105133         me.callParent(arguments);
105134
105135         Ext.menu.Manager.registerCheckable(me);
105136
105137         if (me.group) {
105138             if (!me.iconCls) {
105139                 me.iconCls = me.groupCls;
105140             }
105141             if (me.initialConfig.hideOnClick !== false) {
105142                 me.hideOnClick = true;
105143             }
105144         }
105145     },
105146
105147     /**
105148      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
105149      * will still be accessible
105150      */
105151     disableCheckChange: function() {
105152         var me = this;
105153
105154         if (me.iconEl) {
105155             me.iconEl.addCls(me.disabledCls);
105156         }
105157         me.checkChangeDisabled = true;
105158     },
105159
105160     /**
105161      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
105162      */
105163     enableCheckChange: function() {
105164         var me = this;
105165
105166         me.iconEl.removeCls(me.disabledCls);
105167         me.checkChangeDisabled = false;
105168     },
105169
105170     onClick: function(e) {
105171         var me = this;
105172         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
105173             me.setChecked(!me.checked);
105174         }
105175         this.callParent([e]);
105176     },
105177
105178     onDestroy: function() {
105179         Ext.menu.Manager.unregisterCheckable(this);
105180         this.callParent(arguments);
105181     },
105182
105183     /**
105184      * Sets the checked state of the item
105185      * @param {Boolean} checked True to check, false to uncheck
105186      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
105187      */
105188     setChecked: function(checked, suppressEvents) {
105189         var me = this;
105190         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
105191             if (me.el) {
105192                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
105193             }
105194             me.checked = checked;
105195             Ext.menu.Manager.onCheckChange(me, checked);
105196             if (!suppressEvents) {
105197                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
105198                 me.fireEvent('checkchange', me, checked);
105199             }
105200         }
105201     }
105202 });
105203
105204 /**
105205  * @class Ext.menu.KeyNav
105206  * @private
105207  */
105208 Ext.define('Ext.menu.KeyNav', {
105209     extend: 'Ext.util.KeyNav',
105210
105211     requires: ['Ext.FocusManager'],
105212     
105213     constructor: function(menu) {
105214         var me = this;
105215
105216         me.menu = menu;
105217         me.callParent([menu.el, {
105218             down: me.down,
105219             enter: me.enter,
105220             esc: me.escape,
105221             left: me.left,
105222             right: me.right,
105223             space: me.enter,
105224             tab: me.tab,
105225             up: me.up
105226         }]);
105227     },
105228
105229     down: function(e) {
105230         var me = this,
105231             fi = me.menu.focusedItem;
105232
105233         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
105234             return true;
105235         }
105236         me.focusNextItem(1);
105237     },
105238
105239     enter: function(e) {
105240         var menu = this.menu,
105241             focused = menu.focusedItem;
105242  
105243         if (menu.activeItem) {
105244             menu.onClick(e);
105245         } else if (focused && focused.isFormField) {
105246             // prevent stopEvent being called
105247             return true;
105248         }
105249     },
105250
105251     escape: function(e) {
105252         Ext.menu.Manager.hideAll();
105253     },
105254
105255     focusNextItem: function(step) {
105256         var menu = this.menu,
105257             items = menu.items,
105258             focusedItem = menu.focusedItem,
105259             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
105260             idx = startIdx + step;
105261
105262         while (idx != startIdx) {
105263             if (idx < 0) {
105264                 idx = items.length - 1;
105265             } else if (idx >= items.length) {
105266                 idx = 0;
105267             }
105268
105269             var item = items.getAt(idx);
105270             if (menu.canActivateItem(item)) {
105271                 menu.setActiveItem(item);
105272                 break;
105273             }
105274             idx += step;
105275         }
105276     },
105277
105278     isWhitelisted: function(item) {
105279         return Ext.FocusManager.isWhitelisted(item);
105280     },
105281
105282     left: function(e) {
105283         var menu = this.menu,
105284             fi = menu.focusedItem,
105285             ai = menu.activeItem;
105286
105287         if (fi && this.isWhitelisted(fi)) {
105288             return true;
105289         }
105290
105291         menu.hide();
105292         if (menu.parentMenu) {
105293             menu.parentMenu.focus();
105294         }
105295     },
105296
105297     right: function(e) {
105298         var menu = this.menu,
105299             fi = menu.focusedItem,
105300             ai = menu.activeItem,
105301             am;
105302
105303         if (fi && this.isWhitelisted(fi)) {
105304             return true;
105305         }
105306
105307         if (ai) {
105308             am = menu.activeItem.menu;
105309             if (am) {
105310                 ai.expandMenu(0);
105311                 Ext.defer(function() {
105312                     am.setActiveItem(am.items.getAt(0));
105313                 }, 25);
105314             }
105315         }
105316     },
105317
105318     tab: function(e) {
105319         var me = this;
105320
105321         if (e.shiftKey) {
105322             me.up(e);
105323         } else {
105324             me.down(e);
105325         }
105326     },
105327
105328     up: function(e) {
105329         var me = this,
105330             fi = me.menu.focusedItem;
105331
105332         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
105333             return true;
105334         }
105335         me.focusNextItem(-1);
105336     }
105337 });
105338 /**
105339  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
105340  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
105341  *
105342  *     @example
105343  *     Ext.create('Ext.menu.Menu', {
105344  *         width: 100,
105345  *         height: 100,
105346  *         floating: false,  // usually you want this set to True (default)
105347  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
105348  *         items: [{
105349  *             text: 'icon item',
105350  *             iconCls: 'add16'
105351  *         },{
105352  *             xtype: 'menuseparator'
105353  *         },{
105354  *            text: 'seperator above',
105355  *         },{
105356  *            text: 'regular item',
105357  *         }]
105358  *     });
105359  */
105360 Ext.define('Ext.menu.Separator', {
105361     extend: 'Ext.menu.Item',
105362     alias: 'widget.menuseparator',
105363
105364     /**
105365      * @cfg {String} activeCls @hide
105366      */
105367
105368     /**
105369      * @cfg {Boolean} canActivate @hide
105370      */
105371     canActivate: false,
105372
105373     /**
105374      * @cfg {Boolean} clickHideDelay @hide
105375      */
105376
105377     /**
105378      * @cfg {Boolean} destroyMenu @hide
105379      */
105380
105381     /**
105382      * @cfg {Boolean} disabledCls @hide
105383      */
105384
105385     focusable: false,
105386
105387     /**
105388      * @cfg {String} href @hide
105389      */
105390
105391     /**
105392      * @cfg {String} hrefTarget @hide
105393      */
105394
105395     /**
105396      * @cfg {Boolean} hideOnClick @hide
105397      */
105398     hideOnClick: false,
105399
105400     /**
105401      * @cfg {String} icon @hide
105402      */
105403
105404     /**
105405      * @cfg {String} iconCls @hide
105406      */
105407
105408     /**
105409      * @cfg {Object} menu @hide
105410      */
105411
105412     /**
105413      * @cfg {String} menuAlign @hide
105414      */
105415
105416     /**
105417      * @cfg {Number} menuExpandDelay @hide
105418      */
105419
105420     /**
105421      * @cfg {Number} menuHideDelay @hide
105422      */
105423
105424     /**
105425      * @cfg {Boolean} plain @hide
105426      */
105427     plain: true,
105428
105429     /**
105430      * @cfg {String} separatorCls
105431      * The CSS class used by the separator item to show the incised line.
105432      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
105433      */
105434     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
105435
105436     /**
105437      * @cfg {String} text @hide
105438      */
105439     text: '&#160;',
105440
105441     onRender: function(ct, pos) {
105442         var me = this,
105443             sepCls = me.separatorCls;
105444
105445         me.cls += ' ' + sepCls;
105446
105447         me.callParent(arguments);
105448     }
105449 });
105450 /**
105451  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
105452  *
105453  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
105454  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
105455  *
105456  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
105457  * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
105458  * in line with the other menu items.
105459  *
105460  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
105461  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
105462  *
105463  *     @example
105464  *     Ext.create('Ext.menu.Menu', {
105465  *         width: 100,
105466  *         height: 100,
105467  *         margin: '0 0 10 0',
105468  *         floating: false,  // usually you want this set to True (default)
105469  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
105470  *         items: [{
105471  *             text: 'regular item 1'
105472  *         },{
105473  *             text: 'regular item 2'
105474  *         },{
105475  *             text: 'regular item 3'
105476  *         }]
105477  *     });
105478  *
105479  *     Ext.create('Ext.menu.Menu', {
105480  *         width: 100,
105481  *         height: 100,
105482  *         plain: true,
105483  *         floating: false,  // usually you want this set to True (default)
105484  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
105485  *         items: [{
105486  *             text: 'plain item 1'
105487  *         },{
105488  *             text: 'plain item 2'
105489  *         },{
105490  *             text: 'plain item 3'
105491  *         }]
105492  *     });
105493  */
105494 Ext.define('Ext.menu.Menu', {
105495     extend: 'Ext.panel.Panel',
105496     alias: 'widget.menu',
105497     requires: [
105498         'Ext.layout.container.Fit',
105499         'Ext.layout.container.VBox',
105500         'Ext.menu.CheckItem',
105501         'Ext.menu.Item',
105502         'Ext.menu.KeyNav',
105503         'Ext.menu.Manager',
105504         'Ext.menu.Separator'
105505     ],
105506
105507     /**
105508      * @property {Ext.menu.Menu} parentMenu
105509      * The parent Menu of this Menu.
105510      */
105511
105512     /**
105513      * @cfg {Boolean} allowOtherMenus
105514      * True to allow multiple menus to be displayed at the same time.
105515      */
105516     allowOtherMenus: false,
105517
105518     /**
105519      * @cfg {String} ariaRole @hide
105520      */
105521     ariaRole: 'menu',
105522
105523     /**
105524      * @cfg {Boolean} autoRender @hide
105525      * floating is true, so autoRender always happens
105526      */
105527
105528     /**
105529      * @cfg {String} defaultAlign
105530      * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
105531      * relative to its element of origin.
105532      */
105533     defaultAlign: 'tl-bl?',
105534
105535     /**
105536      * @cfg {Boolean} floating
105537      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
105538      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
105539      * used as a child item of another {@link Ext.container.Container Container}.
105540      */
105541     floating: true,
105542
105543     /**
105544      * @cfg {Boolean} @hide
105545      * Menus are constrained to the document body by default
105546      */
105547     constrain: true,
105548
105549     /**
105550      * @cfg {Boolean} [hidden=undefined]
105551      * True to initially render the Menu as hidden, requiring to be shown manually.
105552      *
105553      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
105554      */
105555     hidden: true,
105556
105557     hideMode: 'visibility',
105558
105559     /**
105560      * @cfg {Boolean} ignoreParentClicks
105561      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
105562      * so that the submenu is not dismissed when clicking the parent item.
105563      */
105564     ignoreParentClicks: false,
105565
105566     isMenu: true,
105567
105568     /**
105569      * @cfg {String/Object} layout @hide
105570      */
105571
105572     /**
105573      * @cfg {Boolean} showSeparator
105574      * True to show the icon separator.
105575      */
105576     showSeparator : true,
105577
105578     /**
105579      * @cfg {Number} minWidth
105580      * The minimum width of the Menu.
105581      */
105582     minWidth: 120,
105583
105584     /**
105585      * @cfg {Boolean} [plain=false]
105586      * True to remove the incised line down the left side of the menu and to not indent general Component items.
105587      */
105588
105589     initComponent: function() {
105590         var me = this,
105591             prefix = Ext.baseCSSPrefix,
105592             cls = [prefix + 'menu'],
105593             bodyCls = me.bodyCls ? [me.bodyCls] : [];
105594
105595         me.addEvents(
105596             /**
105597              * @event click
105598              * Fires when this menu is clicked
105599              * @param {Ext.menu.Menu} menu The menu which has been clicked
105600              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
105601              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
105602              */
105603             'click',
105604
105605             /**
105606              * @event mouseenter
105607              * Fires when the mouse enters this menu
105608              * @param {Ext.menu.Menu} menu The menu
105609              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
105610              */
105611             'mouseenter',
105612
105613             /**
105614              * @event mouseleave
105615              * Fires when the mouse leaves this menu
105616              * @param {Ext.menu.Menu} menu The menu
105617              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
105618              */
105619             'mouseleave',
105620
105621             /**
105622              * @event mouseover
105623              * Fires when the mouse is hovering over this menu
105624              * @param {Ext.menu.Menu} menu The menu
105625              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
105626              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
105627              */
105628             'mouseover'
105629         );
105630
105631         Ext.menu.Manager.register(me);
105632
105633         // Menu classes
105634         if (me.plain) {
105635             cls.push(prefix + 'menu-plain');
105636         }
105637         me.cls = cls.join(' ');
105638
105639         // Menu body classes
105640         bodyCls.unshift(prefix + 'menu-body');
105641         me.bodyCls = bodyCls.join(' ');
105642
105643         // Internal vbox layout, with scrolling overflow
105644         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
105645         // options if we wish to allow for such configurations on the Menu.
105646         // e.g., scrolling speed, vbox align stretch, etc.
105647         me.layout = {
105648             type: 'vbox',
105649             align: 'stretchmax',
105650             autoSize: true,
105651             clearInnerCtOnLayout: true,
105652             overflowHandler: 'Scroller'
105653         };
105654
105655         // hidden defaults to false if floating is configured as false
105656         if (me.floating === false && me.initialConfig.hidden !== true) {
105657             me.hidden = false;
105658         }
105659
105660         me.callParent(arguments);
105661
105662         me.on('beforeshow', function() {
105663             var hasItems = !!me.items.length;
105664             // FIXME: When a menu has its show cancelled because of no items, it
105665             // gets a visibility: hidden applied to it (instead of the default display: none)
105666             // Not sure why, but we remove this style when we want to show again.
105667             if (hasItems && me.rendered) {
105668                 me.el.setStyle('visibility', null);
105669             }
105670             return hasItems;
105671         });
105672     },
105673
105674     afterRender: function(ct) {
105675         var me = this,
105676             prefix = Ext.baseCSSPrefix,
105677             space = '&#160;';
105678
105679         me.callParent(arguments);
105680
105681         // TODO: Move this to a subTemplate When we support them in the future
105682         if (me.showSeparator) {
105683             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
105684                 cls: prefix + 'menu-icon-separator',
105685                 html: space
105686             });
105687         }
105688
105689         me.focusEl = me.el.createChild({
105690             cls: prefix + 'menu-focus',
105691             tabIndex: '-1',
105692             html: space
105693         });
105694
105695         me.mon(me.el, {
105696             click: me.onClick,
105697             mouseover: me.onMouseOver,
105698             scope: me
105699         });
105700         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
105701
105702         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
105703             me.iconSepEl.setHeight(me.el.getHeight());
105704         }
105705
105706         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
105707     },
105708
105709     afterLayout: function() {
105710         var me = this;
105711         me.callParent(arguments);
105712
105713         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
105714         // floating elements inherit their parent's width, making them the width of
105715         // document.body instead of the width of their contents.
105716         // This includes left/right dock items.
105717         if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
105718             var innerCt = me.layout.getRenderTarget(),
105719                 innerCtWidth = 0,
105720                 dis = me.dockedItems,
105721                 l = dis.length,
105722                 i = 0,
105723                 di, clone, newWidth;
105724
105725             innerCtWidth = innerCt.getWidth();
105726
105727             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
105728
105729             // First set the body to the new width
105730             me.body.setWidth(newWidth);
105731
105732             // Now we calculate additional width (docked items) and set the el's width
105733             for (; i < l, di = dis.getAt(i); i++) {
105734                 if (di.dock == 'left' || di.dock == 'right') {
105735                     newWidth += di.getWidth();
105736                 }
105737             }
105738             me.el.setWidth(newWidth);
105739         }
105740     },
105741     
105742     getBubbleTarget: function(){
105743         return this.parentMenu || this.callParent();
105744     },
105745
105746     /**
105747      * Returns whether a menu item can be activated or not.
105748      * @return {Boolean}
105749      */
105750     canActivateItem: function(item) {
105751         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
105752     },
105753
105754     /**
105755      * Deactivates the current active item on the menu, if one exists.
105756      */
105757     deactivateActiveItem: function() {
105758         var me = this;
105759
105760         if (me.activeItem) {
105761             me.activeItem.deactivate();
105762             if (!me.activeItem.activated) {
105763                 delete me.activeItem;
105764             }
105765         }
105766
105767         // only blur if focusedItem is not a filter
105768         if (me.focusedItem && !me.filtered) {
105769             me.focusedItem.blur();
105770             if (!me.focusedItem.$focused) {
105771                 delete me.focusedItem;
105772             }
105773         }
105774     },
105775
105776     clearStretch: function () {
105777         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
105778         // reconsider them unless we clear the dimensions on the el's here:
105779         if (this.rendered) {
105780             this.items.each(function (item) {
105781                 // each menuItem component needs to layout again, so clear its cache
105782                 if (item.componentLayout) {
105783                     delete item.componentLayout.lastComponentSize;
105784                 }
105785                 if (item.el) {
105786                     item.el.setWidth(null);
105787                 }
105788             });
105789         }
105790     },
105791
105792     onAdd: function () {
105793         var me = this;
105794
105795         me.clearStretch();
105796         me.callParent(arguments);
105797
105798         if (Ext.isIE6 || Ext.isIE7) {
105799             // TODO - why does this need to be done (and not ok to do now)?
105800             Ext.Function.defer(me.doComponentLayout, 10, me);
105801         }
105802     },
105803
105804     onRemove: function () {
105805         this.clearStretch();
105806         this.callParent(arguments);
105807
105808     },
105809
105810     redoComponentLayout: function () {
105811         if (this.rendered) {
105812             this.clearStretch();
105813             this.doComponentLayout();
105814         }
105815     },
105816
105817     // inherit docs
105818     getFocusEl: function() {
105819         return this.focusEl;
105820     },
105821
105822     // inherit docs
105823     hide: function() {
105824         this.deactivateActiveItem();
105825         this.callParent(arguments);
105826     },
105827
105828     // private
105829     getItemFromEvent: function(e) {
105830         return this.getChildByElement(e.getTarget());
105831     },
105832
105833     lookupComponent: function(cmp) {
105834         var me = this;
105835
105836         if (Ext.isString(cmp)) {
105837             cmp = me.lookupItemFromString(cmp);
105838         } else if (Ext.isObject(cmp)) {
105839             cmp = me.lookupItemFromObject(cmp);
105840         }
105841
105842         // Apply our minWidth to all of our child components so it's accounted
105843         // for in our VBox layout
105844         cmp.minWidth = cmp.minWidth || me.minWidth;
105845
105846         return cmp;
105847     },
105848
105849     // private
105850     lookupItemFromObject: function(cmp) {
105851         var me = this,
105852             prefix = Ext.baseCSSPrefix,
105853             cls,
105854             intercept;
105855
105856         if (!cmp.isComponent) {
105857             if (!cmp.xtype) {
105858                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
105859             } else {
105860                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
105861             }
105862         }
105863
105864         if (cmp.isMenuItem) {
105865             cmp.parentMenu = me;
105866         }
105867
105868         if (!cmp.isMenuItem && !cmp.dock) {
105869             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
105870             intercept = Ext.Function.createInterceptor;
105871
105872             // Wrap focus/blur to control component focus
105873             cmp.focus = intercept(cmp.focus, function() {
105874                 this.$focused = true;
105875             }, cmp);
105876             cmp.blur = intercept(cmp.blur, function() {
105877                 this.$focused = false;
105878             }, cmp);
105879
105880             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
105881                 cls.push(prefix + 'menu-item-indent');
105882             }
105883
105884             if (cmp.rendered) {
105885                 cmp.el.addCls(cls);
105886             } else {
105887                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
105888             }
105889             cmp.isMenuItem = true;
105890         }
105891         return cmp;
105892     },
105893
105894     // private
105895     lookupItemFromString: function(cmp) {
105896         return (cmp == 'separator' || cmp == '-') ?
105897             Ext.createWidget('menuseparator')
105898             : Ext.createWidget('menuitem', {
105899                 canActivate: false,
105900                 hideOnClick: false,
105901                 plain: true,
105902                 text: cmp
105903             });
105904     },
105905
105906     onClick: function(e) {
105907         var me = this,
105908             item;
105909
105910         if (me.disabled) {
105911             e.stopEvent();
105912             return;
105913         }
105914
105915         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
105916             item = me.getItemFromEvent(e) || me.activeItem;
105917
105918             if (item) {
105919                 if (item.getXTypes().indexOf('menuitem') >= 0) {
105920                     if (!item.menu || !me.ignoreParentClicks) {
105921                         item.onClick(e);
105922                     } else {
105923                         e.stopEvent();
105924                     }
105925                 }
105926             }
105927             me.fireEvent('click', me, item, e);
105928         }
105929     },
105930
105931     onDestroy: function() {
105932         var me = this;
105933
105934         Ext.menu.Manager.unregister(me);
105935         if (me.rendered) {
105936             me.el.un(me.mouseMonitor);
105937             me.keyNav.destroy();
105938             delete me.keyNav;
105939         }
105940         me.callParent(arguments);
105941     },
105942
105943     onMouseLeave: function(e) {
105944         var me = this;
105945
105946         me.deactivateActiveItem();
105947
105948         if (me.disabled) {
105949             return;
105950         }
105951
105952         me.fireEvent('mouseleave', me, e);
105953     },
105954
105955     onMouseOver: function(e) {
105956         var me = this,
105957             fromEl = e.getRelatedTarget(),
105958             mouseEnter = !me.el.contains(fromEl),
105959             item = me.getItemFromEvent(e);
105960
105961         if (mouseEnter && me.parentMenu) {
105962             me.parentMenu.setActiveItem(me.parentItem);
105963             me.parentMenu.mouseMonitor.mouseenter();
105964         }
105965
105966         if (me.disabled) {
105967             return;
105968         }
105969
105970         if (item) {
105971             me.setActiveItem(item);
105972             if (item.activated && item.expandMenu) {
105973                 item.expandMenu();
105974             }
105975         }
105976         if (mouseEnter) {
105977             me.fireEvent('mouseenter', me, e);
105978         }
105979         me.fireEvent('mouseover', me, item, e);
105980     },
105981
105982     setActiveItem: function(item) {
105983         var me = this;
105984
105985         if (item && (item != me.activeItem && item != me.focusedItem)) {
105986             me.deactivateActiveItem();
105987             if (me.canActivateItem(item)) {
105988                 if (item.activate) {
105989                     item.activate();
105990                     if (item.activated) {
105991                         me.activeItem = item;
105992                         me.focusedItem = item;
105993                         me.focus();
105994                     }
105995                 } else {
105996                     item.focus();
105997                     me.focusedItem = item;
105998                 }
105999             }
106000             item.el.scrollIntoView(me.layout.getRenderTarget());
106001         }
106002     },
106003
106004     /**
106005      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
106006      * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
106007      * @param {String} position (optional) Alignment position as used by {@link Ext.Element#getAlignToXY}.
106008      * Defaults to `{@link #defaultAlign}`.
106009      * @param {Number[]} offsets (optional) Alignment offsets as used by {@link Ext.Element#getAlignToXY}. Defaults to `undefined`.
106010      * @return {Ext.menu.Menu} This Menu.
106011      */
106012     showBy: function(cmp, pos, off) {
106013         var me = this,
106014             xy,
106015             region;
106016
106017         if (me.floating && cmp) {
106018             me.layout.autoSize = true;
106019
106020             // show off-screen first so that we can calc position without causing a visual jump
106021             me.doAutoRender();
106022             delete me.needsLayout;
106023
106024             // Component or Element
106025             cmp = cmp.el || cmp;
106026
106027             // Convert absolute to floatParent-relative coordinates if necessary.
106028             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
106029             if (me.floatParent) {
106030                 region = me.floatParent.getTargetEl().getViewRegion();
106031                 xy[0] -= region.x;
106032                 xy[1] -= region.y;
106033             }
106034             me.showAt(xy);
106035         }
106036         return me;
106037     },
106038
106039     doConstrain : function() {
106040         var me = this,
106041             y = me.el.getY(),
106042             max, full,
106043             vector,
106044             returnY = y, normalY, parentEl, scrollTop, viewHeight;
106045
106046         delete me.height;
106047         me.setSize();
106048         full = me.getHeight();
106049         if (me.floating) {
106050             //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
106051             parentEl = Ext.fly(me.el.getScopeParent());
106052             scrollTop = parentEl.getScroll().top;
106053             viewHeight = parentEl.getViewSize().height;
106054             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
106055             //of the view.
106056             normalY = y - scrollTop;
106057             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
106058             if (full > viewHeight) {
106059                 max = viewHeight;
106060                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
106061                 returnY = y - normalY;
106062             } else if (max < full) {
106063                 returnY = y - (full - max);
106064                 max = full;
106065             }
106066         }else{
106067             max = me.getHeight();
106068         }
106069         // Always respect maxHeight
106070         if (me.maxHeight){
106071             max = Math.min(me.maxHeight, max);
106072         }
106073         if (full > max && max > 0){
106074             me.layout.autoSize = false;
106075             me.setHeight(max);
106076             if (me.showSeparator){
106077                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
106078             }
106079         }
106080         vector = me.getConstrainVector(me.el.getScopeParent());
106081         if (vector) {
106082             me.setPosition(me.getPosition()[0] + vector[0]);
106083         }
106084         me.el.setY(returnY);
106085     }
106086 });
106087
106088 /**
106089  * A menu containing a Ext.picker.Color Component.
106090  *
106091  * Notes:
106092  *
106093  *   - Although not listed here, the **constructor** for this class accepts all of the
106094  *     configuration options of {@link Ext.picker.Color}.
106095  *   - If subclassing ColorMenu, any configuration options for the ColorPicker must be
106096  *     applied to the **initialConfig** property of the ColorMenu. Applying
106097  *     {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
106098  *     affect the ColorPicker's configuration.
106099  *
106100  * Example:
106101  *
106102  *     @example
106103  *     var colorPicker = Ext.create('Ext.menu.ColorPicker', {
106104  *         value: '000000'
106105  *     });
106106  *
106107  *     Ext.create('Ext.menu.Menu', {
106108  *         width: 100,
106109  *         height: 90,
106110  *         floating: false,  // usually you want this set to True (default)
106111  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
106112  *         items: [{
106113  *             text: 'choose a color',
106114  *             menu: colorPicker
106115  *         },{
106116  *             iconCls: 'add16',
106117  *             text: 'icon item'
106118  *         },{
106119  *             text: 'regular item'
106120  *         }]
106121  *     });
106122  */
106123  Ext.define('Ext.menu.ColorPicker', {
106124      extend: 'Ext.menu.Menu',
106125
106126      alias: 'widget.colormenu',
106127
106128      requires: [
106129         'Ext.picker.Color'
106130      ],
106131
106132     /**
106133      * @cfg {Boolean} hideOnClick
106134      * False to continue showing the menu after a date is selected.
106135      */
106136     hideOnClick : true,
106137
106138     /**
106139      * @cfg {String} pickerId
106140      * An id to assign to the underlying color picker.
106141      */
106142     pickerId : null,
106143
106144     /**
106145      * @cfg {Number} maxHeight
106146      * @hide
106147      */
106148
106149     /**
106150      * @property {Ext.picker.Color} picker
106151      * The {@link Ext.picker.Color} instance for this ColorMenu
106152      */
106153
106154     /**
106155      * @event click
106156      * @hide
106157      */
106158
106159     /**
106160      * @event itemclick
106161      * @hide
106162      */
106163
106164     initComponent : function(){
106165         var me = this,
106166             cfg = Ext.apply({}, me.initialConfig);
106167
106168         // Ensure we don't get duplicate listeners
106169         delete cfg.listeners;
106170         Ext.apply(me, {
106171             plain: true,
106172             showSeparator: false,
106173             items: Ext.applyIf({
106174                 cls: Ext.baseCSSPrefix + 'menu-color-item',
106175                 id: me.pickerId,
106176                 xtype: 'colorpicker'
106177             }, cfg)
106178         });
106179
106180         me.callParent(arguments);
106181
106182         me.picker = me.down('colorpicker');
106183
106184         /**
106185          * @event select
106186          * @alias Ext.picker.Color#select
106187          */
106188         me.relayEvents(me.picker, ['select']);
106189
106190         if (me.hideOnClick) {
106191             me.on('select', me.hidePickerOnSelect, me);
106192         }
106193     },
106194
106195     /**
106196      * Hides picker on select if hideOnClick is true
106197      * @private
106198      */
106199     hidePickerOnSelect: function() {
106200         Ext.menu.Manager.hideAll();
106201     }
106202  });
106203 /**
106204  * A menu containing an Ext.picker.Date Component.
106205  *
106206  * Notes:
106207  *
106208  * - Although not listed here, the **constructor** for this class accepts all of the
106209  *   configuration options of **{@link Ext.picker.Date}**.
106210  * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
106211  *   to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
106212  *   configuration settings to **this** will **not** affect the Date Picker's configuration.
106213  *
106214  * Example:
106215  *
106216  *     @example
106217  *     var dateMenu = Ext.create('Ext.menu.DatePicker', {
106218  *         handler: function(dp, date){
106219  *             Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
106220  *         }
106221  *     });
106222  *
106223  *     Ext.create('Ext.menu.Menu', {
106224  *         width: 100,
106225  *         height: 90,
106226  *         floating: false,  // usually you want this set to True (default)
106227  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
106228  *         items: [{
106229  *             text: 'choose a date',
106230  *             menu: dateMenu
106231  *         },{
106232  *             iconCls: 'add16',
106233  *             text: 'icon item'
106234  *         },{
106235  *             text: 'regular item'
106236  *         }]
106237  *     });
106238  */
106239  Ext.define('Ext.menu.DatePicker', {
106240      extend: 'Ext.menu.Menu',
106241
106242      alias: 'widget.datemenu',
106243
106244      requires: [
106245         'Ext.picker.Date'
106246      ],
106247
106248     /**
106249      * @cfg {Boolean} hideOnClick
106250      * False to continue showing the menu after a date is selected.
106251      */
106252     hideOnClick : true,
106253
106254     /**
106255      * @cfg {String} pickerId
106256      * An id to assign to the underlying date picker.
106257      */
106258     pickerId : null,
106259
106260     /**
106261      * @cfg {Number} maxHeight
106262      * @hide
106263      */
106264
106265     /**
106266      * @property {Ext.picker.Date} picker
106267      * The {@link Ext.picker.Date} instance for this DateMenu
106268      */
106269
106270     /**
106271      * @event click
106272      * @hide
106273      */
106274
106275     /**
106276      * @event itemclick
106277      * @hide
106278      */
106279
106280     initComponent : function(){
106281         var me = this;
106282
106283         Ext.apply(me, {
106284             showSeparator: false,
106285             plain: true,
106286             border: false,
106287             bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
106288             items: Ext.applyIf({
106289                 cls: Ext.baseCSSPrefix + 'menu-date-item',
106290                 id: me.pickerId,
106291                 xtype: 'datepicker'
106292             }, me.initialConfig)
106293         });
106294
106295         me.callParent(arguments);
106296
106297         me.picker = me.down('datepicker');
106298         /**
106299          * @event select
106300          * @alias Ext.picker.Date#select
106301          */
106302         me.relayEvents(me.picker, ['select']);
106303
106304         if (me.hideOnClick) {
106305             me.on('select', me.hidePickerOnSelect, me);
106306         }
106307     },
106308
106309     hidePickerOnSelect: function() {
106310         Ext.menu.Manager.hideAll();
106311     }
106312  });
106313 /**
106314  * This class is used to display small visual icons in the header of a panel. There are a set of
106315  * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
106316  * can be used to provide a function that will respond to any click events. In general, this class
106317  * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
106318  * configuration on the Panel itself.
106319  *
106320  *     @example
106321  *     Ext.create('Ext.panel.Panel', {
106322  *         width: 200,
106323  *         height: 200,
106324  *         renderTo: document.body,
106325  *         title: 'A Panel',
106326  *         tools: [{
106327  *             type: 'help',
106328  *             handler: function(){
106329  *                 // show help here
106330  *             }
106331  *         }, {
106332  *             itemId: 'refresh',
106333  *             type: 'refresh',
106334  *             hidden: true,
106335  *             handler: function(){
106336  *                 // do refresh
106337  *             }
106338  *         }, {
106339  *             type: 'search',
106340  *             handler: function(event, target, owner, tool){
106341  *                 // do search
106342  *                 owner.child('#refresh').show();
106343  *             }
106344  *         }]
106345  *     });
106346  */
106347 Ext.define('Ext.panel.Tool', {
106348     extend: 'Ext.Component',
106349     requires: ['Ext.tip.QuickTipManager'],
106350     alias: 'widget.tool',
106351
106352     baseCls: Ext.baseCSSPrefix + 'tool',
106353     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
106354     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
106355     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
106356     ariaRole: 'button',
106357     renderTpl: ['<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
106358
106359     /**
106360      * @cfg {Function} handler
106361      * A function to execute when the tool is clicked. Arguments passed are:
106362      *
106363      * - **event** : Ext.EventObject - The click event.
106364      * - **toolEl** : Ext.Element - The tool Element.
106365      * - **owner** : Ext.panel.Header - The host panel header.
106366      * - **tool** : Ext.panel.Tool - The tool object
106367      */
106368
106369     /**
106370      * @cfg {Object} scope
106371      * The scope to execute the {@link #handler} function. Defaults to the tool.
106372      */
106373
106374     /**
106375      * @cfg {String} type
106376      * The type of tool to render. The following types are available:
106377      *
106378      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
106379      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
106380      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
106381      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
106382      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
106383      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
106384      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
106385      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
106386      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
106387      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
106388      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
106389      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
106390      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
106391      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
106392      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
106393      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
106394      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
106395      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
106396      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
106397      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
106398      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
106399      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
106400      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
106401      */
106402
106403     /**
106404      * @cfg {String/Object} tooltip
106405      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
106406      * object
106407      */
106408
106409      /**
106410      * @cfg {String} tooltipType
106411      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
106412      */
106413     tooltipType: 'qtip',
106414
106415     /**
106416      * @cfg {Boolean} stopEvent
106417      * Specify as false to allow click event to propagate.
106418      */
106419     stopEvent: true,
106420
106421     initComponent: function() {
106422         var me = this;
106423         me.addEvents(
106424             /**
106425              * @event click
106426              * Fires when the tool is clicked
106427              * @param {Ext.panel.Tool} this
106428              * @param {Ext.EventObject} e The event object
106429              */
106430             'click'
106431         );
106432
106433         //<debug>
106434         var types = [
106435             'close',
106436             'collapse',
106437             'down',
106438             'expand',
106439             'gear',
106440             'help',
106441             'left',
106442             'maximize',
106443             'minimize',
106444             'minus',
106445             'move',
106446             'next',
106447             'pin',
106448             'plus',
106449             'prev',
106450             'print',
106451             'refresh',
106452             'resize',
106453             'restore',
106454             'right',
106455             'save',
106456             'search',
106457             'toggle',
106458             'unpin',
106459             'up'
106460         ];
106461
106462         if (me.id && Ext.Array.indexOf(types, me.id) > -1 && Ext.global.console) {
106463             Ext.global.console.warn('When specifying a tool you should use the type option, the id can conflict now that tool is a Component');
106464         }
106465         //</debug>
106466
106467         me.type = me.type || me.id;
106468
106469         Ext.applyIf(me.renderData, {
106470             baseCls: me.baseCls,
106471             blank: Ext.BLANK_IMAGE_URL,
106472             type: me.type
106473         });
106474
106475         me.addChildEls('toolEl');
106476
106477         // alias qtip, should use tooltip since it's what we have in the docs
106478         me.tooltip = me.tooltip || me.qtip;
106479         me.callParent();
106480     },
106481
106482     // inherit docs
106483     afterRender: function() {
106484         var me = this,
106485             attr;
106486
106487         me.callParent(arguments);
106488         if (me.tooltip) {
106489             if (Ext.isObject(me.tooltip)) {
106490                 Ext.tip.QuickTipManager.register(Ext.apply({
106491                     target: me.id
106492                 }, me.tooltip));
106493             }
106494             else {
106495                 attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
106496                 me.toolEl.dom.setAttribute(attr, me.tooltip);
106497             }
106498         }
106499
106500         me.mon(me.toolEl, {
106501             click: me.onClick,
106502             mousedown: me.onMouseDown,
106503             mouseover: me.onMouseOver,
106504             mouseout: me.onMouseOut,
106505             scope: me
106506         });
106507     },
106508
106509     /**
106510      * Sets the type of the tool. Allows the icon to be changed.
106511      * @param {String} type The new type. See the {@link #type} config.
106512      * @return {Ext.panel.Tool} this
106513      */
106514     setType: function(type) {
106515         var me = this;
106516
106517         me.type = type;
106518         if (me.rendered) {
106519             me.toolEl.dom.className = me.baseCls + '-' + type;
106520         }
106521         return me;
106522     },
106523
106524     /**
106525      * Binds this tool to a component.
106526      * @private
106527      * @param {Ext.Component} component The component
106528      */
106529     bindTo: function(component) {
106530         this.owner = component;
106531     },
106532
106533     /**
106534      * Called when the tool element is clicked
106535      * @private
106536      * @param {Ext.EventObject} e
106537      * @param {HTMLElement} target The target element
106538      */
106539     onClick: function(e, target) {
106540         var me = this,
106541             owner;
106542
106543         if (me.disabled) {
106544             return false;
106545         }
106546         owner = me.owner || me.ownerCt;
106547
106548         //remove the pressed + over class
106549         me.el.removeCls(me.toolPressedCls);
106550         me.el.removeCls(me.toolOverCls);
106551
106552         if (me.stopEvent !== false) {
106553             e.stopEvent();
106554         }
106555
106556         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
106557         me.fireEvent('click', me, e);
106558         return true;
106559     },
106560
106561     // inherit docs
106562     onDestroy: function(){
106563         if (Ext.isObject(this.tooltip)) {
106564             Ext.tip.QuickTipManager.unregister(this.id);
106565         }
106566         this.callParent();
106567     },
106568
106569     /**
106570      * Called when the user presses their mouse button down on a tool
106571      * Adds the press class ({@link #toolPressedCls})
106572      * @private
106573      */
106574     onMouseDown: function() {
106575         if (this.disabled) {
106576             return false;
106577         }
106578
106579         this.el.addCls(this.toolPressedCls);
106580     },
106581
106582     /**
106583      * Called when the user rolls over a tool
106584      * Adds the over class ({@link #toolOverCls})
106585      * @private
106586      */
106587     onMouseOver: function() {
106588         if (this.disabled) {
106589             return false;
106590         }
106591         this.el.addCls(this.toolOverCls);
106592     },
106593
106594     /**
106595      * Called when the user rolls out from a tool.
106596      * Removes the over class ({@link #toolOverCls})
106597      * @private
106598      */
106599     onMouseOut: function() {
106600         this.el.removeCls(this.toolOverCls);
106601     }
106602 });
106603 /**
106604  * @class Ext.resizer.Handle
106605  * @extends Ext.Component
106606  *
106607  * Provides a handle for 9-point resizing of Elements or Components.
106608  */
106609 Ext.define('Ext.resizer.Handle', {
106610     extend: 'Ext.Component',
106611     handleCls: '',
106612     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
106613     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
106614     // which will be passed in as a region configuration.
106615     region: '',
106616
106617     onRender: function() {
106618         this.addCls(
106619             this.baseHandleCls,
106620             this.baseHandleCls + '-' + this.region,
106621             this.handleCls
106622         );
106623         this.callParent(arguments);
106624         this.el.unselectable();
106625     }
106626 });
106627
106628 /**
106629  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
106630  * (or component's element) and positioned absolute.
106631  *
106632  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
106633  * The original element can be accessed through the originalTarget property.
106634  *
106635  * Here is the list of valid resize handles:
106636  *
106637  *     Value   Description
106638  *     ------  -------------------
106639  *      'n'     north
106640  *      's'     south
106641  *      'e'     east
106642  *      'w'     west
106643  *      'nw'    northwest
106644  *      'sw'    southwest
106645  *      'se'    southeast
106646  *      'ne'    northeast
106647  *      'all'   all
106648  *
106649  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
106650  *
106651  * Here's an example showing the creation of a typical Resizer:
106652  *
106653  *     Ext.create('Ext.resizer.Resizer', {
106654  *         el: 'elToResize',
106655  *         handles: 'all',
106656  *         minWidth: 200,
106657  *         minHeight: 100,
106658  *         maxWidth: 500,
106659  *         maxHeight: 400,
106660  *         pinned: true
106661  *     });
106662  */
106663 Ext.define('Ext.resizer.Resizer', {
106664     mixins: {
106665         observable: 'Ext.util.Observable'
106666     },
106667     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
106668
106669     alternateClassName: 'Ext.Resizable',
106670
106671     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
106672     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
106673     overCls:   Ext.baseCSSPrefix + 'resizable-over',
106674     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
106675
106676     /**
106677      * @cfg {Boolean} dynamic
106678      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
106679      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
106680      * configured as {@link Ext.Component#resizable}.
106681      *
106682      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
106683      * updated on mouseup.
106684      */
106685     dynamic: true,
106686
106687     /**
106688      * @cfg {String} handles
106689      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
106690      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
106691      * of `'n s e w ne nw se sw'`.
106692      */
106693     handles: 's e se',
106694
106695     /**
106696      * @cfg {Number} height
106697      * Optional. The height to set target to in pixels
106698      */
106699     height : null,
106700
106701     /**
106702      * @cfg {Number} width
106703      * Optional. The width to set the target to in pixels
106704      */
106705     width : null,
106706
106707     /**
106708      * @cfg {Number} heightIncrement
106709      * The increment to snap the height resize in pixels.
106710      */
106711     heightIncrement : 0,
106712
106713     /**
106714      * @cfg {Number} widthIncrement
106715      * The increment to snap the width resize in pixels.
106716      */
106717     widthIncrement : 0,
106718
106719     /**
106720      * @cfg {Number} minHeight
106721      * The minimum height for the element
106722      */
106723     minHeight : 20,
106724
106725     /**
106726      * @cfg {Number} minWidth
106727      * The minimum width for the element
106728      */
106729     minWidth : 20,
106730
106731     /**
106732      * @cfg {Number} maxHeight
106733      * The maximum height for the element
106734      */
106735     maxHeight : 10000,
106736
106737     /**
106738      * @cfg {Number} maxWidth
106739      * The maximum width for the element
106740      */
106741     maxWidth : 10000,
106742
106743     /**
106744      * @cfg {Boolean} pinned
106745      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
106746      */
106747     pinned: false,
106748
106749     /**
106750      * @cfg {Boolean} preserveRatio
106751      * True to preserve the original ratio between height and width during resize
106752      */
106753     preserveRatio: false,
106754
106755     /**
106756      * @cfg {Boolean} transparent
106757      * True for transparent handles. This is only applied at config time.
106758      */
106759     transparent: false,
106760
106761     /**
106762      * @cfg {Ext.Element/Ext.util.Region} constrainTo
106763      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
106764      */
106765
106766     possiblePositions: {
106767         n:  'north',
106768         s:  'south',
106769         e:  'east',
106770         w:  'west',
106771         se: 'southeast',
106772         sw: 'southwest',
106773         nw: 'northwest',
106774         ne: 'northeast'
106775     },
106776
106777     /**
106778      * @cfg {Ext.Element/Ext.Component} target
106779      * The Element or Component to resize.
106780      */
106781
106782     /**
106783      * @property {Ext.Element} el
106784      * Outer element for resizing behavior.
106785      */
106786
106787     constructor: function(config) {
106788         var me = this,
106789             target,
106790             tag,
106791             handles = me.handles,
106792             handleCls,
106793             possibles,
106794             len,
106795             i = 0,
106796             pos;
106797
106798         this.addEvents(
106799             /**
106800              * @event beforeresize
106801              * Fired before resize is allowed. Return false to cancel resize.
106802              * @param {Ext.resizer.Resizer} this
106803              * @param {Number} width The start width
106804              * @param {Number} height The start height
106805              * @param {Ext.EventObject} e The mousedown event
106806              */
106807             'beforeresize',
106808             /**
106809              * @event resizedrag
106810              * Fires during resizing. Return false to cancel resize.
106811              * @param {Ext.resizer.Resizer} this
106812              * @param {Number} width The new width
106813              * @param {Number} height The new height
106814              * @param {Ext.EventObject} e The mousedown event
106815              */
106816             'resizedrag',
106817             /**
106818              * @event resize
106819              * Fired after a resize.
106820              * @param {Ext.resizer.Resizer} this
106821              * @param {Number} width The new width
106822              * @param {Number} height The new height
106823              * @param {Ext.EventObject} e The mouseup event
106824              */
106825             'resize'
106826         );
106827
106828         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
106829             target = config;
106830             config = arguments[1] || {};
106831             config.target = target;
106832         }
106833         // will apply config to this
106834         me.mixins.observable.constructor.call(me, config);
106835
106836         // If target is a Component, ensure that we pull the element out.
106837         // Resizer must examine the underlying Element.
106838         target = me.target;
106839         if (target) {
106840             if (target.isComponent) {
106841                 me.el = target.getEl();
106842                 if (target.minWidth) {
106843                     me.minWidth = target.minWidth;
106844                 }
106845                 if (target.minHeight) {
106846                     me.minHeight = target.minHeight;
106847                 }
106848                 if (target.maxWidth) {
106849                     me.maxWidth = target.maxWidth;
106850                 }
106851                 if (target.maxHeight) {
106852                     me.maxHeight = target.maxHeight;
106853                 }
106854                 if (target.floating) {
106855                     if (!this.hasOwnProperty('handles')) {
106856                         this.handles = 'n ne e se s sw w nw';
106857                     }
106858                 }
106859             } else {
106860                 me.el = me.target = Ext.get(target);
106861             }
106862         }
106863         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
106864         else {
106865             me.target = me.el = Ext.get(me.el);
106866         }
106867
106868         // Tags like textarea and img cannot
106869         // have children and therefore must
106870         // be wrapped
106871         tag = me.el.dom.tagName;
106872         if (tag == 'TEXTAREA' || tag == 'IMG') {
106873             /**
106874              * @property {Ext.Element/Ext.Component} originalTarget
106875              * Reference to the original resize target if the element of the original resize target was an IMG or a
106876              * TEXTAREA which must be wrapped in a DIV.
106877              */
106878             me.originalTarget = me.target;
106879             me.target = me.el = me.el.wrap({
106880                 cls: me.wrapCls,
106881                 id: me.el.id + '-rzwrap'
106882             });
106883
106884             // Transfer originalTarget's positioning/sizing
106885             me.el.setPositioning(me.originalTarget.getPositioning());
106886             me.originalTarget.clearPositioning();
106887             var box = me.originalTarget.getBox();
106888             me.el.setBox(box);
106889         }
106890
106891         // Position the element, this enables us to absolute position
106892         // the handles within this.el
106893         me.el.position();
106894         if (me.pinned) {
106895             me.el.addCls(me.pinnedCls);
106896         }
106897
106898         /**
106899          * @property {Ext.resizer.ResizeTracker} resizeTracker
106900          */
106901         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
106902             disabled: me.disabled,
106903             target: me.target,
106904             constrainTo: me.constrainTo,
106905             overCls: me.overCls,
106906             throttle: me.throttle,
106907             originalTarget: me.originalTarget,
106908             delegate: '.' + me.handleCls,
106909             dynamic: me.dynamic,
106910             preserveRatio: me.preserveRatio,
106911             heightIncrement: me.heightIncrement,
106912             widthIncrement: me.widthIncrement,
106913             minHeight: me.minHeight,
106914             maxHeight: me.maxHeight,
106915             minWidth: me.minWidth,
106916             maxWidth: me.maxWidth
106917         });
106918
106919         // Relay the ResizeTracker's superclass events as our own resize events
106920         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
106921         me.resizeTracker.on('drag', me.onResize, me);
106922         me.resizeTracker.on('dragend', me.onResizeEnd, me);
106923
106924         if (me.handles == 'all') {
106925             me.handles = 'n s e w ne nw se sw';
106926         }
106927
106928         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
106929         possibles = me.possiblePositions;
106930         len = handles.length;
106931         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
106932
106933         for(; i < len; i++){
106934             // if specified and possible, create
106935             if (handles[i] && possibles[handles[i]]) {
106936                 pos = possibles[handles[i]];
106937                 // store a reference in this.east, this.west, etc
106938
106939                 me[pos] = Ext.create('Ext.Component', {
106940                     owner: this,
106941                     region: pos,
106942                     cls: handleCls + pos,
106943                     renderTo: me.el
106944                 });
106945                 me[pos].el.unselectable();
106946                 if (me.transparent) {
106947                     me[pos].el.setOpacity(0);
106948                 }
106949             }
106950         }
106951
106952         // Constrain within configured maxima
106953         if (Ext.isNumber(me.width)) {
106954             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
106955         }
106956         if (Ext.isNumber(me.height)) {
106957             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
106958         }
106959
106960         // Size the element
106961         if (me.width != null || me.height != null) {
106962             if (me.originalTarget) {
106963                 me.originalTarget.setWidth(me.width);
106964                 me.originalTarget.setHeight(me.height);
106965             }
106966             me.resizeTo(me.width, me.height);
106967         }
106968
106969         me.forceHandlesHeight();
106970     },
106971
106972     disable: function() {
106973         this.resizeTracker.disable();
106974     },
106975
106976     enable: function() {
106977         this.resizeTracker.enable();
106978     },
106979
106980     /**
106981      * @private Relay the Tracker's mousedown event as beforeresize
106982      * @param tracker The Resizer
106983      * @param e The Event
106984      */
106985     onBeforeResize: function(tracker, e) {
106986         var b = this.target.getBox();
106987         return this.fireEvent('beforeresize', this, b.width, b.height, e);
106988     },
106989
106990     /**
106991      * @private Relay the Tracker's drag event as resizedrag
106992      * @param tracker The Resizer
106993      * @param e The Event
106994      */
106995     onResize: function(tracker, e) {
106996         var me = this,
106997             b = me.target.getBox();
106998         me.forceHandlesHeight();
106999         return me.fireEvent('resizedrag', me, b.width, b.height, e);
107000     },
107001
107002     /**
107003      * @private Relay the Tracker's dragend event as resize
107004      * @param tracker The Resizer
107005      * @param e The Event
107006      */
107007     onResizeEnd: function(tracker, e) {
107008         var me = this,
107009             b = me.target.getBox();
107010         me.forceHandlesHeight();
107011         return me.fireEvent('resize', me, b.width, b.height, e);
107012     },
107013
107014     /**
107015      * Perform a manual resize and fires the 'resize' event.
107016      * @param {Number} width
107017      * @param {Number} height
107018      */
107019     resizeTo : function(width, height){
107020         this.target.setSize(width, height);
107021         this.fireEvent('resize', this, width, height, null);
107022     },
107023
107024     /**
107025      * Returns the element that was configured with the el or target config property. If a component was configured with
107026      * the target property then this will return the element of this component.
107027      *
107028      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
107029      * nodes. The original element can be accessed through the originalTarget property.
107030      * @return {Ext.Element} element
107031      */
107032     getEl : function() {
107033         return this.el;
107034     },
107035
107036     /**
107037      * Returns the element or component that was configured with the target config property.
107038      *
107039      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
107040      * nodes. The original element can be accessed through the originalTarget property.
107041      * @return {Ext.Element/Ext.Component}
107042      */
107043     getTarget: function() {
107044         return this.target;
107045     },
107046
107047     destroy: function() {
107048         var h;
107049         for (var i = 0, l = this.handles.length; i < l; i++) {
107050             h = this[this.possiblePositions[this.handles[i]]];
107051             delete h.owner;
107052             Ext.destroy(h);
107053         }
107054     },
107055
107056     /**
107057      * @private
107058      * Fix IE6 handle height issue.
107059      */
107060     forceHandlesHeight : function() {
107061         var me = this,
107062             handle;
107063         if (Ext.isIE6) {
107064             handle = me.east;
107065             if (handle) {
107066                 handle.setHeight(me.el.getHeight());
107067             }
107068             handle = me.west;
107069             if (handle) {
107070                 handle.setHeight(me.el.getHeight());
107071             }
107072             me.el.repaint();
107073         }
107074     }
107075 });
107076
107077 /**
107078  * @class Ext.resizer.ResizeTracker
107079  * @extends Ext.dd.DragTracker
107080  * Private utility class for Ext.resizer.Resizer.
107081  * @private
107082  */
107083 Ext.define('Ext.resizer.ResizeTracker', {
107084     extend: 'Ext.dd.DragTracker',
107085     dynamic: true,
107086     preserveRatio: false,
107087
107088     // Default to no constraint
107089     constrainTo: null,
107090     
107091     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
107092
107093     constructor: function(config) {
107094         var me = this;
107095
107096         if (!config.el) {
107097             if (config.target.isComponent) {
107098                 me.el = config.target.getEl();
107099             } else {
107100                 me.el = config.target;
107101             }
107102         }
107103         this.callParent(arguments);
107104
107105         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
107106         if (me.preserveRatio && me.minWidth && me.minHeight) {
107107             var widthRatio = me.minWidth / me.el.getWidth(),
107108                 heightRatio = me.minHeight / me.el.getHeight();
107109
107110             // largest ratio of minimum:size must be preserved.
107111             // So if a 400x200 pixel image has
107112             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
107113             if (heightRatio > widthRatio) {
107114                 me.minWidth = me.el.getWidth() * heightRatio;
107115             } else {
107116                 me.minHeight = me.el.getHeight() * widthRatio;
107117             }
107118         }
107119
107120         // If configured as throttled, create an instance version of resize which calls
107121         // a throttled function to perform the resize operation.
107122         if (me.throttle) {
107123             var throttledResizeFn = Ext.Function.createThrottled(function() {
107124                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
107125                 }, me.throttle);
107126
107127             me.resize = function(box, direction, atEnd) {
107128                 if (atEnd) {
107129                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
107130                 } else {
107131                     throttledResizeFn.apply(null, arguments);
107132                 }
107133             };
107134         }
107135     },
107136
107137     onBeforeStart: function(e) {
107138         // record the startBox
107139         this.startBox = this.el.getBox();
107140     },
107141
107142     /**
107143      * @private
107144      * Returns the object that will be resized on every mousemove event.
107145      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
107146      */
107147     getDynamicTarget: function() {
107148         var me = this,
107149             target = me.target;
107150             
107151         if (me.dynamic) {
107152             return target;
107153         } else if (!me.proxy) {
107154             me.proxy = me.createProxy(target);
107155         }
107156         me.proxy.show();
107157         return me.proxy;
107158     },
107159     
107160     /**
107161      * Create a proxy for this resizer
107162      * @param {Ext.Component/Ext.Element} target The target
107163      * @return {Ext.Element} A proxy element
107164      */
107165     createProxy: function(target){
107166         var proxy,
107167             cls = this.proxyCls,
107168             renderTo;
107169             
107170         if (target.isComponent) {
107171             proxy = target.getProxy().addCls(cls);
107172         } else {
107173             renderTo = Ext.getBody();
107174             if (Ext.scopeResetCSS) {
107175                 renderTo = Ext.getBody().createChild({
107176                     cls: Ext.baseCSSPrefix + 'reset'
107177                 });
107178             }
107179             proxy = target.createProxy({
107180                 tag: 'div',
107181                 cls: cls,
107182                 id: target.id + '-rzproxy'
107183             }, renderTo);
107184         }
107185         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
107186         return proxy;
107187     },
107188
107189     onStart: function(e) {
107190         // returns the Ext.ResizeHandle that the user started dragging
107191         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
107192
107193         // If we are using a proxy, ensure it is sized.
107194         if (!this.dynamic) {
107195             this.resize(this.startBox, {
107196                 horizontal: 'none',
107197                 vertical: 'none'
107198             });
107199         }
107200     },
107201
107202     onDrag: function(e) {
107203         // dynamic resizing, update dimensions during resize
107204         if (this.dynamic || this.proxy) {
107205             this.updateDimensions(e);
107206         }
107207     },
107208
107209     updateDimensions: function(e, atEnd) {
107210         var me = this,
107211             region = me.activeResizeHandle.region,
107212             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
107213             box = me.startBox,
107214             ratio,
107215             widthAdjust = 0,
107216             heightAdjust = 0,
107217             snappedWidth,
107218             snappedHeight,
107219             adjustX = 0,
107220             adjustY = 0,
107221             dragRatio,
107222             horizDir = offset[0] < 0 ? 'right' : 'left',
107223             vertDir = offset[1] < 0 ? 'down' : 'up',
107224             oppositeCorner,
107225             axis; // 1 = x, 2 = y, 3 = x and y.
107226
107227         switch (region) {
107228             case 'south':
107229                 heightAdjust = offset[1];
107230                 axis = 2;
107231                 break;
107232             case 'north':
107233                 heightAdjust = -offset[1];
107234                 adjustY = -heightAdjust;
107235                 axis = 2;
107236                 break;
107237             case 'east':
107238                 widthAdjust = offset[0];
107239                 axis = 1;
107240                 break;
107241             case 'west':
107242                 widthAdjust = -offset[0];
107243                 adjustX = -widthAdjust;
107244                 axis = 1;
107245                 break;
107246             case 'northeast':
107247                 heightAdjust = -offset[1];
107248                 adjustY = -heightAdjust;
107249                 widthAdjust = offset[0];
107250                 oppositeCorner = [box.x, box.y + box.height];
107251                 axis = 3;
107252                 break;
107253             case 'southeast':
107254                 heightAdjust = offset[1];
107255                 widthAdjust = offset[0];
107256                 oppositeCorner = [box.x, box.y];
107257                 axis = 3;
107258                 break;
107259             case 'southwest':
107260                 widthAdjust = -offset[0];
107261                 adjustX = -widthAdjust;
107262                 heightAdjust = offset[1];
107263                 oppositeCorner = [box.x + box.width, box.y];
107264                 axis = 3;
107265                 break;
107266             case 'northwest':
107267                 heightAdjust = -offset[1];
107268                 adjustY = -heightAdjust;
107269                 widthAdjust = -offset[0];
107270                 adjustX = -widthAdjust;
107271                 oppositeCorner = [box.x + box.width, box.y + box.height];
107272                 axis = 3;
107273                 break;
107274         }
107275
107276         var newBox = {
107277             width: box.width + widthAdjust,
107278             height: box.height + heightAdjust,
107279             x: box.x + adjustX,
107280             y: box.y + adjustY
107281         };
107282
107283         // Snap value between stops according to configured increments
107284         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
107285         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
107286         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
107287             switch (region) {
107288                 case 'northeast':
107289                     newBox.y -= snappedHeight - newBox.height;
107290                     break;
107291                 case 'north':
107292                     newBox.y -= snappedHeight - newBox.height;
107293                     break;
107294                 case 'southwest':
107295                     newBox.x -= snappedWidth - newBox.width;
107296                     break;
107297                 case 'west':
107298                     newBox.x -= snappedWidth - newBox.width;
107299                     break;
107300                 case 'northwest':
107301                     newBox.x -= snappedWidth - newBox.width;
107302                     newBox.y -= snappedHeight - newBox.height;
107303             }
107304             newBox.width = snappedWidth;
107305             newBox.height = snappedHeight;
107306         }
107307
107308         // out of bounds
107309         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
107310             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
107311
107312             // Re-adjust the X position if we were dragging the west side
107313             if (adjustX) {
107314                 newBox.x = box.x + (box.width - newBox.width);
107315             }
107316         } else {
107317             me.lastX = newBox.x;
107318         }
107319         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
107320             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
107321
107322             // Re-adjust the Y position if we were dragging the north side
107323             if (adjustY) {
107324                 newBox.y = box.y + (box.height - newBox.height);
107325             }
107326         } else {
107327             me.lastY = newBox.y;
107328         }
107329
107330         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
107331         if (me.preserveRatio || e.shiftKey) {
107332             var newHeight,
107333                 newWidth;
107334
107335             ratio = me.startBox.width / me.startBox.height;
107336
107337             // Calculate aspect ratio constrained values.
107338             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
107339             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
107340
107341             // X axis: width-only change, height must obey
107342             if (axis == 1) {
107343                 newBox.height = newHeight;
107344             }
107345
107346             // Y axis: height-only change, width must obey
107347             else if (axis == 2) {
107348                 newBox.width = newWidth;
107349             }
107350
107351             // Corner drag.
107352             else {
107353                 // Drag ratio is the ratio of the mouse point from the opposite corner.
107354                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
107355                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
107356
107357                 // If drag ratio > aspect ratio then width is dominant and height must obey
107358                 if (dragRatio > ratio) {
107359                     newBox.height = newHeight;
107360                 } else {
107361                     newBox.width = newWidth;
107362                 }
107363
107364                 // Handle dragging start coordinates
107365                 if (region == 'northeast') {
107366                     newBox.y = box.y - (newBox.height - box.height);
107367                 } else if (region == 'northwest') {
107368                     newBox.y = box.y - (newBox.height - box.height);
107369                     newBox.x = box.x - (newBox.width - box.width);
107370                 } else if (region == 'southwest') {
107371                     newBox.x = box.x - (newBox.width - box.width);
107372                 }
107373             }
107374         }
107375
107376         if (heightAdjust === 0) {
107377             vertDir = 'none';
107378         }
107379         if (widthAdjust === 0) {
107380             horizDir = 'none';
107381         }
107382         me.resize(newBox, {
107383             horizontal: horizDir,
107384             vertical: vertDir
107385         }, atEnd);
107386     },
107387
107388     getResizeTarget: function(atEnd) {
107389         return atEnd ? this.target : this.getDynamicTarget();
107390     },
107391
107392     resize: function(box, direction, atEnd) {
107393         var target = this.getResizeTarget(atEnd);
107394         if (target.isComponent) {
107395             if (target.floating) {
107396                 target.setPagePosition(box.x, box.y);
107397             }
107398             target.setSize(box.width, box.height);
107399         } else {
107400             target.setBox(box);
107401             // update the originalTarget if this was wrapped.
107402             if (this.originalTarget) {
107403                 this.originalTarget.setBox(box);
107404             }
107405         }
107406     },
107407
107408     onEnd: function(e) {
107409         this.updateDimensions(e, true);
107410         if (this.proxy) {
107411             this.proxy.hide();
107412         }
107413     }
107414 });
107415
107416 /**
107417  * @class Ext.resizer.SplitterTracker
107418  * @extends Ext.dd.DragTracker
107419  * Private utility class for Ext.Splitter.
107420  * @private
107421  */
107422 Ext.define('Ext.resizer.SplitterTracker', {
107423     extend: 'Ext.dd.DragTracker',
107424     requires: ['Ext.util.Region'],
107425     enabled: true,
107426     
107427     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
107428
107429     getPrevCmp: function() {
107430         var splitter = this.getSplitter();
107431         return splitter.previousSibling();
107432     },
107433
107434     getNextCmp: function() {
107435         var splitter = this.getSplitter();
107436         return splitter.nextSibling();
107437     },
107438
107439     // ensure the tracker is enabled, store boxes of previous and next
107440     // components and calculate the constrain region
107441     onBeforeStart: function(e) {
107442         var me = this,
107443             prevCmp = me.getPrevCmp(),
107444             nextCmp = me.getNextCmp(),
107445             collapseEl = me.getSplitter().collapseEl,
107446             overlay;
107447             
107448         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
107449             return false;
107450         }
107451
107452         // SplitterTracker is disabled if any of its adjacents are collapsed.
107453         if (nextCmp.collapsed || prevCmp.collapsed) {
107454             return false;
107455         }
107456         
107457         overlay = me.overlay =  Ext.getBody().createChild({
107458             cls: me.overlayCls, 
107459             html: '&#160;'
107460         });
107461         overlay.unselectable();
107462         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
107463         overlay.show();
107464         
107465         // store boxes of previous and next
107466         me.prevBox  = prevCmp.getEl().getBox();
107467         me.nextBox  = nextCmp.getEl().getBox();
107468         me.constrainTo = me.calculateConstrainRegion();
107469     },
107470
107471     // We move the splitter el. Add the proxy class.
107472     onStart: function(e) {
107473         var splitter = this.getSplitter();
107474         splitter.addCls(splitter.baseCls + '-active');
107475     },
107476
107477     // calculate the constrain Region in which the splitter el may be moved.
107478     calculateConstrainRegion: function() {
107479         var me         = this,
107480             splitter   = me.getSplitter(),
107481             splitWidth = splitter.getWidth(),
107482             defaultMin = splitter.defaultSplitMin,
107483             orient     = splitter.orientation,
107484             prevBox    = me.prevBox,
107485             prevCmp    = me.getPrevCmp(),
107486             nextBox    = me.nextBox,
107487             nextCmp    = me.getNextCmp(),
107488             // prev and nextConstrainRegions are the maximumBoxes minus the
107489             // minimumBoxes. The result is always the intersection
107490             // of these two boxes.
107491             prevConstrainRegion, nextConstrainRegion;
107492
107493         // vertical splitters, so resizing left to right
107494         if (orient === 'vertical') {
107495
107496             // Region constructor accepts (top, right, bottom, left)
107497             // anchored/calculated from the left
107498             prevConstrainRegion = Ext.create('Ext.util.Region',
107499                 prevBox.y,
107500                 // Right boundary is x + maxWidth if there IS a maxWidth.
107501                 // Otherwise it is calculated based upon the minWidth of the next Component
107502                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
107503                 prevBox.bottom,
107504                 prevBox.x + (prevCmp.minWidth || defaultMin)
107505             );
107506             // anchored/calculated from the right
107507             nextConstrainRegion = Ext.create('Ext.util.Region',
107508                 nextBox.y,
107509                 nextBox.right - (nextCmp.minWidth || defaultMin),
107510                 nextBox.bottom,
107511                 // Left boundary is right - maxWidth if there IS a maxWidth.
107512                 // Otherwise it is calculated based upon the minWidth of the previous Component
107513                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
107514             );
107515         } else {
107516             // anchored/calculated from the top
107517             prevConstrainRegion = Ext.create('Ext.util.Region',
107518                 prevBox.y + (prevCmp.minHeight || defaultMin),
107519                 prevBox.right,
107520                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
107521                 // Otherwise it is calculated based upon the minWidth of the next Component
107522                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
107523                 prevBox.x
107524             );
107525             // anchored/calculated from the bottom
107526             nextConstrainRegion = Ext.create('Ext.util.Region',
107527                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
107528                 // Otherwise it is calculated based upon the minHeight of the previous Component
107529                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
107530                 nextBox.right,
107531                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
107532                 nextBox.x
107533             );
107534         }
107535
107536         // intersection of the two regions to provide region draggable
107537         return prevConstrainRegion.intersect(nextConstrainRegion);
107538     },
107539
107540     // Performs the actual resizing of the previous and next components
107541     performResize: function(e) {
107542         var me       = this,
107543             offset   = me.getOffset('dragTarget'),
107544             splitter = me.getSplitter(),
107545             orient   = splitter.orientation,
107546             prevCmp  = me.getPrevCmp(),
107547             nextCmp  = me.getNextCmp(),
107548             owner    = splitter.ownerCt,
107549             layout   = owner.getLayout();
107550
107551         // Inhibit automatic container layout caused by setSize calls below.
107552         owner.suspendLayout = true;
107553
107554         if (orient === 'vertical') {
107555             if (prevCmp) {
107556                 if (!prevCmp.maintainFlex) {
107557                     delete prevCmp.flex;
107558                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
107559                 }
107560             }
107561             if (nextCmp) {
107562                 if (!nextCmp.maintainFlex) {
107563                     delete nextCmp.flex;
107564                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
107565                 }
107566             }
107567         // verticals
107568         } else {
107569             if (prevCmp) {
107570                 if (!prevCmp.maintainFlex) {
107571                     delete prevCmp.flex;
107572                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
107573                 }
107574             }
107575             if (nextCmp) {
107576                 if (!nextCmp.maintainFlex) {
107577                     delete nextCmp.flex;
107578                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
107579                 }
107580             }
107581         }
107582         delete owner.suspendLayout;
107583         layout.onLayout();
107584     },
107585
107586     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
107587     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
107588     // regardless (by onBeforeStart).
107589     endDrag: function () {
107590         var me = this;
107591
107592         if (me.overlay) {
107593              me.overlay.remove();
107594              delete me.overlay;
107595         }
107596
107597         me.callParent(arguments); // this calls onEnd
107598     },
107599
107600     // perform the resize and remove the proxy class from the splitter el
107601     onEnd: function(e) {
107602         var me = this,
107603             splitter = me.getSplitter();
107604             
107605         splitter.removeCls(splitter.baseCls + '-active');
107606         me.performResize();
107607     },
107608
107609     // Track the proxy and set the proper XY coordinates
107610     // while constraining the drag
107611     onDrag: function(e) {
107612         var me        = this,
107613             offset    = me.getOffset('dragTarget'),
107614             splitter  = me.getSplitter(),
107615             splitEl   = splitter.getEl(),
107616             orient    = splitter.orientation;
107617
107618         if (orient === "vertical") {
107619             splitEl.setX(me.startRegion.left + offset[0]);
107620         } else {
107621             splitEl.setY(me.startRegion.top + offset[1]);
107622         }
107623     },
107624
107625     getSplitter: function() {
107626         return Ext.getCmp(this.getDragCt().id);
107627     }
107628 });
107629 /**
107630  * @class Ext.selection.CellModel
107631  * @extends Ext.selection.Model
107632  */
107633 Ext.define('Ext.selection.CellModel', {
107634     extend: 'Ext.selection.Model',
107635     alias: 'selection.cellmodel',
107636     requires: ['Ext.util.KeyNav'],
107637
107638     /**
107639      * @cfg {Boolean} enableKeyNav
107640      * Turns on/off keyboard navigation within the grid.
107641      */
107642     enableKeyNav: true,
107643
107644     /**
107645      * @cfg {Boolean} preventWrap
107646      * Set this configuration to true to prevent wrapping around of selection as
107647      * a user navigates to the first or last column.
107648      */
107649     preventWrap: false,
107650
107651     constructor: function(){
107652         this.addEvents(
107653             /**
107654              * @event deselect
107655              * Fired after a cell is deselected
107656              * @param {Ext.selection.CellModel} this
107657              * @param {Ext.data.Model} record The record of the deselected cell
107658              * @param {Number} row The row index deselected
107659              * @param {Number} column The column index deselected
107660              */
107661             'deselect',
107662
107663             /**
107664              * @event select
107665              * Fired after a cell is selected
107666              * @param {Ext.selection.CellModel} this
107667              * @param {Ext.data.Model} record The record of the selected cell
107668              * @param {Number} row The row index selected
107669              * @param {Number} column The column index selected
107670              */
107671             'select'
107672         );
107673         this.callParent(arguments);
107674     },
107675
107676     bindComponent: function(view) {
107677         var me = this;
107678         me.primaryView = view;
107679         me.views = me.views || [];
107680         me.views.push(view);
107681         me.bind(view.getStore(), true);
107682
107683         view.on({
107684             cellmousedown: me.onMouseDown,
107685             refresh: me.onViewRefresh,
107686             scope: me
107687         });
107688
107689         if (me.enableKeyNav) {
107690             me.initKeyNav(view);
107691         }
107692     },
107693
107694     initKeyNav: function(view) {
107695         var me = this;
107696
107697         if (!view.rendered) {
107698             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
107699             return;
107700         }
107701
107702         view.el.set({
107703             tabIndex: -1
107704         });
107705
107706         // view.el has tabIndex -1 to allow for
107707         // keyboard events to be passed to it.
107708         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
107709             up: me.onKeyUp,
107710             down: me.onKeyDown,
107711             right: me.onKeyRight,
107712             left: me.onKeyLeft,
107713             tab: me.onKeyTab,
107714             scope: me
107715         });
107716     },
107717
107718     getHeaderCt: function() {
107719         return this.primaryView.headerCt;
107720     },
107721
107722     onKeyUp: function(e, t) {
107723         this.move('up', e);
107724     },
107725
107726     onKeyDown: function(e, t) {
107727         this.move('down', e);
107728     },
107729
107730     onKeyLeft: function(e, t) {
107731         this.move('left', e);
107732     },
107733
107734     onKeyRight: function(e, t) {
107735         this.move('right', e);
107736     },
107737
107738     move: function(dir, e) {
107739         var me = this,
107740             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
107741         if (pos) {
107742             me.setCurrentPosition(pos);
107743         }
107744         return pos;
107745     },
107746
107747     /**
107748      * Returns the current position in the format {row: row, column: column}
107749      */
107750     getCurrentPosition: function() {
107751         return this.position;
107752     },
107753
107754     /**
107755      * Sets the current position
107756      * @param {Object} position The position to set.
107757      */
107758     setCurrentPosition: function(pos) {
107759         var me = this;
107760
107761         if (me.position) {
107762             me.onCellDeselect(me.position);
107763         }
107764         if (pos) {
107765             me.onCellSelect(pos);
107766         }
107767         me.position = pos;
107768     },
107769
107770     /**
107771      * Set the current position based on where the user clicks.
107772      * @private
107773      */
107774     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
107775         this.setCurrentPosition({
107776             row: rowIndex,
107777             column: cellIndex
107778         });
107779     },
107780
107781     // notify the view that the cell has been selected to update the ui
107782     // appropriately and bring the cell into focus
107783     onCellSelect: function(position) {
107784         var me = this,
107785             store = me.view.getStore(),
107786             record = store.getAt(position.row);
107787
107788         me.doSelect(record);
107789         me.primaryView.onCellSelect(position);
107790         // TODO: Remove temporary cellFocus call here.
107791         me.primaryView.onCellFocus(position);
107792         me.fireEvent('select', me, record, position.row, position.column);
107793     },
107794
107795     // notify view that the cell has been deselected to update the ui
107796     // appropriately
107797     onCellDeselect: function(position) {
107798         var me = this,
107799             store = me.view.getStore(),
107800             record = store.getAt(position.row);
107801
107802         me.doDeselect(record);
107803         me.primaryView.onCellDeselect(position);
107804         me.fireEvent('deselect', me, record, position.row, position.column);
107805     },
107806
107807     onKeyTab: function(e, t) {
107808         var me = this,
107809             direction = e.shiftKey ? 'left' : 'right',
107810             editingPlugin = me.view.editingPlugin,
107811             position = me.move(direction, e);
107812
107813         if (editingPlugin && position && me.wasEditing) {
107814             editingPlugin.startEditByPosition(position);
107815         }
107816         delete me.wasEditing;
107817     },
107818
107819     onEditorTab: function(editingPlugin, e) {
107820         var me = this,
107821             direction = e.shiftKey ? 'left' : 'right',
107822             position  = me.move(direction, e);
107823
107824         if (position) {
107825             editingPlugin.startEditByPosition(position);
107826             me.wasEditing = true;
107827         }
107828     },
107829
107830     refresh: function() {
107831         var pos = this.getCurrentPosition();
107832         if (pos) {
107833             this.onCellSelect(pos);
107834         }
107835     },
107836
107837     onViewRefresh: function() {
107838         var pos = this.getCurrentPosition();
107839         if (pos) {
107840             this.onCellDeselect(pos);
107841             this.setCurrentPosition(null);
107842         }
107843     },
107844
107845     selectByPosition: function(position) {
107846         this.setCurrentPosition(position);
107847     }
107848 });
107849 /**
107850  * @class Ext.selection.RowModel
107851  * @extends Ext.selection.Model
107852  */
107853 Ext.define('Ext.selection.RowModel', {
107854     extend: 'Ext.selection.Model',
107855     alias: 'selection.rowmodel',
107856     requires: ['Ext.util.KeyNav'],
107857
107858     /**
107859      * @private
107860      * Number of pixels to scroll to the left/right when pressing
107861      * left/right keys.
107862      */
107863     deltaScroll: 5,
107864
107865     /**
107866      * @cfg {Boolean} enableKeyNav
107867      *
107868      * Turns on/off keyboard navigation within the grid.
107869      */
107870     enableKeyNav: true,
107871     
107872     /**
107873      * @cfg {Boolean} [ignoreRightMouseSelection=true]
107874      * True to ignore selections that are made when using the right mouse button if there are
107875      * records that are already selected. If no records are selected, selection will continue 
107876      * as normal
107877      */
107878     ignoreRightMouseSelection: true,
107879
107880     constructor: function(){
107881         this.addEvents(
107882             /**
107883              * @event beforedeselect
107884              * Fired before a record is deselected. If any listener returns false, the
107885              * deselection is cancelled.
107886              * @param {Ext.selection.RowModel} this
107887              * @param {Ext.data.Model} record The deselected record
107888              * @param {Number} index The row index deselected
107889              */
107890             'beforedeselect',
107891
107892             /**
107893              * @event beforeselect
107894              * Fired before a record is selected. If any listener returns false, the
107895              * selection is cancelled.
107896              * @param {Ext.selection.RowModel} this
107897              * @param {Ext.data.Model} record The selected record
107898              * @param {Number} index The row index selected
107899              */
107900             'beforeselect',
107901
107902             /**
107903              * @event deselect
107904              * Fired after a record is deselected
107905              * @param {Ext.selection.RowModel} this
107906              * @param {Ext.data.Model} record The deselected record
107907              * @param {Number} index The row index deselected
107908              */
107909             'deselect',
107910
107911             /**
107912              * @event select
107913              * Fired after a record is selected
107914              * @param {Ext.selection.RowModel} this
107915              * @param {Ext.data.Model} record The selected record
107916              * @param {Number} index The row index selected
107917              */
107918             'select'
107919         );
107920         this.callParent(arguments);
107921     },
107922
107923     bindComponent: function(view) {
107924         var me = this;
107925
107926         me.views = me.views || [];
107927         me.views.push(view);
107928         me.bind(view.getStore(), true);
107929
107930         view.on({
107931             itemmousedown: me.onRowMouseDown,
107932             scope: me
107933         });
107934
107935         if (me.enableKeyNav) {
107936             me.initKeyNav(view);
107937         }
107938     },
107939
107940     initKeyNav: function(view) {
107941         var me = this;
107942
107943         if (!view.rendered) {
107944             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
107945             return;
107946         }
107947
107948         view.el.set({
107949             tabIndex: -1
107950         });
107951
107952         // view.el has tabIndex -1 to allow for
107953         // keyboard events to be passed to it.
107954         me.keyNav = new Ext.util.KeyNav(view.el, {
107955             up: me.onKeyUp,
107956             down: me.onKeyDown,
107957             right: me.onKeyRight,
107958             left: me.onKeyLeft,
107959             pageDown: me.onKeyPageDown,
107960             pageUp: me.onKeyPageUp,
107961             home: me.onKeyHome,
107962             end: me.onKeyEnd,
107963             scope: me
107964         });
107965         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
107966     },
107967
107968     // Returns the number of rows currently visible on the screen or
107969     // false if there were no rows. This assumes that all rows are
107970     // of the same height and the first view is accurate.
107971     getRowsVisible: function() {
107972         var rowsVisible = false,
107973             view = this.views[0],
107974             row = view.getNode(0),
107975             rowHeight, gridViewHeight;
107976
107977         if (row) {
107978             rowHeight = Ext.fly(row).getHeight();
107979             gridViewHeight = view.el.getHeight();
107980             rowsVisible = Math.floor(gridViewHeight / rowHeight);
107981         }
107982
107983         return rowsVisible;
107984     },
107985
107986     // go to last visible record in grid.
107987     onKeyEnd: function(e, t) {
107988         var me = this,
107989             last = me.store.getAt(me.store.getCount() - 1);
107990
107991         if (last) {
107992             if (e.shiftKey) {
107993                 me.selectRange(last, me.lastFocused || 0);
107994                 me.setLastFocused(last);
107995             } else if (e.ctrlKey) {
107996                 me.setLastFocused(last);
107997             } else {
107998                 me.doSelect(last);
107999             }
108000         }
108001     },
108002
108003     // go to first visible record in grid.
108004     onKeyHome: function(e, t) {
108005         var me = this,
108006             first = me.store.getAt(0);
108007
108008         if (first) {
108009             if (e.shiftKey) {
108010                 me.selectRange(first, me.lastFocused || 0);
108011                 me.setLastFocused(first);
108012             } else if (e.ctrlKey) {
108013                 me.setLastFocused(first);
108014             } else {
108015                 me.doSelect(first, false);
108016             }
108017         }
108018     },
108019
108020     // Go one page up from the lastFocused record in the grid.
108021     onKeyPageUp: function(e, t) {
108022         var me = this,
108023             rowsVisible = me.getRowsVisible(),
108024             selIdx,
108025             prevIdx,
108026             prevRecord,
108027             currRec;
108028
108029         if (rowsVisible) {
108030             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
108031             prevIdx = selIdx - rowsVisible;
108032             if (prevIdx < 0) {
108033                 prevIdx = 0;
108034             }
108035             prevRecord = me.store.getAt(prevIdx);
108036             if (e.shiftKey) {
108037                 currRec = me.store.getAt(selIdx);
108038                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
108039                 me.setLastFocused(prevRecord);
108040             } else if (e.ctrlKey) {
108041                 e.preventDefault();
108042                 me.setLastFocused(prevRecord);
108043             } else {
108044                 me.doSelect(prevRecord);
108045             }
108046
108047         }
108048     },
108049
108050     // Go one page down from the lastFocused record in the grid.
108051     onKeyPageDown: function(e, t) {
108052         var me = this,
108053             rowsVisible = me.getRowsVisible(),
108054             selIdx,
108055             nextIdx,
108056             nextRecord,
108057             currRec;
108058
108059         if (rowsVisible) {
108060             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
108061             nextIdx = selIdx + rowsVisible;
108062             if (nextIdx >= me.store.getCount()) {
108063                 nextIdx = me.store.getCount() - 1;
108064             }
108065             nextRecord = me.store.getAt(nextIdx);
108066             if (e.shiftKey) {
108067                 currRec = me.store.getAt(selIdx);
108068                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
108069                 me.setLastFocused(nextRecord);
108070             } else if (e.ctrlKey) {
108071                 // some browsers, this means go thru browser tabs
108072                 // attempt to stop.
108073                 e.preventDefault();
108074                 me.setLastFocused(nextRecord);
108075             } else {
108076                 me.doSelect(nextRecord);
108077             }
108078         }
108079     },
108080
108081     // Select/Deselect based on pressing Spacebar.
108082     // Assumes a SIMPLE selectionmode style
108083     onKeyPress: function(e, t) {
108084         if (e.getKey() === e.SPACE) {
108085             e.stopEvent();
108086             var me = this,
108087                 record = me.lastFocused;
108088
108089             if (record) {
108090                 if (me.isSelected(record)) {
108091                     me.doDeselect(record, false);
108092                 } else {
108093                     me.doSelect(record, true);
108094                 }
108095             }
108096         }
108097     },
108098
108099     // Navigate one record up. This could be a selection or
108100     // could be simply focusing a record for discontiguous
108101     // selection. Provides bounds checking.
108102     onKeyUp: function(e, t) {
108103         var me = this,
108104             view = me.views[0],
108105             idx  = me.store.indexOf(me.lastFocused),
108106             record;
108107
108108         if (idx > 0) {
108109             // needs to be the filtered count as thats what
108110             // will be visible.
108111             record = me.store.getAt(idx - 1);
108112             if (e.shiftKey && me.lastFocused) {
108113                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
108114                     me.doDeselect(me.lastFocused, true);
108115                     me.setLastFocused(record);
108116                 } else if (!me.isSelected(me.lastFocused)) {
108117                     me.doSelect(me.lastFocused, true);
108118                     me.doSelect(record, true);
108119                 } else {
108120                     me.doSelect(record, true);
108121                 }
108122             } else if (e.ctrlKey) {
108123                 me.setLastFocused(record);
108124             } else {
108125                 me.doSelect(record);
108126                 //view.focusRow(idx - 1);
108127             }
108128         }
108129         // There was no lastFocused record, and the user has pressed up
108130         // Ignore??
108131         //else if (this.selected.getCount() == 0) {
108132         //
108133         //    this.doSelect(record);
108134         //    //view.focusRow(idx - 1);
108135         //}
108136     },
108137
108138     // Navigate one record down. This could be a selection or
108139     // could be simply focusing a record for discontiguous
108140     // selection. Provides bounds checking.
108141     onKeyDown: function(e, t) {
108142         var me = this,
108143             view = me.views[0],
108144             idx  = me.store.indexOf(me.lastFocused),
108145             record;
108146
108147         // needs to be the filtered count as thats what
108148         // will be visible.
108149         if (idx + 1 < me.store.getCount()) {
108150             record = me.store.getAt(idx + 1);
108151             if (me.selected.getCount() === 0) {
108152                 me.doSelect(record);
108153                 //view.focusRow(idx + 1);
108154             } else if (e.shiftKey && me.lastFocused) {
108155                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
108156                     me.doDeselect(me.lastFocused, true);
108157                     me.setLastFocused(record);
108158                 } else if (!me.isSelected(me.lastFocused)) {
108159                     me.doSelect(me.lastFocused, true);
108160                     me.doSelect(record, true);
108161                 } else {
108162                     me.doSelect(record, true);
108163                 }
108164             } else if (e.ctrlKey) {
108165                 me.setLastFocused(record);
108166             } else {
108167                 me.doSelect(record);
108168                 //view.focusRow(idx + 1);
108169             }
108170         }
108171     },
108172
108173     scrollByDeltaX: function(delta) {
108174         var view    = this.views[0],
108175             section = view.up(),
108176             hScroll = section.horizontalScroller;
108177
108178         if (hScroll) {
108179             hScroll.scrollByDeltaX(delta);
108180         }
108181     },
108182
108183     onKeyLeft: function(e, t) {
108184         this.scrollByDeltaX(-this.deltaScroll);
108185     },
108186
108187     onKeyRight: function(e, t) {
108188         this.scrollByDeltaX(this.deltaScroll);
108189     },
108190
108191     // Select the record with the event included so that
108192     // we can take into account ctrlKey, shiftKey, etc
108193     onRowMouseDown: function(view, record, item, index, e) {
108194         view.el.focus();
108195         if (!this.allowRightMouseSelection(e)) {
108196             return;
108197         }
108198         this.selectWithEvent(record, e);
108199     },
108200     
108201     /**
108202      * Checks whether a selection should proceed based on the ignoreRightMouseSelection
108203      * option.
108204      * @private
108205      * @param {Ext.EventObject} e The event
108206      * @return {Boolean} False if the selection should not proceed
108207      */
108208     allowRightMouseSelection: function(e) {
108209         var disallow = this.ignoreRightMouseSelection && e.button !== 0;
108210         if (disallow) {
108211             disallow = this.hasSelection();
108212         }
108213         return !disallow;
108214     },
108215
108216     // Allow the GridView to update the UI by
108217     // adding/removing a CSS class from the row.
108218     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
108219         var me      = this,
108220             views   = me.views,
108221             viewsLn = views.length,
108222             store   = me.store,
108223             rowIdx  = store.indexOf(record),
108224             eventName = isSelected ? 'select' : 'deselect',
108225             i = 0;
108226
108227         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
108228                 commitFn() !== false) {
108229
108230             for (; i < viewsLn; i++) {
108231                 if (isSelected) {
108232                     views[i].onRowSelect(rowIdx, suppressEvent);
108233                 } else {
108234                     views[i].onRowDeselect(rowIdx, suppressEvent);
108235                 }
108236             }
108237
108238             if (!suppressEvent) {
108239                 me.fireEvent(eventName, me, record, rowIdx);
108240             }
108241         }
108242     },
108243
108244     // Provide indication of what row was last focused via
108245     // the gridview.
108246     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
108247         var views   = this.views,
108248             viewsLn = views.length,
108249             store   = this.store,
108250             rowIdx,
108251             i = 0;
108252
108253         if (oldFocused) {
108254             rowIdx = store.indexOf(oldFocused);
108255             if (rowIdx != -1) {
108256                 for (; i < viewsLn; i++) {
108257                     views[i].onRowFocus(rowIdx, false);
108258                 }
108259             }
108260         }
108261
108262         if (newFocused) {
108263             rowIdx = store.indexOf(newFocused);
108264             if (rowIdx != -1) {
108265                 for (i = 0; i < viewsLn; i++) {
108266                     views[i].onRowFocus(rowIdx, true, supressFocus);
108267                 }
108268             }
108269         }
108270     },
108271
108272     onEditorTab: function(editingPlugin, e) {
108273         var me = this,
108274             view = me.views[0],
108275             record = editingPlugin.getActiveRecord(),
108276             header = editingPlugin.getActiveColumn(),
108277             position = view.getPosition(record, header),
108278             direction = e.shiftKey ? 'left' : 'right',
108279             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
108280
108281         if (newPosition) {
108282             editingPlugin.startEditByPosition(newPosition);
108283         }
108284     },
108285
108286     selectByPosition: function(position) {
108287         var record = this.store.getAt(position.row);
108288         this.select(record);
108289     }
108290 });
108291 /**
108292  * @class Ext.selection.CheckboxModel
108293  * @extends Ext.selection.RowModel
108294  *
108295  * A selection model that renders a column of checkboxes that can be toggled to
108296  * select or deselect rows. The default mode for this selection model is MULTI.
108297  *
108298  * The selection model will inject a header for the checkboxes in the first view
108299  * and according to the 'injectCheckbox' configuration.
108300  */
108301 Ext.define('Ext.selection.CheckboxModel', {
108302     alias: 'selection.checkboxmodel',
108303     extend: 'Ext.selection.RowModel',
108304
108305     /**
108306      * @cfg {String} mode
108307      * Modes of selection.
108308      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
108309      */
108310     mode: 'MULTI',
108311
108312     /**
108313      * @cfg {Number/Boolean/String} injectCheckbox
108314      * Instructs the SelectionModel whether or not to inject the checkbox header
108315      * automatically or not. (Note: By not placing the checkbox in manually, the
108316      * grid view will need to be rendered 2x on initial render.)
108317      * Supported values are a Number index, false and the strings 'first' and 'last'.
108318      */
108319     injectCheckbox: 0,
108320
108321     /**
108322      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
108323      * checkbox column.
108324      */
108325     checkOnly: false,
108326
108327     headerWidth: 24,
108328
108329     // private
108330     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
108331
108332     bindComponent: function(view) {
108333         var me = this;
108334
108335         me.sortable = false;
108336         me.callParent(arguments);
108337         if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
108338             // if we have a locked header, only hook up to the first
108339             view.headerCt.on('headerclick', me.onHeaderClick, me);
108340             me.addCheckbox(true);
108341             me.mon(view.ownerCt, 'reconfigure', me.addCheckbox, me);
108342         }
108343     },
108344
108345     hasLockedHeader: function(){
108346         var hasLocked = false;
108347         Ext.each(this.views, function(view){
108348             if (view.headerCt.lockedCt) {
108349                 hasLocked = true;
108350                 return false;
108351             }
108352         });
108353         return hasLocked;
108354     },
108355
108356     /**
108357      * Add the header checkbox to the header row
108358      * @private
108359      * @param {Boolean} initial True if we're binding for the first time.
108360      */
108361     addCheckbox: function(initial){
108362         var me = this,
108363             checkbox = me.injectCheckbox,
108364             view = me.views[0],
108365             headerCt = view.headerCt;
108366
108367         if (checkbox !== false) {
108368             if (checkbox == 'first') {
108369                 checkbox = 0;
108370             } else if (checkbox == 'last') {
108371                 checkbox = headerCt.getColumnCount();
108372             }
108373             headerCt.add(checkbox,  me.getHeaderConfig());
108374         }
108375
108376         if (initial !== true) {
108377             view.refresh();
108378         }
108379     },
108380
108381     /**
108382      * Toggle the ui header between checked and unchecked state.
108383      * @param {Boolean} isChecked
108384      * @private
108385      */
108386     toggleUiHeader: function(isChecked) {
108387         var view     = this.views[0],
108388             headerCt = view.headerCt,
108389             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
108390
108391         if (checkHd) {
108392             if (isChecked) {
108393                 checkHd.el.addCls(this.checkerOnCls);
108394             } else {
108395                 checkHd.el.removeCls(this.checkerOnCls);
108396             }
108397         }
108398     },
108399
108400     /**
108401      * Toggle between selecting all and deselecting all when clicking on
108402      * a checkbox header.
108403      */
108404     onHeaderClick: function(headerCt, header, e) {
108405         if (header.isCheckerHd) {
108406             e.stopEvent();
108407             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
108408             if (isChecked) {
108409                 // We have to supress the event or it will scrollTo the change
108410                 this.deselectAll(true);
108411             } else {
108412                 // We have to supress the event or it will scrollTo the change
108413                 this.selectAll(true);
108414             }
108415         }
108416     },
108417
108418     /**
108419      * Retrieve a configuration to be used in a HeaderContainer.
108420      * This should be used when injectCheckbox is set to false.
108421      */
108422     getHeaderConfig: function() {
108423         var me = this;
108424
108425         return {
108426             isCheckerHd: true,
108427             text : '&#160;',
108428             width: me.headerWidth,
108429             sortable: false,
108430             draggable: false,
108431             resizable: false,
108432             hideable: false,
108433             menuDisabled: true,
108434             dataIndex: '',
108435             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
108436             renderer: Ext.Function.bind(me.renderer, me),
108437             locked: me.hasLockedHeader()
108438         };
108439     },
108440
108441     /**
108442      * Generates the HTML to be rendered in the injected checkbox column for each row.
108443      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
108444      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
108445      */
108446     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
108447         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
108448         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
108449     },
108450
108451     // override
108452     onRowMouseDown: function(view, record, item, index, e) {
108453         view.el.focus();
108454         var me = this,
108455             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
108456             
108457         if (!me.allowRightMouseSelection(e)) {
108458             return;
108459         }
108460
108461         // checkOnly set, but we didn't click on a checker.
108462         if (me.checkOnly && !checker) {
108463             return;
108464         }
108465
108466         if (checker) {
108467             var mode = me.getSelectionMode();
108468             // dont change the mode if its single otherwise
108469             // we would get multiple selection
108470             if (mode !== 'SINGLE') {
108471                 me.setSelectionMode('SIMPLE');
108472             }
108473             me.selectWithEvent(record, e);
108474             me.setSelectionMode(mode);
108475         } else {
108476             me.selectWithEvent(record, e);
108477         }
108478     },
108479
108480     /**
108481      * Synchronize header checker value as selection changes.
108482      * @private
108483      */
108484     onSelectChange: function() {
108485         this.callParent(arguments);
108486
108487         // check to see if all records are selected
108488         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
108489         this.toggleUiHeader(hdSelectStatus);
108490     }
108491 });
108492
108493 /**
108494  * @class Ext.selection.TreeModel
108495  * @extends Ext.selection.RowModel
108496  *
108497  * Adds custom behavior for left/right keyboard navigation for use with a tree.
108498  * Depends on the view having an expand and collapse method which accepts a
108499  * record.
108500  * 
108501  * @private
108502  */
108503 Ext.define('Ext.selection.TreeModel', {
108504     extend: 'Ext.selection.RowModel',
108505     alias: 'selection.treemodel',
108506     
108507     // typically selection models prune records from the selection
108508     // model when they are removed, because the TreeView constantly
108509     // adds/removes records as they are expanded/collapsed
108510     pruneRemoved: false,
108511     
108512     onKeyRight: function(e, t) {
108513         var focused = this.getLastFocused(),
108514             view    = this.view;
108515             
108516         if (focused) {
108517             // tree node is already expanded, go down instead
108518             // this handles both the case where we navigate to firstChild and if
108519             // there are no children to the nextSibling
108520             if (focused.isExpanded()) {
108521                 this.onKeyDown(e, t);
108522             // if its not a leaf node, expand it
108523             } else if (!focused.isLeaf()) {
108524                 view.expand(focused);
108525             }
108526         }
108527     },
108528     
108529     onKeyLeft: function(e, t) {
108530         var focused = this.getLastFocused(),
108531             view    = this.view,
108532             viewSm  = view.getSelectionModel(),
108533             parentNode, parentRecord;
108534
108535         if (focused) {
108536             parentNode = focused.parentNode;
108537             // if focused node is already expanded, collapse it
108538             if (focused.isExpanded()) {
108539                 view.collapse(focused);
108540             // has a parentNode and its not root
108541             // TODO: this needs to cover the case where the root isVisible
108542             } else if (parentNode && !parentNode.isRoot()) {
108543                 // Select a range of records when doing multiple selection.
108544                 if (e.shiftKey) {
108545                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
108546                     viewSm.setLastFocused(parentNode);
108547                 // just move focus, not selection
108548                 } else if (e.ctrlKey) {
108549                     viewSm.setLastFocused(parentNode);
108550                 // select it
108551                 } else {
108552                     viewSm.select(parentNode);
108553                 }
108554             }
108555         }
108556     },
108557     
108558     onKeyPress: function(e, t) {
108559         var key = e.getKey(),
108560             selected, 
108561             checked;
108562         
108563         if (key === e.SPACE || key === e.ENTER) {
108564             e.stopEvent();
108565             selected = this.getLastSelected();
108566             if (selected) {
108567                 this.view.onCheckChange(selected);
108568             }
108569         } else {
108570             this.callParent(arguments);
108571         }
108572     }
108573 });
108574
108575 /**
108576  * @class Ext.slider.Thumb
108577  * @extends Ext.Base
108578  * @private
108579  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
108580  * be created internally by an {@link Ext.slider.Multi Multi slider}.
108581  */
108582 Ext.define('Ext.slider.Thumb', {
108583     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
108584     /**
108585      * @private
108586      * @property {Number} topThumbZIndex
108587      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
108588      */
108589     topZIndex: 10000,
108590
108591     /**
108592      * @cfg {Ext.slider.MultiSlider} slider (required)
108593      * The Slider to render to.
108594      */
108595
108596     /**
108597      * Creates new slider thumb.
108598      * @param {Object} config (optional) Config object.
108599      */
108600     constructor: function(config) {
108601         var me = this;
108602
108603         /**
108604          * @property {Ext.slider.MultiSlider} slider
108605          * The slider this thumb is contained within
108606          */
108607         Ext.apply(me, config || {}, {
108608             cls: Ext.baseCSSPrefix + 'slider-thumb',
108609
108610             /**
108611              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
108612              */
108613             constrain: false
108614         });
108615         me.callParent([config]);
108616
108617         if (me.slider.vertical) {
108618             Ext.apply(me, Ext.slider.Thumb.Vertical);
108619         }
108620     },
108621
108622     /**
108623      * Renders the thumb into a slider
108624      */
108625     render: function() {
108626         var me = this;
108627
108628         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
108629         if (me.disabled) {
108630             me.disable();
108631         }
108632         me.initEvents();
108633     },
108634
108635     /**
108636      * @private
108637      * move the thumb
108638      */
108639     move: function(v, animate){
108640         if(!animate){
108641             this.el.setLeft(v);
108642         }else{
108643             Ext.create('Ext.fx.Anim', {
108644                 target: this.el,
108645                 duration: 350,
108646                 to: {
108647                     left: v
108648                 }
108649             });
108650         }
108651     },
108652
108653     /**
108654      * @private
108655      * Bring thumb dom element to front.
108656      */
108657     bringToFront: function() {
108658         this.el.setStyle('zIndex', this.topZIndex);
108659     },
108660
108661     /**
108662      * @private
108663      * Send thumb dom element to back.
108664      */
108665     sendToBack: function() {
108666         this.el.setStyle('zIndex', '');
108667     },
108668
108669     /**
108670      * Enables the thumb if it is currently disabled
108671      */
108672     enable: function() {
108673         var me = this;
108674
108675         me.disabled = false;
108676         if (me.el) {
108677             me.el.removeCls(me.slider.disabledCls);
108678         }
108679     },
108680
108681     /**
108682      * Disables the thumb if it is currently enabled
108683      */
108684     disable: function() {
108685         var me = this;
108686
108687         me.disabled = true;
108688         if (me.el) {
108689             me.el.addCls(me.slider.disabledCls);
108690         }
108691     },
108692
108693     /**
108694      * Sets up an Ext.dd.DragTracker for this thumb
108695      */
108696     initEvents: function() {
108697         var me = this,
108698             el = me.el;
108699
108700         me.tracker = Ext.create('Ext.dd.DragTracker', {
108701             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
108702             onStart      : Ext.Function.bind(me.onDragStart, me),
108703             onDrag       : Ext.Function.bind(me.onDrag, me),
108704             onEnd        : Ext.Function.bind(me.onDragEnd, me),
108705             tolerance    : 3,
108706             autoStart    : 300,
108707             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
108708         });
108709
108710         me.tracker.initEl(el);
108711     },
108712
108713     /**
108714      * @private
108715      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
108716      * this returns false to disable the DragTracker too.
108717      * @return {Boolean} False if the slider is currently disabled
108718      */
108719     onBeforeDragStart : function(e) {
108720         if (this.disabled) {
108721             return false;
108722         } else {
108723             this.slider.promoteThumb(this);
108724             return true;
108725         }
108726     },
108727
108728     /**
108729      * @private
108730      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
108731      * to the thumb and fires the 'dragstart' event
108732      */
108733     onDragStart: function(e){
108734         var me = this;
108735
108736         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
108737         me.dragging = true;
108738         me.dragStartValue = me.value;
108739
108740         me.slider.fireEvent('dragstart', me.slider, e, me);
108741     },
108742
108743     /**
108744      * @private
108745      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
108746      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
108747      */
108748     onDrag: function(e) {
108749         var me       = this,
108750             slider   = me.slider,
108751             index    = me.index,
108752             newValue = me.getNewValue(),
108753             above,
108754             below;
108755
108756         if (me.constrain) {
108757             above = slider.thumbs[index + 1];
108758             below = slider.thumbs[index - 1];
108759
108760             if (below !== undefined && newValue <= below.value) {
108761                 newValue = below.value;
108762             }
108763
108764             if (above !== undefined && newValue >= above.value) {
108765                 newValue = above.value;
108766             }
108767         }
108768
108769         slider.setValue(index, newValue, false);
108770         slider.fireEvent('drag', slider, e, me);
108771     },
108772
108773     getNewValue: function() {
108774         var slider = this.slider,
108775             pos = slider.innerEl.translatePoints(this.tracker.getXY());
108776
108777         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
108778     },
108779
108780     /**
108781      * @private
108782      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
108783      * fires the 'changecomplete' event with the new value
108784      */
108785     onDragEnd: function(e) {
108786         var me     = this,
108787             slider = me.slider,
108788             value  = me.value;
108789
108790         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
108791
108792         me.dragging = false;
108793         slider.fireEvent('dragend', slider, e);
108794
108795         if (me.dragStartValue != value) {
108796             slider.fireEvent('changecomplete', slider, value, me);
108797         }
108798     },
108799
108800     destroy: function() {
108801         Ext.destroy(this.tracker);
108802     },
108803     statics: {
108804         // Method overrides to support vertical dragging of thumb within slider
108805         Vertical: {
108806             getNewValue: function() {
108807                 var slider   = this.slider,
108808                     innerEl  = slider.innerEl,
108809                     pos      = innerEl.translatePoints(this.tracker.getXY()),
108810                     bottom   = innerEl.getHeight() - pos.top;
108811
108812                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
108813             },
108814             move: function(v, animate) {
108815                 if (!animate) {
108816                     this.el.setBottom(v);
108817                 } else {
108818                     Ext.create('Ext.fx.Anim', {
108819                         target: this.el,
108820                         duration: 350,
108821                         to: {
108822                             bottom: v
108823                         }
108824                     });
108825                 }
108826             }
108827         }
108828     }
108829 });
108830
108831 /**
108832  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
108833  * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
108834  * options to the slider directly.
108835  *
108836  *     @example
108837  *     Ext.create('Ext.slider.Single', {
108838  *         width: 214,
108839  *         minValue: 0,
108840  *         maxValue: 100,
108841  *         useTips: true,
108842  *         renderTo: Ext.getBody()
108843  *     });
108844  *
108845  * Optionally provide your own tip text by passing tipText:
108846  *
108847  *     @example
108848  *     Ext.create('Ext.slider.Single', {
108849  *         width: 214,
108850  *         minValue: 0,
108851  *         maxValue: 100,
108852  *         useTips: true,
108853  *         tipText: function(thumb){
108854  *             return Ext.String.format('**{0}% complete**', thumb.value);
108855  *         },
108856  *         renderTo: Ext.getBody()
108857  *     });
108858  */
108859 Ext.define('Ext.slider.Tip', {
108860     extend: 'Ext.tip.Tip',
108861     minWidth: 10,
108862     alias: 'widget.slidertip',
108863     offsets : [0, -10],
108864
108865     isSliderTip: true,
108866
108867     init: function(slider) {
108868         var me = this;
108869
108870         slider.on({
108871             scope    : me,
108872             dragstart: me.onSlide,
108873             drag     : me.onSlide,
108874             dragend  : me.hide,
108875             destroy  : me.destroy
108876         });
108877     },
108878     /**
108879      * @private
108880      * Called whenever a dragstart or drag event is received on the associated Thumb.
108881      * Aligns the Tip with the Thumb's new position.
108882      * @param {Ext.slider.MultiSlider} slider The slider
108883      * @param {Ext.EventObject} e The Event object
108884      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
108885      */
108886     onSlide : function(slider, e, thumb) {
108887         var me = this;
108888         me.show();
108889         me.update(me.getText(thumb));
108890         me.doComponentLayout();
108891         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
108892     },
108893
108894     /**
108895      * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
108896      * Thumb that the Tip is attached to. Override to customize.
108897      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
108898      * @return {String} The text to display in the tip
108899      */
108900     getText : function(thumb) {
108901         return String(thumb.value);
108902     }
108903 });
108904 /**
108905  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
108906  * and animation. Can be added as an item to any container.
108907  *
108908  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
108909  *
108910  *     @example
108911  *     Ext.create('Ext.slider.Multi', {
108912  *         width: 200,
108913  *         values: [25, 50, 75],
108914  *         increment: 5,
108915  *         minValue: 0,
108916  *         maxValue: 100,
108917  *
108918  *         // this defaults to true, setting to false allows the thumbs to pass each other
108919  *         constrainThumbs: false,
108920  *         renderTo: Ext.getBody()
108921  *     });
108922  */
108923 Ext.define('Ext.slider.Multi', {
108924     extend: 'Ext.form.field.Base',
108925     alias: 'widget.multislider',
108926     alternateClassName: 'Ext.slider.MultiSlider',
108927
108928     requires: [
108929         'Ext.slider.Thumb',
108930         'Ext.slider.Tip',
108931         'Ext.Number',
108932         'Ext.util.Format',
108933         'Ext.Template',
108934         'Ext.layout.component.field.Slider'
108935     ],
108936
108937     // note: {id} here is really {inputId}, but {cmpId} is available
108938     fieldSubTpl: [
108939         '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
108940             '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
108941                 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
108942                     '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
108943                 '</div>',
108944             '</div>',
108945         '</div>',
108946         {
108947             disableFormats: true,
108948             compiled: true
108949         }
108950     ],
108951
108952     /**
108953      * @cfg {Number} value
108954      * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
108955      * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
108956      */
108957
108958     /**
108959      * @cfg {Number[]} values
108960      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
108961      * in this array. This will take precedence over the single {@link #value} config.
108962      */
108963
108964     /**
108965      * @cfg {Boolean} vertical
108966      * Orient the Slider vertically rather than horizontally.
108967      */
108968     vertical: false,
108969
108970     /**
108971      * @cfg {Number} minValue
108972      * The minimum value for the Slider.
108973      */
108974     minValue: 0,
108975
108976     /**
108977      * @cfg {Number} maxValue
108978      * The maximum value for the Slider.
108979      */
108980     maxValue: 100,
108981
108982     /**
108983      * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
108984      *
108985      * To disable rounding, configure as **false**.
108986      */
108987     decimalPrecision: 0,
108988
108989     /**
108990      * @cfg {Number} keyIncrement
108991      * How many units to change the Slider when adjusting with keyboard navigation. If the increment
108992      * config is larger, it will be used instead.
108993      */
108994     keyIncrement: 1,
108995
108996     /**
108997      * @cfg {Number} increment
108998      * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
108999      */
109000     increment: 0,
109001
109002     /**
109003      * @private
109004      * @property {Number[]} clickRange
109005      * 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],
109006      * 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'
109007      * 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
109008      */
109009     clickRange: [5,15],
109010
109011     /**
109012      * @cfg {Boolean} clickToChange
109013      * Determines whether or not clicking on the Slider axis will change the slider.
109014      */
109015     clickToChange : true,
109016
109017     /**
109018      * @cfg {Boolean} animate
109019      * Turn on or off animation.
109020      */
109021     animate: true,
109022
109023     /**
109024      * @property {Boolean} dragging
109025      * True while the thumb is in a drag operation
109026      */
109027     dragging: false,
109028
109029     /**
109030      * @cfg {Boolean} constrainThumbs
109031      * True to disallow thumbs from overlapping one another.
109032      */
109033     constrainThumbs: true,
109034
109035     componentLayout: 'sliderfield',
109036
109037     /**
109038      * @cfg {Boolean} useTips
109039      * True to use an Ext.slider.Tip to display tips for the value.
109040      */
109041     useTips : true,
109042
109043     /**
109044      * @cfg {Function} tipText
109045      * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
109046      * plugin.
109047      */
109048     tipText : null,
109049
109050     ariaRole: 'slider',
109051
109052     // private override
109053     initValue: function() {
109054         var me = this,
109055             extValue = Ext.value,
109056             // Fallback for initial values: values config -> value config -> minValue config -> 0
109057             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
109058             i = 0,
109059             len = values.length;
109060
109061         // Store for use in dirty check
109062         me.originalValue = values;
109063
109064         // Add a thumb for each value
109065         for (; i < len; i++) {
109066             me.addThumb(values[i]);
109067         }
109068     },
109069
109070     // private override
109071     initComponent : function() {
109072         var me = this,
109073             tipPlug,
109074             hasTip;
109075
109076         /**
109077          * @property {Array} thumbs
109078          * Array containing references to each thumb
109079          */
109080         me.thumbs = [];
109081
109082         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
109083
109084         me.addEvents(
109085             /**
109086              * @event beforechange
109087              * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
109088              * event and prevent the slider from changing.
109089              * @param {Ext.slider.Multi} slider The slider
109090              * @param {Number} newValue The new value which the slider is being changed to.
109091              * @param {Number} oldValue The old value which the slider was previously.
109092              */
109093             'beforechange',
109094
109095             /**
109096              * @event change
109097              * Fires when the slider value is changed.
109098              * @param {Ext.slider.Multi} slider The slider
109099              * @param {Number} newValue The new value which the slider has been changed to.
109100              * @param {Ext.slider.Thumb} thumb The thumb that was changed
109101              */
109102             'change',
109103
109104             /**
109105              * @event changecomplete
109106              * Fires when the slider value is changed by the user and any drag operations have completed.
109107              * @param {Ext.slider.Multi} slider The slider
109108              * @param {Number} newValue The new value which the slider has been changed to.
109109              * @param {Ext.slider.Thumb} thumb The thumb that was changed
109110              */
109111             'changecomplete',
109112
109113             /**
109114              * @event dragstart
109115              * Fires after a drag operation has started.
109116              * @param {Ext.slider.Multi} slider The slider
109117              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
109118              */
109119             'dragstart',
109120
109121             /**
109122              * @event drag
109123              * Fires continuously during the drag operation while the mouse is moving.
109124              * @param {Ext.slider.Multi} slider The slider
109125              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
109126              */
109127             'drag',
109128
109129             /**
109130              * @event dragend
109131              * Fires after the drag operation has completed.
109132              * @param {Ext.slider.Multi} slider The slider
109133              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
109134              */
109135             'dragend'
109136         );
109137
109138         if (me.vertical) {
109139             Ext.apply(me, Ext.slider.Multi.Vertical);
109140         }
109141
109142         me.callParent();
109143
109144         // only can use it if it exists.
109145         if (me.useTips) {
109146             tipPlug = me.tipText ? {getText: me.tipText} : {};
109147             me.plugins = me.plugins || [];
109148             Ext.each(me.plugins, function(plug){
109149                 if (plug.isSliderTip) {
109150                     hasTip = true;
109151                     return false;
109152                 }
109153             });
109154             if (!hasTip) {
109155                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
109156             }
109157         }
109158     },
109159
109160     /**
109161      * Creates a new thumb and adds it to the slider
109162      * @param {Number} value The initial value to set on the thumb. Defaults to 0
109163      * @return {Ext.slider.Thumb} The thumb
109164      */
109165     addThumb: function(value) {
109166         var me = this,
109167             thumb = Ext.create('Ext.slider.Thumb', {
109168             value    : value,
109169             slider   : me,
109170             index    : me.thumbs.length,
109171             constrain: me.constrainThumbs
109172         });
109173         me.thumbs.push(thumb);
109174
109175         //render the thumb now if needed
109176         if (me.rendered) {
109177             thumb.render();
109178         }
109179
109180         return thumb;
109181     },
109182
109183     /**
109184      * @private
109185      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
109186      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
109187      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
109188      * range, which can result in the user not being able to move any of them.
109189      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
109190      */
109191     promoteThumb: function(topThumb) {
109192         var thumbs = this.thumbs,
109193             ln = thumbs.length,
109194             zIndex, thumb, i;
109195
109196         for (i = 0; i < ln; i++) {
109197             thumb = thumbs[i];
109198
109199             if (thumb == topThumb) {
109200                 thumb.bringToFront();
109201             } else {
109202                 thumb.sendToBack();
109203             }
109204         }
109205     },
109206
109207     // private override
109208     onRender : function() {
109209         var me = this,
109210             i = 0,
109211             thumbs = me.thumbs,
109212             len = thumbs.length,
109213             thumb;
109214
109215         Ext.applyIf(me.subTplData, {
109216             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
109217             minValue: me.minValue,
109218             maxValue: me.maxValue,
109219             value: me.value
109220         });
109221
109222         me.addChildEls('endEl', 'innerEl', 'focusEl');
109223
109224         me.callParent(arguments);
109225
109226         //render each thumb
109227         for (; i < len; i++) {
109228             thumbs[i].render();
109229         }
109230
109231         //calculate the size of half a thumb
109232         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
109233         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
109234
109235     },
109236
109237     /**
109238      * Utility method to set the value of the field when the slider changes.
109239      * @param {Object} slider The slider object.
109240      * @param {Object} v The new value.
109241      * @private
109242      */
109243     onChange : function(slider, v) {
109244         this.setValue(v, undefined, true);
109245     },
109246
109247     /**
109248      * @private
109249      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
109250      */
109251     initEvents : function() {
109252         var me = this;
109253
109254         me.mon(me.el, {
109255             scope    : me,
109256             mousedown: me.onMouseDown,
109257             keydown  : me.onKeyDown,
109258             change : me.onChange
109259         });
109260
109261         me.focusEl.swallowEvent("click", true);
109262     },
109263
109264     /**
109265      * @private
109266      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
109267      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
109268      * @param {Ext.EventObject} e The click event
109269      */
109270     onMouseDown : function(e) {
109271         var me = this,
109272             thumbClicked = false,
109273             i = 0,
109274             thumbs = me.thumbs,
109275             len = thumbs.length,
109276             local;
109277
109278         if (me.disabled) {
109279             return;
109280         }
109281
109282         //see if the click was on any of the thumbs
109283         for (; i < len; i++) {
109284             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
109285         }
109286
109287         if (me.clickToChange && !thumbClicked) {
109288             local = me.innerEl.translatePoints(e.getXY());
109289             me.onClickChange(local);
109290         }
109291         me.focus();
109292     },
109293
109294     /**
109295      * @private
109296      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
109297      * Only changes the value if the click was within this.clickRange.
109298      * @param {Object} local Object containing top and left values for the click event.
109299      */
109300     onClickChange : function(local) {
109301         var me = this,
109302             thumb, index;
109303
109304         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
109305             //find the nearest thumb to the click event
109306             thumb = me.getNearest(local, 'left');
109307             if (!thumb.disabled) {
109308                 index = thumb.index;
109309                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
109310             }
109311         }
109312     },
109313
109314     /**
109315      * @private
109316      * Returns the nearest thumb to a click event, along with its distance
109317      * @param {Object} local Object containing top and left values from a click event
109318      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
109319      * @return {Object} The closest thumb object and its distance from the click event
109320      */
109321     getNearest: function(local, prop) {
109322         var me = this,
109323             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
109324             clickValue = me.reverseValue(localValue),
109325             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
109326             index = 0,
109327             nearest = null,
109328             thumbs = me.thumbs,
109329             i = 0,
109330             len = thumbs.length,
109331             thumb,
109332             value,
109333             dist;
109334
109335         for (; i < len; i++) {
109336             thumb = me.thumbs[i];
109337             value = thumb.value;
109338             dist  = Math.abs(value - clickValue);
109339
109340             if (Math.abs(dist <= nearestDistance)) {
109341                 nearest = thumb;
109342                 index = i;
109343                 nearestDistance = dist;
109344             }
109345         }
109346         return nearest;
109347     },
109348
109349     /**
109350      * @private
109351      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
109352      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
109353      * @param {Ext.EventObject} e The Event object
109354      */
109355     onKeyDown : function(e) {
109356         /*
109357          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
109358          * There's no real sane default for it, so leave it like this until we come up
109359          * with a better way of doing it.
109360          */
109361         var me = this,
109362             k,
109363             val;
109364
109365         if(me.disabled || me.thumbs.length !== 1) {
109366             e.preventDefault();
109367             return;
109368         }
109369         k = e.getKey();
109370
109371         switch(k) {
109372             case e.UP:
109373             case e.RIGHT:
109374                 e.stopEvent();
109375                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
109376                 me.setValue(0, val, undefined, true);
109377             break;
109378             case e.DOWN:
109379             case e.LEFT:
109380                 e.stopEvent();
109381                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
109382                 me.setValue(0, val, undefined, true);
109383             break;
109384             default:
109385                 e.preventDefault();
109386         }
109387     },
109388
109389     // private
109390     afterRender : function() {
109391         var me = this,
109392             i = 0,
109393             thumbs = me.thumbs,
109394             len = thumbs.length,
109395             thumb,
109396             v;
109397
109398         me.callParent(arguments);
109399
109400         for (; i < len; i++) {
109401             thumb = thumbs[i];
109402
109403             if (thumb.value !== undefined) {
109404                 v = me.normalizeValue(thumb.value);
109405                 if (v !== thumb.value) {
109406                     // delete this.value;
109407                     me.setValue(i, v, false);
109408                 } else {
109409                     thumb.move(me.translateValue(v), false);
109410                 }
109411             }
109412         }
109413     },
109414
109415     /**
109416      * @private
109417      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
109418      * the ratio is 2
109419      * @return {Number} The ratio of pixels to mapped values
109420      */
109421     getRatio : function() {
109422         var w = this.innerEl.getWidth(),
109423             v = this.maxValue - this.minValue;
109424         return v === 0 ? w : (w/v);
109425     },
109426
109427     /**
109428      * @private
109429      * Returns a snapped, constrained value when given a desired value
109430      * @param {Number} value Raw number value
109431      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
109432      */
109433     normalizeValue : function(v) {
109434         var me = this;
109435
109436         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
109437         v = Ext.util.Format.round(v, me.decimalPrecision);
109438         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
109439         return v;
109440     },
109441
109442     /**
109443      * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
109444      * value will be changed.
109445      * @param {Number} val The new minimum value
109446      */
109447     setMinValue : function(val) {
109448         var me = this,
109449             i = 0,
109450             thumbs = me.thumbs,
109451             len = thumbs.length,
109452             t;
109453
109454         me.minValue = val;
109455         if (me.rendered) {
109456             me.inputEl.dom.setAttribute('aria-valuemin', val);
109457         }
109458
109459         for (; i < len; ++i) {
109460             t = thumbs[i];
109461             t.value = t.value < val ? val : t.value;
109462         }
109463         me.syncThumbs();
109464     },
109465
109466     /**
109467      * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
109468      * value will be changed.
109469      * @param {Number} val The new maximum value
109470      */
109471     setMaxValue : function(val) {
109472         var me = this,
109473             i = 0,
109474             thumbs = me.thumbs,
109475             len = thumbs.length,
109476             t;
109477
109478         me.maxValue = val;
109479         if (me.rendered) {
109480             me.inputEl.dom.setAttribute('aria-valuemax', val);
109481         }
109482
109483         for (; i < len; ++i) {
109484             t = thumbs[i];
109485             t.value = t.value > val ? val : t.value;
109486         }
109487         me.syncThumbs();
109488     },
109489
109490     /**
109491      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
109492      * maxValue.
109493      * @param {Number} index Index of the thumb to move
109494      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
109495      * @param {Boolean} [animate=true] Turn on or off animation
109496      */
109497     setValue : function(index, value, animate, changeComplete) {
109498         var me = this,
109499             thumb = me.thumbs[index];
109500
109501         // ensures value is contstrained and snapped
109502         value = me.normalizeValue(value);
109503
109504         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
109505             thumb.value = value;
109506             if (me.rendered) {
109507                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
109508                 // Perhaps this should go on each thumb element rather than the outer element.
109509                 me.inputEl.set({
109510                     'aria-valuenow': value,
109511                     'aria-valuetext': value
109512                 });
109513
109514                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
109515
109516                 me.fireEvent('change', me, value, thumb);
109517                 if (changeComplete) {
109518                     me.fireEvent('changecomplete', me, value, thumb);
109519                 }
109520             }
109521         }
109522     },
109523
109524     /**
109525      * @private
109526      */
109527     translateValue : function(v) {
109528         var ratio = this.getRatio();
109529         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
109530     },
109531
109532     /**
109533      * @private
109534      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
109535      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
109536      * returns 200
109537      * @param {Number} pos The position along the slider to return a mapped value for
109538      * @return {Number} The mapped value for the given position
109539      */
109540     reverseValue : function(pos) {
109541         var ratio = this.getRatio();
109542         return (pos + (this.minValue * ratio)) / ratio;
109543     },
109544
109545     // private
109546     focus : function() {
109547         this.focusEl.focus(10);
109548     },
109549
109550     //private
109551     onDisable: function() {
109552         var me = this,
109553             i = 0,
109554             thumbs = me.thumbs,
109555             len = thumbs.length,
109556             thumb,
109557             el,
109558             xy;
109559
109560         me.callParent();
109561
109562         for (; i < len; i++) {
109563             thumb = thumbs[i];
109564             el = thumb.el;
109565
109566             thumb.disable();
109567
109568             if(Ext.isIE) {
109569                 //IE breaks when using overflow visible and opacity other than 1.
109570                 //Create a place holder for the thumb and display it.
109571                 xy = el.getXY();
109572                 el.hide();
109573
109574                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
109575
109576                 if (!me.thumbHolder) {
109577                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
109578                 }
109579
109580                 me.thumbHolder.show().setXY(xy);
109581             }
109582         }
109583     },
109584
109585     //private
109586     onEnable: function() {
109587         var me = this,
109588             i = 0,
109589             thumbs = me.thumbs,
109590             len = thumbs.length,
109591             thumb,
109592             el;
109593
109594         this.callParent();
109595
109596         for (; i < len; i++) {
109597             thumb = thumbs[i];
109598             el = thumb.el;
109599
109600             thumb.enable();
109601
109602             if (Ext.isIE) {
109603                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
109604
109605                 if (me.thumbHolder) {
109606                     me.thumbHolder.hide();
109607                 }
109608
109609                 el.show();
109610                 me.syncThumbs();
109611             }
109612         }
109613     },
109614
109615     /**
109616      * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
109617      * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
109618      * auto width, this method can be called from another resize handler to sync the Slider if necessary.
109619      */
109620     syncThumbs : function() {
109621         if (this.rendered) {
109622             var thumbs = this.thumbs,
109623                 length = thumbs.length,
109624                 i = 0;
109625
109626             for (; i < length; i++) {
109627                 thumbs[i].move(this.translateValue(thumbs[i].value));
109628             }
109629         }
109630     },
109631
109632     /**
109633      * Returns the current value of the slider
109634      * @param {Number} index The index of the thumb to return a value for
109635      * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
109636      * no index is given.
109637      */
109638     getValue : function(index) {
109639         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
109640     },
109641
109642     /**
109643      * Returns an array of values - one for the location of each thumb
109644      * @return {Number[]} The set of thumb values
109645      */
109646     getValues: function() {
109647         var values = [],
109648             i = 0,
109649             thumbs = this.thumbs,
109650             len = thumbs.length;
109651
109652         for (; i < len; i++) {
109653             values.push(thumbs[i].value);
109654         }
109655
109656         return values;
109657     },
109658
109659     getSubmitValue: function() {
109660         var me = this;
109661         return (me.disabled || !me.submitValue) ? null : me.getValue();
109662     },
109663
109664     reset: function() {
109665         var me = this,
109666             Array = Ext.Array;
109667         Array.forEach(Array.from(me.originalValue), function(val, i) {
109668             me.setValue(i, val);
109669         });
109670         me.clearInvalid();
109671         // delete here so we reset back to the original state
109672         delete me.wasValid;
109673     },
109674
109675     // private
109676     beforeDestroy : function() {
109677         var me = this;
109678
109679         Ext.destroy(me.innerEl, me.endEl, me.focusEl);
109680         Ext.each(me.thumbs, function(thumb) {
109681             Ext.destroy(thumb);
109682         }, me);
109683
109684         me.callParent();
109685     },
109686
109687     statics: {
109688         // Method overrides to support slider with vertical orientation
109689         Vertical: {
109690             getRatio : function() {
109691                 var h = this.innerEl.getHeight(),
109692                     v = this.maxValue - this.minValue;
109693                 return h/v;
109694             },
109695
109696             onClickChange : function(local) {
109697                 var me = this,
109698                     thumb, index, bottom;
109699
109700                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
109701                     thumb = me.getNearest(local, 'top');
109702                     if (!thumb.disabled) {
109703                         index = thumb.index;
109704                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
109705
109706                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
109707                     }
109708                 }
109709             }
109710         }
109711     }
109712 });
109713
109714 /**
109715  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
109716  * and animation. Can be added as an item to any container.
109717  *
109718  *     @example
109719  *     Ext.create('Ext.slider.Single', {
109720  *         width: 200,
109721  *         value: 50,
109722  *         increment: 10,
109723  *         minValue: 0,
109724  *         maxValue: 100,
109725  *         renderTo: Ext.getBody()
109726  *     });
109727  *
109728  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
109729  */
109730 Ext.define('Ext.slider.Single', {
109731     extend: 'Ext.slider.Multi',
109732     alias: ['widget.slider', 'widget.sliderfield'],
109733     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
109734
109735     /**
109736      * Returns the current value of the slider
109737      * @return {Number} The current value of the slider
109738      */
109739     getValue: function() {
109740         // just returns the value of the first thumb, which should be the only one in a single slider
109741         return this.callParent([0]);
109742     },
109743
109744     /**
109745      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
109746      * maxValue.
109747      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
109748      * @param {Boolean} [animate] Turn on or off animation
109749      */
109750     setValue: function(value, animate) {
109751         var args = Ext.toArray(arguments),
109752             len  = args.length;
109753
109754         // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
109755         // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
109756         // signature without the required index. The index will always be 0 for a single slider
109757         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
109758             args.unshift(0);
109759         }
109760
109761         return this.callParent(args);
109762     },
109763
109764     // private
109765     getNearest : function(){
109766         // Since there's only 1 thumb, it's always the nearest
109767         return this.thumbs[0];
109768     }
109769 });
109770
109771 /**
109772  * @author Ed Spencer
109773  * @class Ext.tab.Tab
109774  * @extends Ext.button.Button
109775  *
109776  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
109777  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
109778  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
109779  */
109780 Ext.define('Ext.tab.Tab', {
109781     extend: 'Ext.button.Button',
109782     alias: 'widget.tab',
109783
109784     requires: [
109785         'Ext.layout.component.Tab',
109786         'Ext.util.KeyNav'
109787     ],
109788
109789     componentLayout: 'tab',
109790
109791     isTab: true,
109792
109793     baseCls: Ext.baseCSSPrefix + 'tab',
109794
109795     /**
109796      * @cfg {String} activeCls
109797      * The CSS class to be applied to a Tab when it is active.
109798      * Providing your own CSS for this class enables you to customize the active state.
109799      */
109800     activeCls: 'active',
109801
109802     /**
109803      * @cfg {String} disabledCls
109804      * The CSS class to be applied to a Tab when it is disabled.
109805      */
109806
109807     /**
109808      * @cfg {String} closableCls
109809      * The CSS class which is added to the tab when it is closable
109810      */
109811     closableCls: 'closable',
109812
109813     /**
109814      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
109815      */
109816     closable: true,
109817
109818     /**
109819      * @cfg {String} closeText
109820      * The accessible text label for the close button link; only used when {@link #closable} = true.
109821      */
109822     closeText: 'Close Tab',
109823
109824     /**
109825      * @property {Boolean} active
109826      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
109827      */
109828     active: false,
109829
109830     /**
109831      * @property closable
109832      * @type Boolean
109833      * True if the tab is currently closable
109834      */
109835
109836     scale: false,
109837
109838     position: 'top',
109839
109840     initComponent: function() {
109841         var me = this;
109842
109843         me.addEvents(
109844             /**
109845              * @event activate
109846              * Fired when the tab is activated.
109847              * @param {Ext.tab.Tab} this
109848              */
109849             'activate',
109850
109851             /**
109852              * @event deactivate
109853              * Fired when the tab is deactivated.
109854              * @param {Ext.tab.Tab} this
109855              */
109856             'deactivate',
109857
109858             /**
109859              * @event beforeclose
109860              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
109861              * false from any listener to stop the close event being fired
109862              * @param {Ext.tab.Tab} tab The Tab object
109863              */
109864             'beforeclose',
109865
109866             /**
109867              * @event close
109868              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
109869              * @param {Ext.tab.Tab} tab The Tab object
109870              */
109871             'close'
109872         );
109873
109874         me.callParent(arguments);
109875
109876         if (me.card) {
109877             me.setCard(me.card);
109878         }
109879     },
109880
109881     /**
109882      * @ignore
109883      */
109884     onRender: function() {
109885         var me = this,
109886             tabBar = me.up('tabbar'),
109887             tabPanel = me.up('tabpanel');
109888
109889         me.addClsWithUI(me.position);
109890
109891         // Set all the state classNames, as they need to include the UI
109892         // me.disabledCls = me.getClsWithUIs('disabled');
109893
109894         me.syncClosableUI();
109895
109896         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
109897         if (!me.minWidth) {
109898             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
109899             if (!me.minWidth && tabPanel) {
109900                 me.minWidth = tabPanel.minTabWidth;
109901             }
109902             if (me.minWidth && me.iconCls) {
109903                 me.minWidth += 25;
109904             }
109905         }
109906         if (!me.maxWidth) {
109907             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
109908             if (!me.maxWidth && tabPanel) {
109909                 me.maxWidth = tabPanel.maxTabWidth;
109910             }
109911         }
109912
109913         me.callParent(arguments);
109914
109915         if (me.active) {
109916             me.activate(true);
109917         }
109918
109919         me.syncClosableElements();
109920
109921         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
109922             enter: me.onEnterKey,
109923             del: me.onDeleteKey,
109924             scope: me
109925         });
109926     },
109927
109928     // inherit docs
109929     enable : function(silent) {
109930         var me = this;
109931
109932         me.callParent(arguments);
109933
109934         me.removeClsWithUI(me.position + '-disabled');
109935
109936         return me;
109937     },
109938
109939     // inherit docs
109940     disable : function(silent) {
109941         var me = this;
109942
109943         me.callParent(arguments);
109944
109945         me.addClsWithUI(me.position + '-disabled');
109946
109947         return me;
109948     },
109949
109950     /**
109951      * @ignore
109952      */
109953     onDestroy: function() {
109954         var me = this;
109955
109956         if (me.closeEl) {
109957             me.closeEl.un('click', Ext.EventManager.preventDefault);
109958             me.closeEl = null;
109959         }
109960
109961         Ext.destroy(me.keyNav);
109962         delete me.keyNav;
109963
109964         me.callParent(arguments);
109965     },
109966
109967     /**
109968      * Sets the tab as either closable or not
109969      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
109970      * close button will appear on the tab)
109971      */
109972     setClosable: function(closable) {
109973         var me = this;
109974
109975         // Closable must be true if no args
109976         closable = (!arguments.length || !!closable);
109977
109978         if (me.closable != closable) {
109979             me.closable = closable;
109980
109981             // set property on the user-facing item ('card'):
109982             if (me.card) {
109983                 me.card.closable = closable;
109984             }
109985
109986             me.syncClosableUI();
109987
109988             if (me.rendered) {
109989                 me.syncClosableElements();
109990
109991                 // Tab will change width to accommodate close icon
109992                 me.doComponentLayout();
109993                 if (me.ownerCt) {
109994                     me.ownerCt.doLayout();
109995                 }
109996             }
109997         }
109998     },
109999
110000     /**
110001      * This method ensures that the closeBtn element exists or not based on 'closable'.
110002      * @private
110003      */
110004     syncClosableElements: function () {
110005         var me = this;
110006
110007         if (me.closable) {
110008             if (!me.closeEl) {
110009                 me.closeEl = me.el.createChild({
110010                     tag: 'a',
110011                     cls: me.baseCls + '-close-btn',
110012                     href: '#',
110013                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
110014                     title: me.closeText
110015                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
110016             }
110017         } else {
110018             var closeEl = me.closeEl;
110019             if (closeEl) {
110020                 closeEl.un('click', Ext.EventManager.preventDefault);
110021                 closeEl.remove();
110022                 me.closeEl = null;
110023             }
110024         }
110025     },
110026
110027     /**
110028      * This method ensures that the UI classes are added or removed based on 'closable'.
110029      * @private
110030      */
110031     syncClosableUI: function () {
110032         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
110033
110034         if (me.closable) {
110035             me.addClsWithUI(classes);
110036         } else {
110037             me.removeClsWithUI(classes);
110038         }
110039     },
110040
110041     /**
110042      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
110043      * belongs to and would not need to be done by the developer
110044      * @param {Ext.Component} card The card to set
110045      */
110046     setCard: function(card) {
110047         var me = this;
110048
110049         me.card = card;
110050         me.setText(me.title || card.title);
110051         me.setIconCls(me.iconCls || card.iconCls);
110052     },
110053
110054     /**
110055      * @private
110056      * Listener attached to click events on the Tab's close button
110057      */
110058     onCloseClick: function() {
110059         var me = this;
110060
110061         if (me.fireEvent('beforeclose', me) !== false) {
110062             if (me.tabBar) {
110063                 if (me.tabBar.closeTab(me) === false) {
110064                     // beforeclose on the panel vetoed the event, stop here
110065                     return;
110066                 }
110067             } else {
110068                 // if there's no tabbar, fire the close event
110069                 me.fireEvent('close', me);
110070             }
110071         }
110072     },
110073
110074     /**
110075      * Fires the close event on the tab.
110076      * @private
110077      */
110078     fireClose: function(){
110079         this.fireEvent('close', this);
110080     },
110081
110082     /**
110083      * @private
110084      */
110085     onEnterKey: function(e) {
110086         var me = this;
110087
110088         if (me.tabBar) {
110089             me.tabBar.onClick(e, me.el);
110090         }
110091     },
110092
110093    /**
110094      * @private
110095      */
110096     onDeleteKey: function(e) {
110097         var me = this;
110098
110099         if (me.closable) {
110100             me.onCloseClick();
110101         }
110102     },
110103
110104     // @private
110105     activate : function(supressEvent) {
110106         var me = this;
110107
110108         me.active = true;
110109         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
110110
110111         if (supressEvent !== true) {
110112             me.fireEvent('activate', me);
110113         }
110114     },
110115
110116     // @private
110117     deactivate : function(supressEvent) {
110118         var me = this;
110119
110120         me.active = false;
110121         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
110122
110123         if (supressEvent !== true) {
110124             me.fireEvent('deactivate', me);
110125         }
110126     }
110127 });
110128
110129 /**
110130  * @author Ed Spencer
110131  * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
110132  * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
110133  */
110134 Ext.define('Ext.tab.Bar', {
110135     extend: 'Ext.panel.Header',
110136     alias: 'widget.tabbar',
110137     baseCls: Ext.baseCSSPrefix + 'tab-bar',
110138
110139     requires: [
110140         'Ext.tab.Tab',
110141         'Ext.FocusManager'
110142     ],
110143
110144     isTabBar: true,
110145     
110146     /**
110147      * @cfg {String} title @hide
110148      */
110149     
110150     /**
110151      * @cfg {String} iconCls @hide
110152      */
110153
110154     // @private
110155     defaultType: 'tab',
110156
110157     /**
110158      * @cfg {Boolean} plain
110159      * True to not show the full background on the tabbar
110160      */
110161     plain: false,
110162
110163     // @private
110164     renderTpl: [
110165         '<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>',
110166         '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
110167     ],
110168
110169     /**
110170      * @cfg {Number} minTabWidth
110171      * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
110172      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
110173      */
110174
110175     /**
110176      * @cfg {Number} maxTabWidth
110177      * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
110178      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
110179      */
110180
110181     // @private
110182     initComponent: function() {
110183         var me = this,
110184             keys;
110185
110186         if (me.plain) {
110187             me.setUI(me.ui + '-plain');
110188         }
110189
110190         me.addClsWithUI(me.dock);
110191
110192         me.addEvents(
110193             /**
110194              * @event change
110195              * Fired when the currently-active tab has changed
110196              * @param {Ext.tab.Bar} tabBar The TabBar
110197              * @param {Ext.tab.Tab} tab The new Tab
110198              * @param {Ext.Component} card The card that was just shown in the TabPanel
110199              */
110200             'change'
110201         );
110202
110203         me.addChildEls('body', 'strip');
110204         me.callParent(arguments);
110205
110206         // TabBar must override the Header's align setting.
110207         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
110208         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
110209
110210         me.remove(me.titleCmp);
110211         delete me.titleCmp;
110212
110213         // Subscribe to Ext.FocusManager for key navigation
110214         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
110215         Ext.FocusManager.subscribe(me, {
110216             keys: keys
110217         });
110218
110219         Ext.apply(me.renderData, {
110220             bodyCls: me.bodyCls
110221         });
110222     },
110223
110224     // @private
110225     onAdd: function(tab) {
110226         tab.position = this.dock;
110227         this.callParent(arguments);
110228     },
110229     
110230     onRemove: function(tab) {
110231         var me = this;
110232         
110233         if (tab === me.previousTab) {
110234             me.previousTab = null;
110235         }
110236         if (me.items.getCount() === 0) {
110237             me.activeTab = null;
110238         }
110239         me.callParent(arguments);    
110240     },
110241
110242     // @private
110243     afterRender: function() {
110244         var me = this;
110245
110246         me.mon(me.el, {
110247             scope: me,
110248             click: me.onClick,
110249             delegate: '.' + Ext.baseCSSPrefix + 'tab'
110250         });
110251         me.callParent(arguments);
110252
110253     },
110254
110255     afterComponentLayout : function() {
110256         var me = this;
110257
110258         me.callParent(arguments);
110259         me.strip.setWidth(me.el.getWidth());
110260     },
110261
110262     // @private
110263     onClick: function(e, target) {
110264         // The target might not be a valid tab el.
110265         var tab = Ext.getCmp(target.id),
110266             tabPanel = this.tabPanel;
110267
110268         target = e.getTarget();
110269
110270         if (tab && tab.isDisabled && !tab.isDisabled()) {
110271             if (tab.closable && target === tab.closeEl.dom) {
110272                 tab.onCloseClick();
110273             } else {
110274                 if (tabPanel) {
110275                     // TabPanel will card setActiveTab of the TabBar
110276                     tabPanel.setActiveTab(tab.card);
110277                 } else {
110278                     this.setActiveTab(tab);
110279                 }
110280                 tab.focus();
110281             }
110282         }
110283     },
110284
110285     /**
110286      * @private
110287      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
110288      * @param {Ext.tab.Tab} tab The tab to close
110289      */
110290     closeTab: function(tab) {
110291         var me = this,
110292             card = tab.card,
110293             tabPanel = me.tabPanel,
110294             nextTab;
110295
110296         if (card && card.fireEvent('beforeclose', card) === false) {
110297             return false;
110298         }
110299
110300         if (tab.active && me.items.getCount() > 1) {
110301             nextTab = me.previousTab || tab.next('tab') || me.items.first();
110302             me.setActiveTab(nextTab);
110303             if (tabPanel) {
110304                 tabPanel.setActiveTab(nextTab.card);
110305             }
110306         }
110307         /*
110308          * force the close event to fire. By the time this function returns,
110309          * the tab is already destroyed and all listeners have been purged
110310          * so the tab can't fire itself.
110311          */
110312         tab.fireClose();
110313         me.remove(tab);
110314
110315         if (tabPanel && card) {
110316             card.fireEvent('close', card);
110317             tabPanel.remove(card);
110318         }
110319
110320         if (nextTab) {
110321             nextTab.focus();
110322         }
110323     },
110324
110325     /**
110326      * @private
110327      * Marks the given tab as active
110328      * @param {Ext.tab.Tab} tab The tab to mark active
110329      */
110330     setActiveTab: function(tab) {
110331         if (tab.disabled) {
110332             return;
110333         }
110334         var me = this;
110335         if (me.activeTab) {
110336             me.previousTab = me.activeTab;
110337             me.activeTab.deactivate();
110338         }
110339         tab.activate();
110340
110341         if (me.rendered) {
110342             me.layout.layout();
110343             tab.el && tab.el.scrollIntoView(me.layout.getRenderTarget());
110344         }
110345         me.activeTab = tab;
110346         me.fireEvent('change', me, tab, tab.card);
110347     }
110348 });
110349
110350 /**
110351  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
110352  *
110353  * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
110354  * layout purposes, but also have special support for containing child Components
110355  * (`{@link Ext.container.Container#items items}`) that are managed using a
110356  * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
110357  *
110358  * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
110359  * This makes the child tab Component, and all its descendants **unusable**.  To enable re-use of a tab,
110360  * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
110361  *
110362  * ## TabPanel's layout
110363  *
110364  * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
110365  * Panels added to the TabPanel will have their header hidden by default because the Tab will
110366  * automatically take the Panel's configured title and icon.
110367  *
110368  * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
110369  * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
110370  * This means that a TabPanel will not display any configured title, and will not display any configured
110371  * header {@link Ext.panel.Panel#tools tools}.
110372  *
110373  * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
110374  * `{@link Ext.container.Container#layout layout: 'fit'}`.
110375  *
110376  * ## Controlling tabs
110377  *
110378  * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
110379  * by specifying the tabConfig option:
110380  *
110381  *     @example
110382  *     Ext.create('Ext.tab.Panel', {
110383  *         width: 400,
110384  *         height: 400,
110385  *         renderTo: document.body,
110386  *         items: [{
110387  *             title: 'Foo'
110388  *         }, {
110389  *             title: 'Bar',
110390  *             tabConfig: {
110391  *                 title: 'Custom Title',
110392  *                 tooltip: 'A button tooltip'
110393  *             }
110394  *         }]
110395  *     });
110396  *
110397  * # Examples
110398  *
110399  * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
110400  * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
110401  * active by default.
110402  *
110403  *     @example
110404  *     Ext.create('Ext.tab.Panel', {
110405  *         width: 300,
110406  *         height: 200,
110407  *         activeTab: 0,
110408  *         items: [
110409  *             {
110410  *                 title: 'Tab 1',
110411  *                 bodyPadding: 10,
110412  *                 html : 'A simple tab'
110413  *             },
110414  *             {
110415  *                 title: 'Tab 2',
110416  *                 html : 'Another one'
110417  *             }
110418  *         ],
110419  *         renderTo : Ext.getBody()
110420  *     });
110421  *
110422  * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
110423  * tab button hidden initially. Items can be subsequently hidden and show by accessing the
110424  * tab property on the child item.
110425  *
110426  *     @example
110427  *     var tabs = Ext.create('Ext.tab.Panel', {
110428  *         width: 400,
110429  *         height: 400,
110430  *         renderTo: document.body,
110431  *         items: [{
110432  *             title: 'Home',
110433  *             html: 'Home',
110434  *             itemId: 'home'
110435  *         }, {
110436  *             title: 'Users',
110437  *             html: 'Users',
110438  *             itemId: 'users',
110439  *             hidden: true
110440  *         }, {
110441  *             title: 'Tickets',
110442  *             html: 'Tickets',
110443  *             itemId: 'tickets'
110444  *         }]
110445  *     });
110446  *
110447  *     setTimeout(function(){
110448  *         tabs.child('#home').tab.hide();
110449  *         var users = tabs.child('#users');
110450  *         users.tab.show();
110451  *         tabs.setActiveTab(users);
110452  *     }, 1000);
110453  *
110454  * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
110455  *
110456  *     @example
110457  *     Ext.create('Ext.tab.Panel', {
110458  *         width: 300,
110459  *         height: 200,
110460  *         activeTab: 0,
110461  *         plain: true,
110462  *         items: [
110463  *             {
110464  *                 title: 'Tab 1',
110465  *                 bodyPadding: 10,
110466  *                 html : 'A simple tab'
110467  *             },
110468  *             {
110469  *                 title: 'Tab 2',
110470  *                 html : 'Another one'
110471  *             }
110472  *         ],
110473  *         renderTo : Ext.getBody()
110474  *     });
110475  *
110476  * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
110477  * position where the tabs are displayed. The available options for this are `'top'` (default) and
110478  * `'bottom'`.
110479  *
110480  *     @example
110481  *     Ext.create('Ext.tab.Panel', {
110482  *         width: 300,
110483  *         height: 200,
110484  *         activeTab: 0,
110485  *         bodyPadding: 10,
110486  *         tabPosition: 'bottom',
110487  *         items: [
110488  *             {
110489  *                 title: 'Tab 1',
110490  *                 html : 'A simple tab'
110491  *             },
110492  *             {
110493  *                 title: 'Tab 2',
110494  *                 html : 'Another one'
110495  *             }
110496  *         ],
110497  *         renderTo : Ext.getBody()
110498  *     });
110499  *
110500  * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
110501  * current active tab. You can either give it an index or an instance of a tab. For example:
110502  *
110503  *     @example
110504  *     var tabs = Ext.create('Ext.tab.Panel', {
110505  *         items: [
110506  *             {
110507  *                 id   : 'my-tab',
110508  *                 title: 'Tab 1',
110509  *                 html : 'A simple tab'
110510  *             },
110511  *             {
110512  *                 title: 'Tab 2',
110513  *                 html : 'Another one'
110514  *             }
110515  *         ],
110516  *         renderTo : Ext.getBody()
110517  *     });
110518  *
110519  *     var tab = Ext.getCmp('my-tab');
110520  *
110521  *     Ext.create('Ext.button.Button', {
110522  *         renderTo: Ext.getBody(),
110523  *         text    : 'Select the first tab',
110524  *         scope   : this,
110525  *         handler : function() {
110526  *             tabs.setActiveTab(tab);
110527  *         }
110528  *     });
110529  *
110530  *     Ext.create('Ext.button.Button', {
110531  *         text    : 'Select the second tab',
110532  *         scope   : this,
110533  *         handler : function() {
110534  *             tabs.setActiveTab(1);
110535  *         },
110536  *         renderTo : Ext.getBody()
110537  *     });
110538  *
110539  * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
110540  *
110541  *     @example
110542  *     var tabs = Ext.create('Ext.tab.Panel', {
110543  *         items: [
110544  *             {
110545  *                 title: 'Tab 1',
110546  *                 html : 'A simple tab'
110547  *             },
110548  *             {
110549  *                 title: 'Tab 2',
110550  *                 html : 'Another one'
110551  *             }
110552  *         ],
110553  *         renderTo : Ext.getBody()
110554  *     });
110555  *
110556  *     Ext.create('Ext.button.Button', {
110557  *         text    : 'Get active tab',
110558  *         scope   : this,
110559  *         handler : function() {
110560  *             var tab = tabs.getActiveTab();
110561  *             alert('Current tab: ' + tab.title);
110562  *         },
110563  *         renderTo : Ext.getBody()
110564  *     });
110565  *
110566  * Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config
110567  * object for a panel.
110568  *
110569  *     @example
110570  *     var tabs = Ext.create('Ext.tab.Panel', {
110571  *         items: [
110572  *             {
110573  *                 title: 'Tab 1',
110574  *                 html : 'A simple tab'
110575  *             },
110576  *             {
110577  *                 title: 'Tab 2',
110578  *                 html : 'Another one'
110579  *             }
110580  *         ],
110581  *         renderTo : Ext.getBody()
110582  *     });
110583  *
110584  *     Ext.create('Ext.button.Button', {
110585  *         text    : 'New tab',
110586  *         scope   : this,
110587  *         handler : function() {
110588  *             var tab = tabs.add({
110589  *                 // we use the tabs.items property to get the length of current items/tabs
110590  *                 title: 'Tab ' + (tabs.items.length + 1),
110591  *                 html : 'Another one'
110592  *             });
110593  *
110594  *             tabs.setActiveTab(tab);
110595  *         },
110596  *         renderTo : Ext.getBody()
110597  *     });
110598  *
110599  * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method
110600  * with an config object for a panel.
110601  *
110602  *     @example
110603  *     var tabs = Ext.create('Ext.tab.Panel', {
110604  *         items: [
110605  *             {
110606  *                 title: 'Tab 1',
110607  *                 html : 'A simple tab'
110608  *             },
110609  *             {
110610  *                 id   : 'remove-this-tab',
110611  *                 title: 'Tab 2',
110612  *                 html : 'Another one'
110613  *             }
110614  *         ],
110615  *         renderTo : Ext.getBody()
110616  *     });
110617  *
110618  *     Ext.create('Ext.button.Button', {
110619  *         text    : 'Remove tab',
110620  *         scope   : this,
110621  *         handler : function() {
110622  *             var tab = Ext.getCmp('remove-this-tab');
110623  *             tabs.remove(tab);
110624  *         },
110625  *         renderTo : Ext.getBody()
110626  *     });
110627  */
110628 Ext.define('Ext.tab.Panel', {
110629     extend: 'Ext.panel.Panel',
110630     alias: 'widget.tabpanel',
110631     alternateClassName: ['Ext.TabPanel'],
110632
110633     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
110634
110635     /**
110636      * @cfg {String} tabPosition
110637      * The position where the tab strip should be rendered. Can be `top` or `bottom`.
110638      */
110639     tabPosition : 'top',
110640
110641     /**
110642      * @cfg {String/Number} activeItem
110643      * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
110644      */
110645
110646     /**
110647      * @cfg {String/Number/Ext.Component} activeTab
110648      * The tab to activate initially. Either an ID, index or the tab component itself.
110649      */
110650
110651     /**
110652      * @cfg {Object} tabBar
110653      * Optional configuration object for the internal {@link Ext.tab.Bar}.
110654      * If present, this is passed straight through to the TabBar's constructor
110655      */
110656
110657     /**
110658      * @cfg {Object} layout
110659      * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
110660      * If present, this is passed straight through to the layout's constructor
110661      */
110662
110663     /**
110664      * @cfg {Boolean} removePanelHeader
110665      * True to instruct each Panel added to the TabContainer to not render its header element.
110666      * This is to ensure that the title of the panel does not appear twice.
110667      */
110668     removePanelHeader: true,
110669
110670     /**
110671      * @cfg {Boolean} plain
110672      * True to not show the full background on the TabBar.
110673      */
110674     plain: false,
110675
110676     /**
110677      * @cfg {String} itemCls
110678      * The class added to each child item of this TabPanel.
110679      */
110680     itemCls: 'x-tabpanel-child',
110681
110682     /**
110683      * @cfg {Number} minTabWidth
110684      * The minimum width for a tab in the {@link #tabBar}.
110685      */
110686     minTabWidth: undefined,
110687
110688     /**
110689      * @cfg {Number} maxTabWidth The maximum width for each tab.
110690      */
110691     maxTabWidth: undefined,
110692
110693     /**
110694      * @cfg {Boolean} deferredRender
110695      *
110696      * True by default to defer the rendering of child {@link Ext.container.Container#items items} to the browsers DOM
110697      * until a tab is activated. False will render all contained {@link Ext.container.Container#items items} as soon as
110698      * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
110699      * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
110700      * performance.
110701      *
110702      * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
110703      * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
110704      *
110705      * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
110706      */
110707     deferredRender : true,
110708
110709     //inherit docs
110710     initComponent: function() {
110711         var me = this,
110712             dockedItems = me.dockedItems || [],
110713             activeTab = me.activeTab || 0;
110714
110715         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
110716             owner: me,
110717             deferredRender: me.deferredRender,
110718             itemCls: me.itemCls
110719         }, me.layout));
110720
110721         /**
110722          * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
110723          */
110724         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
110725             dock: me.tabPosition,
110726             plain: me.plain,
110727             border: me.border,
110728             cardLayout: me.layout,
110729             tabPanel: me
110730         }));
110731
110732         if (dockedItems && !Ext.isArray(dockedItems)) {
110733             dockedItems = [dockedItems];
110734         }
110735
110736         dockedItems.push(me.tabBar);
110737         me.dockedItems = dockedItems;
110738
110739         me.addEvents(
110740             /**
110741              * @event
110742              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
110743              * the tabchange
110744              * @param {Ext.tab.Panel} tabPanel The TabPanel
110745              * @param {Ext.Component} newCard The card that is about to be activated
110746              * @param {Ext.Component} oldCard The card that is currently active
110747              */
110748             'beforetabchange',
110749
110750             /**
110751              * @event
110752              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
110753              * @param {Ext.tab.Panel} tabPanel The TabPanel
110754              * @param {Ext.Component} newCard The newly activated item
110755              * @param {Ext.Component} oldCard The previously active item
110756              */
110757             'tabchange'
110758         );
110759         me.callParent(arguments);
110760
110761         //set the active tab
110762         me.setActiveTab(activeTab);
110763         //set the active tab after initial layout
110764         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
110765     },
110766
110767     /**
110768      * @private
110769      * We have to wait until after the initial layout to visually activate the activeTab (if set).
110770      * The active tab has different margins than normal tabs, so if the initial layout happens with
110771      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
110772      * until after the initial layout avoids this issue.
110773      */
110774     afterInitialLayout: function() {
110775         var me = this,
110776             card = me.getComponent(me.activeTab);
110777
110778         if (card) {
110779             me.layout.setActiveItem(card);
110780         }
110781     },
110782
110783     /**
110784      * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
110785      * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
110786      */
110787     setActiveTab: function(card) {
110788         var me = this,
110789             previous;
110790
110791         card = me.getComponent(card);
110792         if (card) {
110793             previous = me.getActiveTab();
110794
110795             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
110796                 return false;
110797             }
110798
110799             me.tabBar.setActiveTab(card.tab);
110800             me.activeTab = card;
110801             if (me.rendered) {
110802                 me.layout.setActiveItem(card);
110803             }
110804
110805             if (previous && previous !== card) {
110806                 me.fireEvent('tabchange', me, card, previous);
110807             }
110808         }
110809     },
110810
110811     /**
110812      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
110813      * child component this will return whatever was configured in the {@link #activeTab} config option
110814      * @return {String/Number/Ext.Component} The currently active item
110815      */
110816     getActiveTab: function() {
110817         return this.activeTab;
110818     },
110819
110820     /**
110821      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
110822      * @return {Ext.tab.Bar} The TabBar
110823      */
110824     getTabBar: function() {
110825         return this.tabBar;
110826     },
110827
110828     /**
110829      * @ignore
110830      * Makes sure we have a Tab for each item added to the TabPanel
110831      */
110832     onAdd: function(item, index) {
110833         var me = this,
110834             cfg = item.tabConfig || {},
110835             defaultConfig = {
110836                 xtype: 'tab',
110837                 card: item,
110838                 disabled: item.disabled,
110839                 closable: item.closable,
110840                 hidden: item.hidden,
110841                 tabBar: me.tabBar
110842             };
110843
110844         if (item.closeText) {
110845             defaultConfig.closeText = item.closeText;
110846         }
110847         cfg = Ext.applyIf(cfg, defaultConfig);
110848         item.tab = me.tabBar.insert(index, cfg);
110849
110850         item.on({
110851             scope : me,
110852             enable: me.onItemEnable,
110853             disable: me.onItemDisable,
110854             beforeshow: me.onItemBeforeShow,
110855             iconchange: me.onItemIconChange,
110856             titlechange: me.onItemTitleChange
110857         });
110858
110859         if (item.isPanel) {
110860             if (me.removePanelHeader) {
110861                 item.preventHeader = true;
110862                 if (item.rendered) {
110863                     item.updateHeader();
110864                 }
110865             }
110866             if (item.isPanel && me.border) {
110867                 item.setBorder(false);
110868             }
110869         }
110870
110871         // ensure that there is at least one active tab
110872         if (this.rendered && me.items.getCount() === 1) {
110873             me.setActiveTab(0);
110874         }
110875     },
110876
110877     /**
110878      * @private
110879      * Enable corresponding tab when item is enabled.
110880      */
110881     onItemEnable: function(item){
110882         item.tab.enable();
110883     },
110884
110885     /**
110886      * @private
110887      * Disable corresponding tab when item is enabled.
110888      */
110889     onItemDisable: function(item){
110890         item.tab.disable();
110891     },
110892
110893     /**
110894      * @private
110895      * Sets activeTab before item is shown.
110896      */
110897     onItemBeforeShow: function(item) {
110898         if (item !== this.activeTab) {
110899             this.setActiveTab(item);
110900             return false;
110901         }
110902     },
110903
110904     /**
110905      * @private
110906      * Update the tab iconCls when panel iconCls has been set or changed.
110907      */
110908     onItemIconChange: function(item, newIconCls) {
110909         item.tab.setIconCls(newIconCls);
110910         this.getTabBar().doLayout();
110911     },
110912
110913     /**
110914      * @private
110915      * Update the tab title when panel title has been set or changed.
110916      */
110917     onItemTitleChange: function(item, newTitle) {
110918         item.tab.setText(newTitle);
110919         this.getTabBar().doLayout();
110920     },
110921
110922
110923     /**
110924      * @ignore
110925      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
110926      * so we can do preprocessing before then to find the card's index
110927      */
110928     doRemove: function(item, autoDestroy) {
110929         var me = this,
110930             items = me.items,
110931             // At this point the item hasn't been removed from the items collection.
110932             // As such, if we want to check if there are no more tabs left, we have to
110933             // check for one, as opposed to 0.
110934             hasItemsLeft = items.getCount() > 1;
110935
110936         if (me.destroying || !hasItemsLeft) {
110937             me.activeTab = null;
110938         } else if (item === me.activeTab) {
110939              me.setActiveTab(item.next() || items.getAt(0));
110940         }
110941         me.callParent(arguments);
110942
110943         // Remove the two references
110944         delete item.tab.card;
110945         delete item.tab;
110946     },
110947
110948     /**
110949      * @ignore
110950      * Makes sure we remove the corresponding Tab when an item is removed
110951      */
110952     onRemove: function(item, autoDestroy) {
110953         var me = this;
110954
110955         item.un({
110956             scope : me,
110957             enable: me.onItemEnable,
110958             disable: me.onItemDisable,
110959             beforeshow: me.onItemBeforeShow
110960         });
110961         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
110962             me.tabBar.remove(item.tab);
110963         }
110964     }
110965 });
110966
110967 /**
110968  * A simple element that adds extra horizontal space between items in a toolbar.
110969  * By default a 2px wide space is added via CSS specification:
110970  *
110971  *     .x-toolbar .x-toolbar-spacer {
110972  *         width: 2px;
110973  *     }
110974  *
110975  * Example:
110976  *
110977  *     @example
110978  *     Ext.create('Ext.panel.Panel', {
110979  *         title: 'Toolbar Spacer Example',
110980  *         width: 300,
110981  *         height: 200,
110982  *         tbar : [
110983  *             'Item 1',
110984  *             { xtype: 'tbspacer' }, // or ' '
110985  *             'Item 2',
110986  *             // space width is also configurable via javascript
110987  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
110988  *             'Item 3'
110989  *         ],
110990  *         renderTo: Ext.getBody()
110991  *     });
110992  */
110993 Ext.define('Ext.toolbar.Spacer', {
110994     extend: 'Ext.Component',
110995     alias: 'widget.tbspacer',
110996     alternateClassName: 'Ext.Toolbar.Spacer',
110997     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
110998     focusable: false
110999 });
111000 /**
111001  * @class Ext.tree.Column
111002  * @extends Ext.grid.column.Column
111003  * 
111004  * Provides indentation and folder structure markup for a Tree taking into account
111005  * depth and position within the tree hierarchy.
111006  * 
111007  * @private
111008  */
111009 Ext.define('Ext.tree.Column', {
111010     extend: 'Ext.grid.column.Column',
111011     alias: 'widget.treecolumn',
111012
111013     initComponent: function() {
111014         var origRenderer = this.renderer || this.defaultRenderer,
111015             origScope    = this.scope || window;
111016
111017         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
111018             var buf   = [],
111019                 format = Ext.String.format,
111020                 depth = record.getDepth(),
111021                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
111022                 elbowPrefix = treePrefix + 'elbow-',
111023                 expanderCls = treePrefix + 'expander',
111024                 imgText     = '<img src="{1}" class="{0}" />',
111025                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
111026                 formattedValue = origRenderer.apply(origScope, arguments),
111027                 href = record.get('href'),
111028                 target = record.get('hrefTarget'),
111029                 cls = record.get('cls');
111030
111031             while (record) {
111032                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
111033                     if (record.getDepth() === depth) {
111034                         buf.unshift(format(imgText,
111035                             treePrefix + 'icon ' + 
111036                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
111037                             (record.get('iconCls') || ''),
111038                             record.get('icon') || Ext.BLANK_IMAGE_URL
111039                         ));
111040                         if (record.get('checked') !== null) {
111041                             buf.unshift(format(
111042                                 checkboxText,
111043                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
111044                                 record.get('checked') ? 'aria-checked="true"' : ''
111045                             ));
111046                             if (record.get('checked')) {
111047                                 metaData.tdCls += (' ' + treePrefix + 'checked');
111048                             }
111049                         }
111050                         if (record.isLast()) {
111051                             if (record.isExpandable()) {
111052                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
111053                             } else {
111054                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
111055                             }
111056                             
111057                         } else {
111058                             if (record.isExpandable()) {
111059                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
111060                             } else {
111061                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
111062                             }
111063                         }
111064                     } else {
111065                         if (record.isLast() || record.getDepth() === 0) {
111066                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
111067                         } else if (record.getDepth() !== 0) {
111068                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
111069                         }                      
111070                     }
111071                 }
111072                 record = record.parentNode;
111073             }
111074             if (href) {
111075                 buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
111076             } else {
111077                 buf.push(formattedValue);
111078             }
111079             if (cls) {
111080                 metaData.tdCls += ' ' + cls;
111081             }
111082             return buf.join('');
111083         };
111084         this.callParent(arguments);
111085     },
111086
111087     defaultRenderer: function(value) {
111088         return value;
111089     }
111090 });
111091 /**
111092  * Used as a view by {@link Ext.tree.Panel TreePanel}.
111093  */
111094 Ext.define('Ext.tree.View', {
111095     extend: 'Ext.view.Table',
111096     alias: 'widget.treeview',
111097
111098     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
111099     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
111100
111101     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
111102     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
111103     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
111104
111105     // Class to add to the node wrap element used to hold nodes when a parent is being
111106     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
111107     // for an ancestor node with this class.
111108     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
111109
111110     blockRefresh: true,
111111
111112     /** 
111113      * @cfg {Boolean} rootVisible
111114      * False to hide the root node.
111115      */
111116     rootVisible: true,
111117
111118     /** 
111119      * @cfg {Boolean} animate
111120      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
111121      */
111122
111123     expandDuration: 250,
111124     collapseDuration: 250,
111125     
111126     toggleOnDblClick: true,
111127
111128     initComponent: function() {
111129         var me = this;
111130         
111131         if (me.initialConfig.animate === undefined) {
111132             me.animate = Ext.enableFx;
111133         }
111134         
111135         me.store = Ext.create('Ext.data.NodeStore', {
111136             recursive: true,
111137             rootVisible: me.rootVisible,
111138             listeners: {
111139                 beforeexpand: me.onBeforeExpand,
111140                 expand: me.onExpand,
111141                 beforecollapse: me.onBeforeCollapse,
111142                 collapse: me.onCollapse,
111143                 scope: me
111144             }
111145         });
111146         
111147         if (me.node) {
111148             me.setRootNode(me.node);
111149         }
111150         me.animQueue = {};
111151         me.callParent(arguments);
111152     },
111153
111154     processUIEvent: function(e) {
111155         // If the clicked node is part of an animation, ignore the click.
111156         // This is because during a collapse animation, the associated Records
111157         // will already have been removed from the Store, and the event is not processable.
111158         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
111159             return false;
111160         }
111161         return this.callParent(arguments);
111162     },
111163
111164     onClear: function(){
111165         this.store.removeAll();    
111166     },
111167
111168     setRootNode: function(node) {
111169         var me = this;        
111170         me.store.setNode(node);
111171         me.node = node;
111172         if (!me.rootVisible) {
111173             node.expand();
111174         }
111175     },
111176     
111177     onRender: function() {
111178         var me = this,
111179             el;
111180
111181         me.callParent(arguments);
111182
111183         el = me.el;
111184         el.on({
111185             scope: me,
111186             delegate: me.expanderSelector,
111187             mouseover: me.onExpanderMouseOver,
111188             mouseout: me.onExpanderMouseOut
111189         });
111190         el.on({
111191             scope: me,
111192             delegate: me.checkboxSelector,
111193             click: me.onCheckboxChange
111194         });
111195     },
111196
111197     onCheckboxChange: function(e, t) {
111198         var me = this,
111199             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
111200             
111201         if (item) {
111202             me.onCheckChange(me.getRecord(item));
111203         }
111204     },
111205     
111206     onCheckChange: function(record){
111207         var checked = record.get('checked');
111208         if (Ext.isBoolean(checked)) {
111209             checked = !checked;
111210             record.set('checked', checked);
111211             this.fireEvent('checkchange', record, checked);
111212         }
111213     },
111214
111215     getChecked: function() {
111216         var checked = [];
111217         this.node.cascadeBy(function(rec){
111218             if (rec.get('checked')) {
111219                 checked.push(rec);
111220             }
111221         });
111222         return checked;
111223     },
111224     
111225     isItemChecked: function(rec){
111226         return rec.get('checked');
111227     },
111228
111229     createAnimWrap: function(record, index) {
111230         var thHtml = '',
111231             headerCt = this.panel.headerCt,
111232             headers = headerCt.getGridColumns(),
111233             i = 0, len = headers.length, item,
111234             node = this.getNode(record),
111235             tmpEl, nodeEl;
111236
111237         for (; i < len; i++) {
111238             item = headers[i];
111239             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
111240         }
111241
111242         nodeEl = Ext.get(node);        
111243         tmpEl = nodeEl.insertSibling({
111244             tag: 'tr',
111245             html: [
111246                 '<td colspan="' + headerCt.getColumnCount() + '">',
111247                     '<div class="' + this.nodeAnimWrapCls + '">',
111248                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
111249                             thHtml,
111250                         '</tbody></table>',
111251                     '</div>',
111252                 '</td>'
111253             ].join('')
111254         }, 'after');
111255
111256         return {
111257             record: record,
111258             node: node,
111259             el: tmpEl,
111260             expanding: false,
111261             collapsing: false,
111262             animating: false,
111263             animateEl: tmpEl.down('div'),
111264             targetEl: tmpEl.down('tbody')
111265         };
111266     },
111267
111268     getAnimWrap: function(parent) {
111269         if (!this.animate) {
111270             return null;
111271         }
111272
111273         // We are checking to see which parent is having the animation wrap
111274         while (parent) {
111275             if (parent.animWrap) {
111276                 return parent.animWrap;
111277             }
111278             parent = parent.parentNode;
111279         }
111280         return null;
111281     },
111282
111283     doAdd: function(nodes, records, index) {
111284         // If we are adding records which have a parent that is currently expanding
111285         // lets add them to the animation wrap
111286         var me = this,
111287             record = records[0],
111288             parent = record.parentNode,
111289             a = me.all.elements,
111290             relativeIndex = 0,
111291             animWrap = me.getAnimWrap(parent),
111292             targetEl, children, len;
111293
111294         if (!animWrap || !animWrap.expanding) {
111295             me.resetScrollers();
111296             return me.callParent(arguments);
111297         }
111298
111299         // We need the parent that has the animWrap, not the nodes parent
111300         parent = animWrap.record;
111301         
111302         // If there is an anim wrap we do our special magic logic
111303         targetEl = animWrap.targetEl;
111304         children = targetEl.dom.childNodes;
111305         
111306         // We subtract 1 from the childrens length because we have a tr in there with the th'es
111307         len = children.length - 1;
111308         
111309         // The relative index is the index in the full flat collection minus the index of the wraps parent
111310         relativeIndex = index - me.indexOf(parent) - 1;
111311         
111312         // If we are adding records to the wrap that have a higher relative index then there are currently children
111313         // it means we have to append the nodes to the wrap
111314         if (!len || relativeIndex >= len) {
111315             targetEl.appendChild(nodes);
111316         }
111317         // If there are already more children then the relative index it means we are adding child nodes of
111318         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
111319         else {
111320             // +1 because of the tr with th'es that is already there
111321             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
111322         }
111323
111324         // We also have to update the CompositeElementLite collection of the DataView
111325         Ext.Array.insert(a, index, nodes);
111326         
111327         // If we were in an animation we need to now change the animation
111328         // because the targetEl just got higher.
111329         if (animWrap.isAnimating) {
111330             me.onExpand(parent);
111331         }
111332     },
111333     
111334     beginBulkUpdate: function(){
111335         this.bulkUpdate = true;
111336         this.ownerCt.changingScrollbars = true;  
111337     },
111338     
111339     endBulkUpdate: function(){
111340         var me = this,
111341             ownerCt = me.ownerCt;
111342         
111343         me.bulkUpdate = false;
111344         me.ownerCt.changingScrollbars = true;  
111345         me.resetScrollers();  
111346     },
111347     
111348     onRemove : function(ds, record, index) {
111349         var me = this,
111350             bulk = me.bulkUpdate;
111351
111352         me.doRemove(record, index);
111353         if (!bulk) {
111354             me.updateIndexes(index);
111355         }
111356         if (me.store.getCount() === 0){
111357             me.refresh();
111358         }
111359         if (!bulk) {
111360             me.fireEvent('itemremove', record, index);
111361         }
111362     },
111363     
111364     doRemove: function(record, index) {
111365         // If we are adding records which have a parent that is currently expanding
111366         // lets add them to the animation wrap
111367         var me = this,
111368             parent = record.parentNode,
111369             all = me.all,
111370             animWrap = me.getAnimWrap(record),
111371             node = all.item(index).dom;
111372
111373         if (!animWrap || !animWrap.collapsing) {
111374             me.resetScrollers();
111375             return me.callParent(arguments);
111376         }
111377
111378         animWrap.targetEl.appendChild(node);
111379         all.removeElement(index);
111380     },
111381
111382     onBeforeExpand: function(parent, records, index) {
111383         var me = this,
111384             animWrap;
111385             
111386         if (!me.rendered || !me.animate) {
111387             return;
111388         }
111389
111390         if (me.getNode(parent)) {
111391             animWrap = me.getAnimWrap(parent);
111392             if (!animWrap) {
111393                 animWrap = parent.animWrap = me.createAnimWrap(parent);
111394                 animWrap.animateEl.setHeight(0);
111395             }
111396             else if (animWrap.collapsing) {
111397                 // If we expand this node while it is still expanding then we
111398                 // have to remove the nodes from the animWrap.
111399                 animWrap.targetEl.select(me.itemSelector).remove();
111400             } 
111401             animWrap.expanding = true;
111402             animWrap.collapsing = false;
111403         }
111404     },
111405
111406     onExpand: function(parent) {
111407         var me = this,
111408             queue = me.animQueue,
111409             id = parent.getId(),
111410             animWrap,
111411             animateEl, 
111412             targetEl,
111413             queueItem;        
111414         
111415         if (me.singleExpand) {
111416             me.ensureSingleExpand(parent);
111417         }
111418         
111419         animWrap = me.getAnimWrap(parent);
111420
111421         if (!animWrap) {
111422             me.resetScrollers();
111423             return;
111424         }
111425         
111426         animateEl = animWrap.animateEl;
111427         targetEl = animWrap.targetEl;
111428
111429         animateEl.stopAnimation();
111430         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
111431         queue[id] = true;
111432         animateEl.slideIn('t', {
111433             duration: me.expandDuration,
111434             listeners: {
111435                 scope: me,
111436                 lastframe: function() {
111437                     // Move all the nodes out of the anim wrap to their proper location
111438                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
111439                     animWrap.el.remove();
111440                     me.resetScrollers();
111441                     delete animWrap.record.animWrap;
111442                     delete queue[id];
111443                 }
111444             }
111445         });
111446         
111447         animWrap.isAnimating = true;
111448     },
111449     
111450     resetScrollers: function(){
111451         if (!this.bulkUpdate) {
111452             var panel = this.panel;
111453             
111454             panel.determineScrollbars();
111455             panel.invalidateScroller();
111456         }
111457     },
111458
111459     onBeforeCollapse: function(parent, records, index) {
111460         var me = this,
111461             animWrap;
111462             
111463         if (!me.rendered || !me.animate) {
111464             return;
111465         }
111466
111467         if (me.getNode(parent)) {
111468             animWrap = me.getAnimWrap(parent);
111469             if (!animWrap) {
111470                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
111471             }
111472             else if (animWrap.expanding) {
111473                 // If we collapse this node while it is still expanding then we
111474                 // have to remove the nodes from the animWrap.
111475                 animWrap.targetEl.select(this.itemSelector).remove();
111476             }
111477             animWrap.expanding = false;
111478             animWrap.collapsing = true;
111479         }
111480     },
111481     
111482     onCollapse: function(parent) {
111483         var me = this,
111484             queue = me.animQueue,
111485             id = parent.getId(),
111486             animWrap = me.getAnimWrap(parent),
111487             animateEl, targetEl;
111488
111489         if (!animWrap) {
111490             me.resetScrollers();
111491             return;
111492         }
111493         
111494         animateEl = animWrap.animateEl;
111495         targetEl = animWrap.targetEl;
111496
111497         queue[id] = true;
111498         
111499         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
111500         animateEl.stopAnimation();
111501         animateEl.slideOut('t', {
111502             duration: me.collapseDuration,
111503             listeners: {
111504                 scope: me,
111505                 lastframe: function() {
111506                     animWrap.el.remove();
111507                     delete animWrap.record.animWrap;
111508                     me.resetScrollers();
111509                     delete queue[id];
111510                 }             
111511             }
111512         });
111513         animWrap.isAnimating = true;
111514     },
111515     
111516     /**
111517      * Checks if a node is currently undergoing animation
111518      * @private
111519      * @param {Ext.data.Model} node The node
111520      * @return {Boolean} True if the node is animating
111521      */
111522     isAnimating: function(node) {
111523         return !!this.animQueue[node.getId()];    
111524     },
111525     
111526     collectData: function(records) {
111527         var data = this.callParent(arguments),
111528             rows = data.rows,
111529             len = rows.length,
111530             i = 0,
111531             row, record;
111532             
111533         for (; i < len; i++) {
111534             row = rows[i];
111535             record = records[i];
111536             if (record.get('qtip')) {
111537                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
111538                 if (record.get('qtitle')) {
111539                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
111540                 }
111541             }
111542             if (record.isExpanded()) {
111543                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
111544             }
111545             if (record.isLoading()) {
111546                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
111547             }
111548         }
111549         
111550         return data;
111551     },
111552     
111553     /**
111554      * Expands a record that is loaded in the view.
111555      * @param {Ext.data.Model} record The record to expand
111556      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
111557      * @param {Function} callback (optional) The function to run after the expand is completed
111558      * @param {Object} scope (optional) The scope of the callback function.
111559      */
111560     expand: function(record, deep, callback, scope) {
111561         return record.expand(deep, callback, scope);
111562     },
111563     
111564     /**
111565      * Collapses a record that is loaded in the view.
111566      * @param {Ext.data.Model} record The record to collapse
111567      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
111568      * @param {Function} callback (optional) The function to run after the collapse is completed
111569      * @param {Object} scope (optional) The scope of the callback function.
111570      */
111571     collapse: function(record, deep, callback, scope) {
111572         return record.collapse(deep, callback, scope);
111573     },
111574     
111575     /**
111576      * Toggles a record between expanded and collapsed.
111577      * @param {Ext.data.Model} recordInstance
111578      */
111579     toggle: function(record) {
111580         this[record.isExpanded() ? 'collapse' : 'expand'](record);
111581     },
111582     
111583     onItemDblClick: function(record, item, index) {
111584         this.callParent(arguments);
111585         if (this.toggleOnDblClick) {
111586             this.toggle(record);
111587         }
111588     },
111589     
111590     onBeforeItemMouseDown: function(record, item, index, e) {
111591         if (e.getTarget(this.expanderSelector, item)) {
111592             return false;
111593         }
111594         return this.callParent(arguments);
111595     },
111596     
111597     onItemClick: function(record, item, index, e) {
111598         if (e.getTarget(this.expanderSelector, item)) {
111599             this.toggle(record);
111600             return false;
111601         }
111602         return this.callParent(arguments);
111603     },
111604     
111605     onExpanderMouseOver: function(e, t) {
111606         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
111607     },
111608     
111609     onExpanderMouseOut: function(e, t) {
111610         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
111611     },
111612     
111613     /**
111614      * Gets the base TreeStore from the bound TreePanel.
111615      */
111616     getTreeStore: function() {
111617         return this.panel.store;
111618     },    
111619     
111620     ensureSingleExpand: function(node) {
111621         var parent = node.parentNode;
111622         if (parent) {
111623             parent.eachChild(function(child) {
111624                 if (child !== node && child.isExpanded()) {
111625                     child.collapse();
111626                 }
111627             });
111628         }
111629     }
111630 });
111631 /**
111632  * The TreePanel provides tree-structured UI representation of tree-structured data.
111633  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
111634  * multiple columns through the {@link #columns} configuration.
111635  *
111636  * Simple TreePanel using inline data:
111637  *
111638  *     @example
111639  *     var store = Ext.create('Ext.data.TreeStore', {
111640  *         root: {
111641  *             expanded: true,
111642  *             children: [
111643  *                 { text: "detention", leaf: true },
111644  *                 { text: "homework", expanded: true, children: [
111645  *                     { text: "book report", leaf: true },
111646  *                     { text: "alegrbra", leaf: true}
111647  *                 ] },
111648  *                 { text: "buy lottery tickets", leaf: true }
111649  *             ]
111650  *         }
111651  *     });
111652  *
111653  *     Ext.create('Ext.tree.Panel', {
111654  *         title: 'Simple Tree',
111655  *         width: 200,
111656  *         height: 150,
111657  *         store: store,
111658  *         rootVisible: false,
111659  *         renderTo: Ext.getBody()
111660  *     });
111661  *
111662  * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
111663  * {@link Ext.data.NodeInterface NodeInterface} config options.
111664  */
111665 Ext.define('Ext.tree.Panel', {
111666     extend: 'Ext.panel.Table',
111667     alias: 'widget.treepanel',
111668     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
111669     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
111670     viewType: 'treeview',
111671     selType: 'treemodel',
111672
111673     treeCls: Ext.baseCSSPrefix + 'tree-panel',
111674
111675     deferRowRender: false,
111676
111677     /**
111678      * @cfg {Boolean} lines False to disable tree lines.
111679      */
111680     lines: true,
111681
111682     /**
111683      * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
111684      */
111685     useArrows: false,
111686
111687     /**
111688      * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
111689      */
111690     singleExpand: false,
111691
111692     ddConfig: {
111693         enableDrag: true,
111694         enableDrop: true
111695     },
111696
111697     /**
111698      * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
111699      */
111700
111701     /**
111702      * @cfg {Boolean} rootVisible False to hide the root node.
111703      */
111704     rootVisible: true,
111705
111706     /**
111707      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
111708      */
111709     displayField: 'text',
111710
111711     /**
111712      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
111713      * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
111714      * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
111715      * to that store. For example:
111716      *
111717      *     Ext.create('Ext.tree.Panel', {
111718      *         title: 'Simple Tree',
111719      *         root: {
111720      *             text: "Root node",
111721      *             expanded: true,
111722      *             children: [
111723      *                 { text: "Child 1", leaf: true },
111724      *                 { text: "Child 2", leaf: true }
111725      *             ]
111726      *         },
111727      *         renderTo: Ext.getBody()
111728      *     });
111729      */
111730     root: null,
111731
111732     // Required for the Lockable Mixin. These are the configurations which will be copied to the
111733     // normal and locked sub tablepanels
111734     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
111735     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
111736
111737     /**
111738      * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
111739      */
111740
111741     /**
111742      * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
111743      */
111744
111745     constructor: function(config) {
111746         config = config || {};
111747         if (config.animate === undefined) {
111748             config.animate = Ext.enableFx;
111749         }
111750         this.enableAnimations = config.animate;
111751         delete config.animate;
111752
111753         this.callParent([config]);
111754     },
111755
111756     initComponent: function() {
111757         var me = this,
111758             cls = [me.treeCls];
111759
111760         if (me.useArrows) {
111761             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
111762             me.lines = false;
111763         }
111764
111765         if (me.lines) {
111766             cls.push(Ext.baseCSSPrefix + 'tree-lines');
111767         } else if (!me.useArrows) {
111768             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
111769         }
111770
111771         if (Ext.isString(me.store)) {
111772             me.store = Ext.StoreMgr.lookup(me.store);
111773         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
111774             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
111775                 root: me.root,
111776                 fields: me.fields,
111777                 model: me.model,
111778                 folderSort: me.folderSort
111779             }));
111780         } else if (me.root) {
111781             me.store = Ext.data.StoreManager.lookup(me.store);
111782             me.store.setRootNode(me.root);
111783             if (me.folderSort !== undefined) {
111784                 me.store.folderSort = me.folderSort;
111785                 me.store.sort();
111786             }
111787         }
111788
111789         // I'm not sure if we want to this. It might be confusing
111790         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
111791         //     me.rootVisible = false;
111792         // }
111793
111794         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
111795             rootVisible: me.rootVisible,
111796             animate: me.enableAnimations,
111797             singleExpand: me.singleExpand,
111798             node: me.store.getRootNode(),
111799             hideHeaders: me.hideHeaders
111800         });
111801
111802         me.mon(me.store, {
111803             scope: me,
111804             rootchange: me.onRootChange,
111805             clear: me.onClear
111806         });
111807
111808         me.relayEvents(me.store, [
111809             /**
111810              * @event beforeload
111811              * @alias Ext.data.Store#beforeload
111812              */
111813             'beforeload',
111814
111815             /**
111816              * @event load
111817              * @alias Ext.data.Store#load
111818              */
111819             'load'
111820         ]);
111821
111822         me.store.on({
111823             /**
111824              * @event itemappend
111825              * @alias Ext.data.TreeStore#append
111826              */
111827             append: me.createRelayer('itemappend'),
111828
111829             /**
111830              * @event itemremove
111831              * @alias Ext.data.TreeStore#remove
111832              */
111833             remove: me.createRelayer('itemremove'),
111834
111835             /**
111836              * @event itemmove
111837              * @alias Ext.data.TreeStore#move
111838              */
111839             move: me.createRelayer('itemmove'),
111840
111841             /**
111842              * @event iteminsert
111843              * @alias Ext.data.TreeStore#insert
111844              */
111845             insert: me.createRelayer('iteminsert'),
111846
111847             /**
111848              * @event beforeitemappend
111849              * @alias Ext.data.TreeStore#beforeappend
111850              */
111851             beforeappend: me.createRelayer('beforeitemappend'),
111852
111853             /**
111854              * @event beforeitemremove
111855              * @alias Ext.data.TreeStore#beforeremove
111856              */
111857             beforeremove: me.createRelayer('beforeitemremove'),
111858
111859             /**
111860              * @event beforeitemmove
111861              * @alias Ext.data.TreeStore#beforemove
111862              */
111863             beforemove: me.createRelayer('beforeitemmove'),
111864
111865             /**
111866              * @event beforeiteminsert
111867              * @alias Ext.data.TreeStore#beforeinsert
111868              */
111869             beforeinsert: me.createRelayer('beforeiteminsert'),
111870
111871             /**
111872              * @event itemexpand
111873              * @alias Ext.data.TreeStore#expand
111874              */
111875             expand: me.createRelayer('itemexpand'),
111876
111877             /**
111878              * @event itemcollapse
111879              * @alias Ext.data.TreeStore#collapse
111880              */
111881             collapse: me.createRelayer('itemcollapse'),
111882
111883             /**
111884              * @event beforeitemexpand
111885              * @alias Ext.data.TreeStore#beforeexpand
111886              */
111887             beforeexpand: me.createRelayer('beforeitemexpand'),
111888
111889             /**
111890              * @event beforeitemcollapse
111891              * @alias Ext.data.TreeStore#beforecollapse
111892              */
111893             beforecollapse: me.createRelayer('beforeitemcollapse')
111894         });
111895
111896         // If the user specifies the headers collection manually then dont inject our own
111897         if (!me.columns) {
111898             if (me.initialConfig.hideHeaders === undefined) {
111899                 me.hideHeaders = true;
111900             }
111901             me.columns = [{
111902                 xtype    : 'treecolumn',
111903                 text     : 'Name',
111904                 flex     : 1,
111905                 dataIndex: me.displayField
111906             }];
111907         }
111908
111909         if (me.cls) {
111910             cls.push(me.cls);
111911         }
111912         me.cls = cls.join(' ');
111913         me.callParent();
111914
111915         me.relayEvents(me.getView(), [
111916             /**
111917              * @event checkchange
111918              * Fires when a node with a checkbox's checked property changes
111919              * @param {Ext.data.Model} node The node who's checked property was changed
111920              * @param {Boolean} checked The node's new checked state
111921              */
111922             'checkchange'
111923         ]);
111924
111925         // If the root is not visible and there is no rootnode defined, then just lets load the store
111926         if (!me.getView().rootVisible && !me.getRootNode()) {
111927             me.setRootNode({
111928                 expanded: true
111929             });
111930         }
111931     },
111932
111933     onClear: function(){
111934         this.view.onClear();
111935     },
111936
111937     /**
111938      * Sets root node of this tree.
111939      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
111940      * @return {Ext.data.NodeInterface} The new root
111941      */
111942     setRootNode: function() {
111943         return this.store.setRootNode.apply(this.store, arguments);
111944     },
111945
111946     /**
111947      * Returns the root node for this tree.
111948      * @return {Ext.data.NodeInterface}
111949      */
111950     getRootNode: function() {
111951         return this.store.getRootNode();
111952     },
111953
111954     onRootChange: function(root) {
111955         this.view.setRootNode(root);
111956     },
111957
111958     /**
111959      * Retrieve an array of checked records.
111960      * @return {Ext.data.Model[]} An array containing the checked records
111961      */
111962     getChecked: function() {
111963         return this.getView().getChecked();
111964     },
111965
111966     isItemChecked: function(rec) {
111967         return rec.get('checked');
111968     },
111969
111970     /**
111971      * Expand all nodes
111972      * @param {Function} callback (optional) A function to execute when the expand finishes.
111973      * @param {Object} scope (optional) The scope of the callback function
111974      */
111975     expandAll : function(callback, scope) {
111976         var root = this.getRootNode(),
111977             animate = this.enableAnimations,
111978             view = this.getView();
111979         if (root) {
111980             if (!animate) {
111981                 view.beginBulkUpdate();
111982             }
111983             root.expand(true, callback, scope);
111984             if (!animate) {
111985                 view.endBulkUpdate();
111986             }
111987         }
111988     },
111989
111990     /**
111991      * Collapse all nodes
111992      * @param {Function} callback (optional) A function to execute when the collapse finishes.
111993      * @param {Object} scope (optional) The scope of the callback function
111994      */
111995     collapseAll : function(callback, scope) {
111996         var root = this.getRootNode(),
111997             animate = this.enableAnimations,
111998             view = this.getView();
111999
112000         if (root) {
112001             if (!animate) {
112002                 view.beginBulkUpdate();
112003             }
112004             if (view.rootVisible) {
112005                 root.collapse(true, callback, scope);
112006             } else {
112007                 root.collapseChildren(true, callback, scope);
112008             }
112009             if (!animate) {
112010                 view.endBulkUpdate();
112011             }
112012         }
112013     },
112014
112015     /**
112016      * Expand the tree to the path of a particular node.
112017      * @param {String} path The path to expand. The path should include a leading separator.
112018      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
112019      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
112020      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
112021      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
112022      * @param {Object} scope (optional) The scope of the callback function
112023      */
112024     expandPath: function(path, field, separator, callback, scope) {
112025         var me = this,
112026             current = me.getRootNode(),
112027             index = 1,
112028             view = me.getView(),
112029             keys,
112030             expander;
112031
112032         field = field || me.getRootNode().idProperty;
112033         separator = separator || '/';
112034
112035         if (Ext.isEmpty(path)) {
112036             Ext.callback(callback, scope || me, [false, null]);
112037             return;
112038         }
112039
112040         keys = path.split(separator);
112041         if (current.get(field) != keys[1]) {
112042             // invalid root
112043             Ext.callback(callback, scope || me, [false, current]);
112044             return;
112045         }
112046
112047         expander = function(){
112048             if (++index === keys.length) {
112049                 Ext.callback(callback, scope || me, [true, current]);
112050                 return;
112051             }
112052             var node = current.findChild(field, keys[index]);
112053             if (!node) {
112054                 Ext.callback(callback, scope || me, [false, current]);
112055                 return;
112056             }
112057             current = node;
112058             current.expand(false, expander);
112059         };
112060         current.expand(false, expander);
112061     },
112062
112063     /**
112064      * Expand the tree to the path of a particular node, then select it.
112065      * @param {String} path The path to select. The path should include a leading separator.
112066      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
112067      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
112068      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
112069      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
112070      * @param {Object} scope (optional) The scope of the callback function
112071      */
112072     selectPath: function(path, field, separator, callback, scope) {
112073         var me = this,
112074             keys,
112075             last;
112076
112077         field = field || me.getRootNode().idProperty;
112078         separator = separator || '/';
112079
112080         keys = path.split(separator);
112081         last = keys.pop();
112082
112083         me.expandPath(keys.join(separator), field, separator, function(success, node){
112084             var doSuccess = false;
112085             if (success && node) {
112086                 node = node.findChild(field, last);
112087                 if (node) {
112088                     me.getSelectionModel().select(node);
112089                     Ext.callback(callback, scope || me, [true, node]);
112090                     doSuccess = true;
112091                 }
112092             } else if (node === me.getRootNode()) {
112093                 doSuccess = true;
112094             }
112095             Ext.callback(callback, scope || me, [doSuccess, node]);
112096         }, me);
112097     }
112098 });
112099
112100 /**
112101  * @class Ext.view.DragZone
112102  * @extends Ext.dd.DragZone
112103  * @private
112104  */
112105 Ext.define('Ext.view.DragZone', {
112106     extend: 'Ext.dd.DragZone',
112107     containerScroll: false,
112108
112109     constructor: function(config) {
112110         var me = this;
112111
112112         Ext.apply(me, config);
112113
112114         // Create a ddGroup unless one has been configured.
112115         // User configuration of ddGroups allows users to specify which
112116         // DD instances can interact with each other. Using one
112117         // based on the id of the View would isolate it and mean it can only
112118         // interact with a DropZone on the same View also using a generated ID.
112119         if (!me.ddGroup) {
112120             me.ddGroup = 'view-dd-zone-' + me.view.id;
112121         }
112122
112123         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
112124         // So a View's DragZone cannot use the View's main element because the DropZone must use that
112125         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
112126         // main element which handles scrolling.
112127         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
112128         // is transient; DataView's recreate the internal structure dynamically as data changes.
112129         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
112130         me.callParent([me.view.el.dom.parentNode]);
112131
112132         me.ddel = Ext.get(document.createElement('div'));
112133         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
112134     },
112135
112136     init: function(id, sGroup, config) {
112137         this.initTarget(id, sGroup, config);
112138         this.view.mon(this.view, {
112139             itemmousedown: this.onItemMouseDown,
112140             scope: this
112141         });
112142     },
112143
112144     onItemMouseDown: function(view, record, item, index, e) {
112145         if (!this.isPreventDrag(e, record, item, index)) {
112146             this.handleMouseDown(e);
112147
112148             // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
112149             // if the mousedowned record is selected
112150             if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
112151                 return false;
112152             }
112153         }
112154     },
112155
112156     // private template method
112157     isPreventDrag: function(e) {
112158         return false;
112159     },
112160
112161     getDragData: function(e) {
112162         var view = this.view,
112163             item = e.getTarget(view.getItemSelector()),
112164             record, selectionModel, records;
112165
112166         if (item) {
112167             record = view.getRecord(item);
112168             selectionModel = view.getSelectionModel();
112169             records = selectionModel.getSelection();
112170             return {
112171                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
112172                 event: new Ext.EventObjectImpl(e),
112173                 view: view,
112174                 ddel: this.ddel,
112175                 item: item,
112176                 records: records,
112177                 fromPosition: Ext.fly(item).getXY()
112178             };
112179         }
112180     },
112181
112182     onInitDrag: function(x, y) {
112183         var me = this,
112184             data = me.dragData,
112185             view = data.view,
112186             selectionModel = view.getSelectionModel(),
112187             record = view.getRecord(data.item),
112188             e = data.event;
112189
112190         // Update the selection to match what would have been selected if the user had
112191         // done a full click on the target node rather than starting a drag from it
112192         if (!selectionModel.isSelected(record) || e.hasModifier()) {
112193             selectionModel.selectWithEvent(record, e, true);
112194         }
112195         data.records = selectionModel.getSelection();
112196
112197         me.ddel.update(me.getDragText());
112198         me.proxy.update(me.ddel.dom);
112199         me.onStartDrag(x, y);
112200         return true;
112201     },
112202
112203     getDragText: function() {
112204         var count = this.dragData.records.length;
112205         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
112206     },
112207
112208     getRepairXY : function(e, data){
112209         return data ? data.fromPosition : false;
112210     }
112211 });
112212 Ext.define('Ext.tree.ViewDragZone', {
112213     extend: 'Ext.view.DragZone',
112214
112215     isPreventDrag: function(e, record) {
112216         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
112217     },
112218     
112219     afterRepair: function() {
112220         var me = this,
112221             view = me.view,
112222             selectedRowCls = view.selectedItemCls,
112223             records = me.dragData.records,
112224             fly = Ext.fly;
112225         
112226         if (Ext.enableFx && me.repairHighlight) {
112227             // Roll through all records and highlight all the ones we attempted to drag.
112228             Ext.Array.forEach(records, function(record) {
112229                 // anonymous fns below, don't hoist up unless below is wrapped in
112230                 // a self-executing function passing in item.
112231                 var item = view.getNode(record);
112232                 
112233                 // We must remove the selected row class before animating, because
112234                 // the selected row class declares !important on its background-color.
112235                 fly(item.firstChild).highlight(me.repairHighlightColor, {
112236                     listeners: {
112237                         beforeanimate: function() {
112238                             if (view.isSelected(item)) {
112239                                 fly(item).removeCls(selectedRowCls);
112240                             }
112241                         },
112242                         afteranimate: function() {
112243                             if (view.isSelected(item)) {
112244                                 fly(item).addCls(selectedRowCls);
112245                             }
112246                         }
112247                     }
112248                 });
112249             });
112250         }
112251         me.dragging = false;
112252     }
112253 });
112254 /**
112255  * @class Ext.tree.ViewDropZone
112256  * @extends Ext.view.DropZone
112257  * @private
112258  */
112259 Ext.define('Ext.tree.ViewDropZone', {
112260     extend: 'Ext.view.DropZone',
112261
112262     /**
112263      * @cfg {Boolean} allowParentInsert
112264      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
112265      * sibling of the parent when dropped.
112266      */
112267     allowParentInserts: false,
112268  
112269     /**
112270      * @cfg {String} allowContainerDrop
112271      * True if drops on the tree container (outside of a specific tree node) are allowed.
112272      */
112273     allowContainerDrops: false,
112274
112275     /**
112276      * @cfg {String} appendOnly
112277      * True if the tree should only allow append drops (use for trees which are sorted).
112278      */
112279     appendOnly: false,
112280
112281     /**
112282      * @cfg {String} expandDelay
112283      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
112284      * over the target.
112285      */
112286     expandDelay : 500,
112287
112288     indicatorCls: 'x-tree-ddindicator',
112289
112290     // private
112291     expandNode : function(node) {
112292         var view = this.view;
112293         if (!node.isLeaf() && !node.isExpanded()) {
112294             view.expand(node);
112295             this.expandProcId = false;
112296         }
112297     },
112298
112299     // private
112300     queueExpand : function(node) {
112301         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
112302     },
112303
112304     // private
112305     cancelExpand : function() {
112306         if (this.expandProcId) {
112307             clearTimeout(this.expandProcId);
112308             this.expandProcId = false;
112309         }
112310     },
112311
112312     getPosition: function(e, node) {
112313         var view = this.view,
112314             record = view.getRecord(node),
112315             y = e.getPageY(),
112316             noAppend = record.isLeaf(),
112317             noBelow = false,
112318             region = Ext.fly(node).getRegion(),
112319             fragment;
112320
112321         // If we are dragging on top of the root node of the tree, we always want to append.
112322         if (record.isRoot()) {
112323             return 'append';
112324         }
112325
112326         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
112327         if (this.appendOnly) {
112328             return noAppend ? false : 'append';
112329         }
112330
112331         if (!this.allowParentInsert) {
112332             noBelow = record.hasChildNodes() && record.isExpanded();
112333         }
112334
112335         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
112336         if (y >= region.top && y < (region.top + fragment)) {
112337             return 'before';
112338         }
112339         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
112340             return 'after';
112341         }
112342         else {
112343             return 'append';
112344         }
112345     },
112346
112347     isValidDropPoint : function(node, position, dragZone, e, data) {
112348         if (!node || !data.item) {
112349             return false;
112350         }
112351
112352         var view = this.view,
112353             targetNode = view.getRecord(node),
112354             draggedRecords = data.records,
112355             dataLength = draggedRecords.length,
112356             ln = draggedRecords.length,
112357             i, record;
112358
112359         // No drop position, or dragged records: invalid drop point
112360         if (!(targetNode && position && dataLength)) {
112361             return false;
112362         }
112363
112364         // If the targetNode is within the folder we are dragging
112365         for (i = 0; i < ln; i++) {
112366             record = draggedRecords[i];
112367             if (record.isNode && record.contains(targetNode)) {
112368                 return false;
112369             }
112370         }
112371         
112372         // Respect the allowDrop field on Tree nodes
112373         if (position === 'append' && targetNode.get('allowDrop') === false) {
112374             return false;
112375         }
112376         else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
112377             return false;
112378         }
112379
112380         // If the target record is in the dragged dataset, then invalid drop
112381         if (Ext.Array.contains(draggedRecords, targetNode)) {
112382              return false;
112383         }
112384
112385         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
112386         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
112387         return true;
112388     },
112389
112390     onNodeOver : function(node, dragZone, e, data) {
112391         var position = this.getPosition(e, node),
112392             returnCls = this.dropNotAllowed,
112393             view = this.view,
112394             targetNode = view.getRecord(node),
112395             indicator = this.getIndicator(),
112396             indicatorX = 0,
112397             indicatorY = 0;
112398
112399         // auto node expand check
112400         this.cancelExpand();
112401         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
112402             this.queueExpand(targetNode);
112403         }
112404             
112405             
112406         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
112407             this.valid = true;
112408             this.currentPosition = position;
112409             this.overRecord = targetNode;
112410
112411             indicator.setWidth(Ext.fly(node).getWidth());
112412             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
112413
112414             /*
112415              * In the code below we show the proxy again. The reason for doing this is showing the indicator will
112416              * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always 
112417              * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
112418              */
112419             if (position == 'before') {
112420                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
112421                 indicator.showAt(0, indicatorY);
112422                 dragZone.proxy.show();
112423             } else if (position == 'after') {
112424                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
112425                 indicatorY += Ext.fly(node).getHeight();
112426                 indicator.showAt(0, indicatorY);
112427                 dragZone.proxy.show();
112428             } else {
112429                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
112430                 // @TODO: set a class on the parent folder node to be able to style it
112431                 indicator.hide();
112432             }
112433         } else {
112434             this.valid = false;
112435         }
112436
112437         this.currentCls = returnCls;
112438         return returnCls;
112439     },
112440
112441     onContainerOver : function(dd, e, data) {
112442         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
112443     },
112444     
112445     notifyOut: function() {
112446         this.callParent(arguments);
112447         this.cancelExpand();
112448     },
112449
112450     handleNodeDrop : function(data, targetNode, position) {
112451         var me = this,
112452             view = me.view,
112453             parentNode = targetNode.parentNode,
112454             store = view.getStore(),
112455             recordDomNodes = [],
112456             records, i, len,
112457             insertionMethod, argList,
112458             needTargetExpand,
112459             transferData,
112460             processDrop;
112461
112462         // If the copy flag is set, create a copy of the Models with the same IDs
112463         if (data.copy) {
112464             records = data.records;
112465             data.records = [];
112466             for (i = 0, len = records.length; i < len; i++) {
112467                 data.records.push(Ext.apply({}, records[i].data));
112468             }
112469         }
112470
112471         // Cancel any pending expand operation
112472         me.cancelExpand();
112473
112474         // Grab a reference to the correct node insertion method.
112475         // Create an arg list array intended for the apply method of the
112476         // chosen node insertion method.
112477         // Ensure the target object for the method is referenced by 'targetNode'
112478         if (position == 'before') {
112479             insertionMethod = parentNode.insertBefore;
112480             argList = [null, targetNode];
112481             targetNode = parentNode;
112482         }
112483         else if (position == 'after') {
112484             if (targetNode.nextSibling) {
112485                 insertionMethod = parentNode.insertBefore;
112486                 argList = [null, targetNode.nextSibling];
112487             }
112488             else {
112489                 insertionMethod = parentNode.appendChild;
112490                 argList = [null];
112491             }
112492             targetNode = parentNode;
112493         }
112494         else {
112495             if (!targetNode.isExpanded()) {
112496                 needTargetExpand = true;
112497             }
112498             insertionMethod = targetNode.appendChild;
112499             argList = [null];
112500         }
112501
112502         // A function to transfer the data into the destination tree
112503         transferData = function() {
112504             var node;
112505             for (i = 0, len = data.records.length; i < len; i++) {
112506                 argList[0] = data.records[i];
112507                 node = insertionMethod.apply(targetNode, argList);
112508                 
112509                 if (Ext.enableFx && me.dropHighlight) {
112510                     recordDomNodes.push(view.getNode(node));
112511                 }
112512             }
112513             
112514             // Kick off highlights after everything's been inserted, so they are
112515             // more in sync without insertion/render overhead.
112516             if (Ext.enableFx && me.dropHighlight) {
112517                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
112518                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
112519                 Ext.Array.forEach(recordDomNodes, function(n) {
112520                     if (n) {
112521                         Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
112522                     }
112523                 });
112524             }
112525         };
112526
112527         // If dropping right on an unexpanded node, transfer the data after it is expanded.
112528         if (needTargetExpand) {
112529             targetNode.expand(false, transferData);
112530         }
112531         // Otherwise, call the data transfer function immediately
112532         else {
112533             transferData();
112534         }
112535     }
112536 });
112537 /**
112538  * This plugin provides drag and/or drop functionality for a TreeView.
112539  *
112540  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
112541  * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
112542  * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
112543  *
112544  *   - copy : Boolean
112545  *
112546  *     The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
112547  *     the control key was pressed when the drag operation was begun.
112548  *
112549  *   - view : TreeView
112550  *
112551  *     The source TreeView from which the drag originated.
112552  *
112553  *   - ddel : HtmlElement
112554  *
112555  *     The drag proxy element which moves with the mouse
112556  *
112557  *   - item : HtmlElement
112558  *
112559  *     The TreeView node upon which the mousedown event was registered.
112560  *
112561  *   - records : Array
112562  *
112563  *     An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
112564  *
112565  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
112566  * members of the same ddGroup which processes such data objects.
112567  *
112568  * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
112569  * {@link #drop}.
112570  *
112571  * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
112572  *
112573  *     viewConfig: {
112574  *         plugins: { ptype: 'treeviewdragdrop' }
112575  *     }
112576  */
112577 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
112578     extend: 'Ext.AbstractPlugin',
112579     alias: 'plugin.treeviewdragdrop',
112580
112581     uses: [
112582         'Ext.tree.ViewDragZone',
112583         'Ext.tree.ViewDropZone'
112584     ],
112585
112586     /**
112587      * @event beforedrop
112588      *
112589      * **This event is fired through the TreeView. Add listeners to the TreeView object**
112590      *
112591      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
112592      *
112593      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
112594      *
112595      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
112596      * back to the point from which the drag began.
112597      *
112598      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
112599      * was valid, and that the repair operation should not take place.
112600      *
112601      * Any other return value continues with the data transfer operation.
112602      *
112603      * @param {Object} data The data object gathered at mousedown time by the cooperating
112604      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
112605      * properties:
112606      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
112607      * `allowCopy: true` and the control key was pressed when the drag operation was begun
112608      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
112609      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
112610      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
112611      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
112612      * dragged from the source TreeView.
112613      *
112614      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
112615      *
112616      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
112617      * the midline of the node, or the node is a branch node which accepts new child nodes.
112618      *
112619      * @param {Function} dropFunction A function to call to complete the data transfer operation and either move or copy
112620      * Model instances from the source View's Store to the destination View's Store.
112621      *
112622      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
112623      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
112624      *
112625      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
112626      */
112627
112628     /**
112629      * @event drop
112630      *
112631      * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
112632      * has been completed and the data has been moved or copied.
112633      *
112634      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
112635      *
112636      * @param {Object} data The data object gathered at mousedown time by the cooperating
112637      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
112638      * properties:
112639      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
112640      * `allowCopy: true` and the control key was pressed when the drag operation was begun
112641      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
112642      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
112643      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
112644      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
112645      * dragged from the source TreeView.
112646      *
112647      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
112648      *
112649      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
112650      * the midline of the node, or the node is a branch node which accepts new child nodes.
112651      */
112652
112653     dragText : '{0} selected node{1}',
112654
112655     /**
112656      * @cfg {Boolean} allowParentInsert
112657      * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
112658      * the parent when dropped.
112659      */
112660     allowParentInserts: false,
112661
112662     /**
112663      * @cfg {String} allowContainerDrop
112664      * True if drops on the tree container (outside of a specific tree node) are allowed.
112665      */
112666     allowContainerDrops: false,
112667
112668     /**
112669      * @cfg {String} appendOnly
112670      * True if the tree should only allow append drops (use for trees which are sorted).
112671      */
112672     appendOnly: false,
112673
112674     /**
112675      * @cfg {String} ddGroup
112676      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
112677      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
112678      */
112679     ddGroup : "TreeDD",
112680
112681     /**
112682      * @cfg {String} dragGroup
112683      * The ddGroup to which the DragZone will belong.
112684      *
112685      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
112686      * Drag/DropZones which are members of the same ddGroup.
112687      */
112688
112689     /**
112690      * @cfg {String} dropGroup
112691      * The ddGroup to which the DropZone will belong.
112692      *
112693      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
112694      * Drag/DropZones which are members of the same ddGroup.
112695      */
112696
112697     /**
112698      * @cfg {String} expandDelay
112699      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
112700      * target.
112701      */
112702     expandDelay : 1000,
112703
112704     /**
112705      * @cfg {Boolean} enableDrop
112706      * Set to `false` to disallow the View from accepting drop gestures.
112707      */
112708     enableDrop: true,
112709
112710     /**
112711      * @cfg {Boolean} enableDrag
112712      * Set to `false` to disallow dragging items from the View.
112713      */
112714     enableDrag: true,
112715
112716     /**
112717      * @cfg {String} nodeHighlightColor
112718      * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
112719      * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
112720      * {@link #nodeHighlightOnRepair}.
112721      */
112722     nodeHighlightColor: 'c3daf9',
112723
112724     /**
112725      * @cfg {Boolean} nodeHighlightOnDrop
112726      * Whether or not to highlight any nodes after they are
112727      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
112728      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
112729      */
112730     nodeHighlightOnDrop: Ext.enableFx,
112731
112732     /**
112733      * @cfg {Boolean} nodeHighlightOnRepair
112734      * Whether or not to highlight any nodes after they are
112735      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
112736      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
112737      */
112738     nodeHighlightOnRepair: Ext.enableFx,
112739
112740     init : function(view) {
112741         view.on('render', this.onViewRender, this, {single: true});
112742     },
112743
112744     /**
112745      * @private
112746      * AbstractComponent calls destroy on all its plugins at destroy time.
112747      */
112748     destroy: function() {
112749         Ext.destroy(this.dragZone, this.dropZone);
112750     },
112751
112752     onViewRender : function(view) {
112753         var me = this;
112754
112755         if (me.enableDrag) {
112756             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
112757                 view: view,
112758                 ddGroup: me.dragGroup || me.ddGroup,
112759                 dragText: me.dragText,
112760                 repairHighlightColor: me.nodeHighlightColor,
112761                 repairHighlight: me.nodeHighlightOnRepair
112762             });
112763         }
112764
112765         if (me.enableDrop) {
112766             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
112767                 view: view,
112768                 ddGroup: me.dropGroup || me.ddGroup,
112769                 allowContainerDrops: me.allowContainerDrops,
112770                 appendOnly: me.appendOnly,
112771                 allowParentInserts: me.allowParentInserts,
112772                 expandDelay: me.expandDelay,
112773                 dropHighlightColor: me.nodeHighlightColor,
112774                 dropHighlight: me.nodeHighlightOnDrop
112775             });
112776         }
112777     }
112778 });
112779 /**
112780  * @class Ext.util.Cookies
112781
112782 Utility class for setting/reading values from browser cookies.
112783 Values can be written using the {@link #set} method.
112784 Values can be read using the {@link #get} method.
112785 A cookie can be invalidated on the client machine using the {@link #clear} method.
112786
112787  * @markdown
112788  * @singleton
112789  */
112790 Ext.define('Ext.util.Cookies', {
112791     singleton: true,
112792     
112793     /**
112794      * Create a cookie with the specified name and value. Additional settings
112795      * for the cookie may be optionally specified (for example: expiration,
112796      * access restriction, SSL).
112797      * @param {String} name The name of the cookie to set. 
112798      * @param {Object} value The value to set for the cookie.
112799      * @param {Object} expires (Optional) Specify an expiration date the
112800      * cookie is to persist until.  Note that the specified Date object will
112801      * be converted to Greenwich Mean Time (GMT). 
112802      * @param {String} path (Optional) Setting a path on the cookie restricts
112803      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
112804      * @param {String} domain (Optional) Setting a domain restricts access to
112805      * pages on a given domain (typically used to allow cookie access across
112806      * subdomains). For example, "sencha.com" will create a cookie that can be
112807      * accessed from any subdomain of sencha.com, including www.sencha.com,
112808      * support.sencha.com, etc.
112809      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
112810      * should only be accessible via SSL on a page using the HTTPS protocol.
112811      * Defaults to <tt>false</tt>. Note that this will only work if the page
112812      * calling this code uses the HTTPS protocol, otherwise the cookie will be
112813      * created with default options.
112814      */
112815     set : function(name, value){
112816         var argv = arguments,
112817             argc = arguments.length,
112818             expires = (argc > 2) ? argv[2] : null,
112819             path = (argc > 3) ? argv[3] : '/',
112820             domain = (argc > 4) ? argv[4] : null,
112821             secure = (argc > 5) ? argv[5] : false;
112822             
112823         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
112824     },
112825
112826     /**
112827      * Retrieves cookies that are accessible by the current page. If a cookie
112828      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
112829      * example retrieves the cookie called "valid" and stores the String value
112830      * in the variable <tt>validStatus</tt>.
112831      * <pre><code>
112832      * var validStatus = Ext.util.Cookies.get("valid");
112833      * </code></pre>
112834      * @param {String} name The name of the cookie to get
112835      * @return {Object} Returns the cookie value for the specified name;
112836      * null if the cookie name does not exist.
112837      */
112838     get : function(name){
112839         var arg = name + "=",
112840             alen = arg.length,
112841             clen = document.cookie.length,
112842             i = 0,
112843             j = 0;
112844             
112845         while(i < clen){
112846             j = i + alen;
112847             if(document.cookie.substring(i, j) == arg){
112848                 return this.getCookieVal(j);
112849             }
112850             i = document.cookie.indexOf(" ", i) + 1;
112851             if(i === 0){
112852                 break;
112853             }
112854         }
112855         return null;
112856     },
112857
112858     /**
112859      * Removes a cookie with the provided name from the browser
112860      * if found by setting its expiration date to sometime in the past. 
112861      * @param {String} name The name of the cookie to remove
112862      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
112863      */
112864     clear : function(name, path){
112865         if(this.get(name)){
112866             path = path || '/';
112867             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
112868         }
112869     },
112870     
112871     /**
112872      * @private
112873      */
112874     getCookieVal : function(offset){
112875         var endstr = document.cookie.indexOf(";", offset);
112876         if(endstr == -1){
112877             endstr = document.cookie.length;
112878         }
112879         return unescape(document.cookie.substring(offset, endstr));
112880     }
112881 });
112882
112883 /**
112884  * @class Ext.util.CSS
112885  * Utility class for manipulating CSS rules
112886  * @singleton
112887  */
112888 Ext.define('Ext.util.CSS', function() {
112889     var rules = null;
112890     var doc = document;
112891
112892     var camelRe = /(-[a-z])/gi;
112893     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
112894
112895     return {
112896
112897         singleton: true,
112898
112899         constructor: function() {
112900             this.rules = {};
112901             this.initialized = false;
112902         },
112903
112904         /**
112905          * Creates a stylesheet from a text blob of rules.
112906          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
112907          * @param {String} cssText The text containing the css rules
112908          * @param {String} id An id to add to the stylesheet for later removal
112909          * @return {CSSStyleSheet}
112910          */
112911         createStyleSheet : function(cssText, id) {
112912             var ss,
112913                 head = doc.getElementsByTagName("head")[0],
112914                 styleEl = doc.createElement("style");
112915
112916             styleEl.setAttribute("type", "text/css");
112917             if (id) {
112918                styleEl.setAttribute("id", id);
112919             }
112920
112921             if (Ext.isIE) {
112922                head.appendChild(styleEl);
112923                ss = styleEl.styleSheet;
112924                ss.cssText = cssText;
112925             } else {
112926                 try{
112927                     styleEl.appendChild(doc.createTextNode(cssText));
112928                 } catch(e) {
112929                    styleEl.cssText = cssText;
112930                 }
112931                 head.appendChild(styleEl);
112932                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
112933             }
112934             this.cacheStyleSheet(ss);
112935             return ss;
112936         },
112937
112938         /**
112939          * Removes a style or link tag by id
112940          * @param {String} id The id of the tag
112941          */
112942         removeStyleSheet : function(id) {
112943             var existing = document.getElementById(id);
112944             if (existing) {
112945                 existing.parentNode.removeChild(existing);
112946             }
112947         },
112948
112949         /**
112950          * Dynamically swaps an existing stylesheet reference for a new one
112951          * @param {String} id The id of an existing link tag to remove
112952          * @param {String} url The href of the new stylesheet to include
112953          */
112954         swapStyleSheet : function(id, url) {
112955             var doc = document;
112956             this.removeStyleSheet(id);
112957             var ss = doc.createElement("link");
112958             ss.setAttribute("rel", "stylesheet");
112959             ss.setAttribute("type", "text/css");
112960             ss.setAttribute("id", id);
112961             ss.setAttribute("href", url);
112962             doc.getElementsByTagName("head")[0].appendChild(ss);
112963         },
112964
112965         /**
112966          * Refresh the rule cache if you have dynamically added stylesheets
112967          * @return {Object} An object (hash) of rules indexed by selector
112968          */
112969         refreshCache : function() {
112970             return this.getRules(true);
112971         },
112972
112973         // private
112974         cacheStyleSheet : function(ss) {
112975             if(!rules){
112976                 rules = {};
112977             }
112978             try {// try catch for cross domain access issue
112979                 var ssRules = ss.cssRules || ss.rules,
112980                     selectorText,
112981                     i = ssRules.length - 1,
112982                     j,
112983                     selectors;
112984
112985                 for (; i >= 0; --i) {
112986                     selectorText = ssRules[i].selectorText;
112987                     if (selectorText) {
112988
112989                         // Split in case there are multiple, comma-delimited selectors
112990                         selectorText = selectorText.split(',');
112991                         selectors = selectorText.length;
112992                         for (j = 0; j < selectors; j++) {
112993                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
112994                         }
112995                     }
112996                 }
112997             } catch(e) {}
112998         },
112999
113000         /**
113001         * Gets all css rules for the document
113002         * @param {Boolean} refreshCache true to refresh the internal cache
113003         * @return {Object} An object (hash) of rules indexed by selector
113004         */
113005         getRules : function(refreshCache) {
113006             if (rules === null || refreshCache) {
113007                 rules = {};
113008                 var ds = doc.styleSheets,
113009                     i = 0,
113010                     len = ds.length;
113011
113012                 for (; i < len; i++) {
113013                     try {
113014                         if (!ds[i].disabled) {
113015                             this.cacheStyleSheet(ds[i]);
113016                         }
113017                     } catch(e) {}
113018                 }
113019             }
113020             return rules;
113021         },
113022
113023         /**
113024          * Gets an an individual CSS rule by selector(s)
113025          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
113026          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
113027          * @return {CSSStyleRule} The CSS rule or null if one is not found
113028          */
113029         getRule: function(selector, refreshCache) {
113030             var rs = this.getRules(refreshCache);
113031             if (!Ext.isArray(selector)) {
113032                 return rs[selector.toLowerCase()];
113033             }
113034             for (var i = 0; i < selector.length; i++) {
113035                 if (rs[selector[i]]) {
113036                     return rs[selector[i].toLowerCase()];
113037                 }
113038             }
113039             return null;
113040         },
113041
113042         /**
113043          * Updates a rule property
113044          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
113045          * @param {String} property The css property
113046          * @param {String} value The new value for the property
113047          * @return {Boolean} true If a rule was found and updated
113048          */
113049         updateRule : function(selector, property, value){
113050             if (!Ext.isArray(selector)) {
113051                 var rule = this.getRule(selector);
113052                 if (rule) {
113053                     rule.style[property.replace(camelRe, camelFn)] = value;
113054                     return true;
113055                 }
113056             } else {
113057                 for (var i = 0; i < selector.length; i++) {
113058                     if (this.updateRule(selector[i], property, value)) {
113059                         return true;
113060                     }
113061                 }
113062             }
113063             return false;
113064         }
113065     };
113066 }());
113067 /**
113068  * @class Ext.util.History
113069  *
113070  * History management component that allows you to register arbitrary tokens that signify application
113071  * history state on navigation actions.  You can then handle the history {@link #change} event in order
113072  * to reset your application UI to the appropriate state when the user navigates forward or backward through
113073  * the browser history stack.
113074  *
113075  * ## Initializing
113076  * The {@link #init} method of the History object must be called before using History. This sets up the internal
113077  * state and must be the first thing called before using History.
113078  *
113079  * ## Setup
113080  * The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
113081  * an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
113082  * these to be on the page before the {@link #init} method is called. The following markup is suggested in order
113083  * to support all browsers:
113084  *
113085  *     <form id="history-form" class="x-hide-display">
113086  *         <input type="hidden" id="x-history-field" />
113087  *         <iframe id="x-history-frame"></iframe>
113088  *     </form>
113089  *
113090  * @singleton
113091  */
113092 Ext.define('Ext.util.History', {
113093     singleton: true,
113094     alternateClassName: 'Ext.History',
113095     mixins: {
113096         observable: 'Ext.util.Observable'
113097     },
113098
113099     constructor: function() {
113100         var me = this;
113101         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
113102         me.iframe = null;
113103         me.hiddenField = null;
113104         me.ready = false;
113105         me.currentToken = null;
113106     },
113107
113108     getHash: function() {
113109         var href = window.location.href,
113110             i = href.indexOf("#");
113111
113112         return i >= 0 ? href.substr(i + 1) : null;
113113     },
113114
113115     doSave: function() {
113116         this.hiddenField.value = this.currentToken;
113117     },
113118
113119
113120     handleStateChange: function(token) {
113121         this.currentToken = token;
113122         this.fireEvent('change', token);
113123     },
113124
113125     updateIFrame: function(token) {
113126         var html = '<html><body><div id="state">' +
113127                     Ext.util.Format.htmlEncode(token) +
113128                     '</div></body></html>';
113129
113130         try {
113131             var doc = this.iframe.contentWindow.document;
113132             doc.open();
113133             doc.write(html);
113134             doc.close();
113135             return true;
113136         } catch (e) {
113137             return false;
113138         }
113139     },
113140
113141     checkIFrame: function () {
113142         var me = this,
113143             contentWindow = me.iframe.contentWindow;
113144
113145         if (!contentWindow || !contentWindow.document) {
113146             Ext.Function.defer(this.checkIFrame, 10, this);
113147             return;
113148         }
113149
113150         var doc = contentWindow.document,
113151             elem = doc.getElementById("state"),
113152             oldToken = elem ? elem.innerText : null,
113153             oldHash = me.getHash();
113154
113155         Ext.TaskManager.start({
113156             run: function () {
113157                 var doc = contentWindow.document,
113158                     elem = doc.getElementById("state"),
113159                     newToken = elem ? elem.innerText : null,
113160                     newHash = me.getHash();
113161
113162                 if (newToken !== oldToken) {
113163                     oldToken = newToken;
113164                     me.handleStateChange(newToken);
113165                     window.top.location.hash = newToken;
113166                     oldHash = newToken;
113167                     me.doSave();
113168                 } else if (newHash !== oldHash) {
113169                     oldHash = newHash;
113170                     me.updateIFrame(newHash);
113171                 }
113172             },
113173             interval: 50,
113174             scope: me
113175         });
113176         me.ready = true;
113177         me.fireEvent('ready', me);
113178     },
113179
113180     startUp: function () {
113181         var me = this;
113182
113183         me.currentToken = me.hiddenField.value || this.getHash();
113184
113185         if (me.oldIEMode) {
113186             me.checkIFrame();
113187         } else {
113188             var hash = me.getHash();
113189             Ext.TaskManager.start({
113190                 run: function () {
113191                     var newHash = me.getHash();
113192                     if (newHash !== hash) {
113193                         hash = newHash;
113194                         me.handleStateChange(hash);
113195                         me.doSave();
113196                     }
113197                 },
113198                 interval: 50,
113199                 scope: me
113200             });
113201             me.ready = true;
113202             me.fireEvent('ready', me);
113203         }
113204
113205     },
113206
113207     /**
113208      * The id of the hidden field required for storing the current history token.
113209      * @type String
113210      * @property
113211      */
113212     fieldId: Ext.baseCSSPrefix + 'history-field',
113213     /**
113214      * The id of the iframe required by IE to manage the history stack.
113215      * @type String
113216      * @property
113217      */
113218     iframeId: Ext.baseCSSPrefix + 'history-frame',
113219
113220     /**
113221      * Initialize the global History instance.
113222      * @param {Boolean} onReady (optional) A callback function that will be called once the history
113223      * component is fully initialized.
113224      * @param {Object} scope (optional) The scope (`this` reference) in which the callback is executed. Defaults to the browser window.
113225      */
113226     init: function (onReady, scope) {
113227         var me = this;
113228
113229         if (me.ready) {
113230             Ext.callback(onReady, scope, [me]);
113231             return;
113232         }
113233
113234         if (!Ext.isReady) {
113235             Ext.onReady(function() {
113236                 me.init(onReady, scope);
113237             });
113238             return;
113239         }
113240
113241         me.hiddenField = Ext.getDom(me.fieldId);
113242
113243         if (me.oldIEMode) {
113244             me.iframe = Ext.getDom(me.iframeId);
113245         }
113246
113247         me.addEvents(
113248             /**
113249              * @event ready
113250              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
113251              * @param {Ext.util.History} The Ext.util.History singleton.
113252              */
113253             'ready',
113254             /**
113255              * @event change
113256              * Fires when navigation back or forwards within the local page's history occurs.
113257              * @param {String} token An identifier associated with the page state at that point in its history.
113258              */
113259             'change'
113260         );
113261
113262         if (onReady) {
113263             me.on('ready', onReady, scope, {single: true});
113264         }
113265         me.startUp();
113266     },
113267
113268     /**
113269      * Add a new token to the history stack. This can be any arbitrary value, although it would
113270      * commonly be the concatenation of a component id and another id marking the specific history
113271      * state of that component. Example usage:
113272      *
113273      *     // Handle tab changes on a TabPanel
113274      *     tabPanel.on('tabchange', function(tabPanel, tab){
113275      *          Ext.History.add(tabPanel.id + ':' + tab.id);
113276      *     });
113277      *
113278      * @param {String} token The value that defines a particular application-specific history state
113279      * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
113280      * it will not save a new history step. Set to false if the same state can be saved more than once
113281      * at the same history stack location.
113282      */
113283     add: function (token, preventDup) {
113284         var me = this;
113285
113286         if (preventDup !== false) {
113287             if (me.getToken() === token) {
113288                 return true;
113289             }
113290         }
113291
113292         if (me.oldIEMode) {
113293             return me.updateIFrame(token);
113294         } else {
113295             window.top.location.hash = token;
113296             return true;
113297         }
113298     },
113299
113300     /**
113301      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
113302      */
113303     back: function() {
113304         window.history.go(-1);
113305     },
113306
113307     /**
113308      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
113309      */
113310     forward: function(){
113311         window.history.go(1);
113312     },
113313
113314     /**
113315      * Retrieves the currently-active history token.
113316      * @return {String} The token
113317      */
113318     getToken: function() {
113319         return this.ready ? this.currentToken : this.getHash();
113320     }
113321 });
113322 /**
113323  * @class Ext.view.TableChunker
113324  * 
113325  * Produces optimized XTemplates for chunks of tables to be
113326  * used in grids, trees and other table based widgets.
113327  *
113328  * @singleton
113329  */
113330 Ext.define('Ext.view.TableChunker', {
113331     singleton: true,
113332     requires: ['Ext.XTemplate'],
113333     metaTableTpl: [
113334         '{[this.openTableWrap()]}',
113335         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
113336             '<tbody>',
113337             '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
113338             '<tpl for="columns">',
113339                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
113340             '</tpl>',
113341             '</tr>',
113342             '{[this.openRows()]}',
113343                 '{row}',
113344                 '<tpl for="features">',
113345                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
113346                 '</tpl>',
113347             '{[this.closeRows()]}',
113348             '</tbody>',
113349         '</table>',
113350         '{[this.closeTableWrap()]}'
113351     ],
113352
113353     constructor: function() {
113354         Ext.XTemplate.prototype.recurse = function(values, reference) {
113355             return this.apply(reference ? values[reference] : values);
113356         };
113357     },
113358
113359     embedFeature: function(values, parent, x, xcount) {
113360         var tpl = '';
113361         if (!values.disabled) {
113362             tpl = values.getFeatureTpl(values, parent, x, xcount);
113363         }
113364         return tpl;
113365     },
113366
113367     embedFullWidth: function() {
113368         return 'style="width: {fullWidth}px;"';
113369     },
113370
113371     openRows: function() {
113372         return '<tpl for="rows">';
113373     },
113374
113375     closeRows: function() {
113376         return '</tpl>';
113377     },
113378
113379     metaRowTpl: [
113380         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
113381             '<tpl for="columns">',
113382                 '<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>',
113383             '</tpl>',
113384         '</tr>'
113385     ],
113386     
113387     firstOrLastCls: function(xindex, xcount) {
113388         var cssCls = '';
113389         if (xindex === 1) {
113390             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
113391         } else if (xindex === xcount) {
113392             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
113393         }
113394         return cssCls;
113395     },
113396     
113397     embedRowCls: function() {
113398         return '{rowCls}';
113399     },
113400     
113401     embedRowAttr: function() {
113402         return '{rowAttr}';
113403     },
113404     
113405     openTableWrap: function() {
113406         return '';
113407     },
113408     
113409     closeTableWrap: function() {
113410         return '';
113411     },
113412
113413     getTableTpl: function(cfg, textOnly) {
113414         var tpl,
113415             tableTplMemberFns = {
113416                 openRows: this.openRows,
113417                 closeRows: this.closeRows,
113418                 embedFeature: this.embedFeature,
113419                 embedFullWidth: this.embedFullWidth,
113420                 openTableWrap: this.openTableWrap,
113421                 closeTableWrap: this.closeTableWrap
113422             },
113423             tplMemberFns = {},
113424             features = cfg.features || [],
113425             ln = features.length,
113426             i  = 0,
113427             memberFns = {
113428                 embedRowCls: this.embedRowCls,
113429                 embedRowAttr: this.embedRowAttr,
113430                 firstOrLastCls: this.firstOrLastCls
113431             },
113432             // copy the default
113433             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
113434             metaTableTpl;
113435             
113436         for (; i < ln; i++) {
113437             if (!features[i].disabled) {
113438                 features[i].mutateMetaRowTpl(metaRowTpl);
113439                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
113440                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
113441                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
113442             }
113443         }
113444         
113445         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
113446         cfg.row = metaRowTpl.applyTemplate(cfg);
113447         
113448         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
113449         
113450         tpl = metaTableTpl.applyTemplate(cfg);
113451         
113452         // TODO: Investigate eliminating.
113453         if (!textOnly) {
113454             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
113455         }
113456         return tpl;
113457         
113458     }
113459 });
113460
113461