Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / EventManager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.EventManager
17  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
18  * several useful events directly.
19  * See {@link Ext.EventObject} for more details on normalized event objects.
20  * @singleton
21  */
22 Ext.EventManager = {
23
24     // --------------------- onReady ---------------------
25
26     /**
27      * Check if we have bound our global onReady listener
28      * @private
29      */
30     hasBoundOnReady: false,
31
32     /**
33      * Check if fireDocReady has been called
34      * @private
35      */
36     hasFiredReady: false,
37
38     /**
39      * Timer for the document ready event in old IE versions
40      * @private
41      */
42     readyTimeout: null,
43
44     /**
45      * Checks if we have bound an onreadystatechange event
46      * @private
47      */
48     hasOnReadyStateChange: false,
49
50     /**
51      * Holds references to any onReady functions
52      * @private
53      */
54     readyEvent: new Ext.util.Event(),
55
56     /**
57      * Check the ready state for old IE versions
58      * @private
59      * @return {Boolean} True if the document is ready
60      */
61     checkReadyState: function(){
62         var me = Ext.EventManager;
63
64         if(window.attachEvent){
65             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
66             // licensed courtesy of http://developer.yahoo.com/yui/license.html
67             if (window != top) {
68                 return false;
69             }
70             try{
71                 document.documentElement.doScroll('left');
72             }catch(e){
73                 return false;
74             }
75             me.fireDocReady();
76             return true;
77         }
78         if (document.readyState == 'complete') {
79             me.fireDocReady();
80             return true;
81         }
82         me.readyTimeout = setTimeout(arguments.callee, 2);
83         return false;
84     },
85
86     /**
87      * Binds the appropriate browser event for checking if the DOM has loaded.
88      * @private
89      */
90     bindReadyEvent: function(){
91         var me = Ext.EventManager;
92         if (me.hasBoundOnReady) {
93             return;
94         }
95
96         if (document.addEventListener) {
97             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
98             // fallback, load will ~always~ fire
99             window.addEventListener('load', me.fireDocReady, false);
100         } else {
101             // check if the document is ready, this will also kick off the scroll checking timer
102             if (!me.checkReadyState()) {
103                 document.attachEvent('onreadystatechange', me.checkReadyState);
104                 me.hasOnReadyStateChange = true;
105             }
106             // fallback, onload will ~always~ fire
107             window.attachEvent('onload', me.fireDocReady, false);
108         }
109         me.hasBoundOnReady = true;
110     },
111
112     /**
113      * We know the document is loaded, so trigger any onReady events.
114      * @private
115      */
116     fireDocReady: function(){
117         var me = Ext.EventManager;
118
119         // only unbind these events once
120         if (!me.hasFiredReady) {
121             me.hasFiredReady = true;
122
123             if (document.addEventListener) {
124                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
125                 window.removeEventListener('load', me.fireDocReady, false);
126             } else {
127                 if (me.readyTimeout !== null) {
128                     clearTimeout(me.readyTimeout);
129                 }
130                 if (me.hasOnReadyStateChange) {
131                     document.detachEvent('onreadystatechange', me.checkReadyState);
132                 }
133                 window.detachEvent('onload', me.fireDocReady);
134             }
135             Ext.supports.init();
136         }
137         if (!Ext.isReady) {
138             Ext.isReady = true;
139             me.onWindowUnload();
140             me.readyEvent.fire();
141         }
142     },
143
144     /**
145      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
146      * accessed shorthanded as Ext.onReady().
147      * @param {Function} fn The method the event invokes.
148      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
149      * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}.
150      */
151     onDocumentReady: function(fn, scope, options){
152         options = options || {};
153         var me = Ext.EventManager,
154             readyEvent = me.readyEvent;
155
156         // force single to be true so our event is only ever fired once.
157         options.single = true;
158
159         // Document already loaded, let's just fire it
160         if (Ext.isReady) {
161             readyEvent.addListener(fn, scope, options);
162             readyEvent.fire();
163         } else {
164             options.delay = options.delay || 1;
165             readyEvent.addListener(fn, scope, options);
166             me.bindReadyEvent();
167         }
168     },
169
170
171     // --------------------- event binding ---------------------
172
173     /**
174      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
175      * @private
176      */
177     stoppedMouseDownEvent: new Ext.util.Event(),
178
179     /**
180      * Options to parse for the 4th argument to addListener.
181      * @private
182      */
183     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
184
185     /**
186      * Get the id of the element. If one has not been assigned, automatically assign it.
187      * @param {HTMLElement/Ext.Element} element The element to get the id for.
188      * @return {String} id
189      */
190     getId : function(element) {
191         var skipGarbageCollection = false,
192             id;
193
194         element = Ext.getDom(element);
195
196         if (element === document || element === window) {
197             id = element === document ? Ext.documentId : Ext.windowId;
198         }
199         else {
200             id = Ext.id(element);
201         }
202         // skip garbage collection for special elements (window, document, iframes)
203         if (element && (element.getElementById || element.navigator)) {
204             skipGarbageCollection = true;
205         }
206
207         if (!Ext.cache[id]){
208             Ext.Element.addToCache(new Ext.Element(element), id);
209             if (skipGarbageCollection) {
210                 Ext.cache[id].skipGarbageCollection = true;
211             }
212         }
213         return id;
214     },
215
216     /**
217      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
218      * @private
219      * @param {Object} element The element the event is for
220      * @param {Object} event The event configuration
221      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
222      */
223     prepareListenerConfig: function(element, config, isRemove){
224         var me = this,
225             propRe = me.propRe,
226             key, value, args;
227
228         // loop over all the keys in the object
229         for (key in config) {
230             if (config.hasOwnProperty(key)) {
231                 // if the key is something else then an event option
232                 if (!propRe.test(key)) {
233                     value = config[key];
234                     // if the value is a function it must be something like click: function(){}, scope: this
235                     // which means that there might be multiple event listeners with shared options
236                     if (Ext.isFunction(value)) {
237                         // shared options
238                         args = [element, key, value, config.scope, config];
239                     } else {
240                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
241                         args = [element, key, value.fn, value.scope, value];
242                     }
243
244                     if (isRemove === true) {
245                         me.removeListener.apply(this, args);
246                     } else {
247                         me.addListener.apply(me, args);
248                     }
249                 }
250             }
251         }
252     },
253
254     /**
255      * Normalize cross browser event differences
256      * @private
257      * @param {Object} eventName The event name
258      * @param {Object} fn The function to execute
259      * @return {Object} The new event name/function
260      */
261     normalizeEvent: function(eventName, fn){
262         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
263             if (fn) {
264                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
265             }
266             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
267         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
268             eventName = 'DOMMouseScroll';
269         }
270         return {
271             eventName: eventName,
272             fn: fn
273         };
274     },
275
276     /**
277      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
278      * @private
279      * @param {Object} event
280      */
281     contains: function(event){
282         var parent = event.browserEvent.currentTarget,
283             child = this.getRelatedTarget(event);
284
285         if (parent && parent.firstChild) {
286             while (child) {
287                 if (child === parent) {
288                     return false;
289                 }
290                 child = child.parentNode;
291                 if (child && (child.nodeType != 1)) {
292                     child = null;
293                 }
294             }
295         }
296         return true;
297     },
298
299     /**
300     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
301     * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
302     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
303     * @param {String} eventName The name of the event to listen for.
304     * @param {Function} handler The handler function the event invokes. This function is passed
305     * the following parameters:<ul>
306     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
307     * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
308     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
309     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
310     * </ul>
311     * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
312     * @param {Object} options (optional) An object containing handler configuration properties.
313     * This may contain any of the following properties:<ul>
314     * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
315     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
316     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
317     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
318     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
319     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
320     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
321     * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
322     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
323     * by the specified number of milliseconds. If the event fires again within that time, the original
324     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
325     * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
326     * </ul><br>
327     * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
328     */
329     addListener: function(element, eventName, fn, scope, options){
330         // Check if we've been passed a "config style" event.
331         if (typeof eventName !== 'string') {
332             this.prepareListenerConfig(element, eventName);
333             return;
334         }
335
336         var dom = Ext.getDom(element),
337             bind,
338             wrap;
339
340         //<debug>
341         if (!dom){
342             Ext.Error.raise({
343                 sourceClass: 'Ext.EventManager',
344                 sourceMethod: 'addListener',
345                 targetElement: element,
346                 eventName: eventName,
347                 msg: 'Error adding "' + eventName + '\" listener for nonexistent element "' + element + '"'
348             });
349         }
350         if (!fn) {
351             Ext.Error.raise({
352                 sourceClass: 'Ext.EventManager',
353                 sourceMethod: 'addListener',
354                 targetElement: element,
355                 eventName: eventName,
356                 msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
357             });
358         }
359         //</debug>
360
361         // create the wrapper function
362         options = options || {};
363
364         bind = this.normalizeEvent(eventName, fn);
365         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
366
367
368         if (dom.attachEvent) {
369             dom.attachEvent('on' + bind.eventName, wrap);
370         } else {
371             dom.addEventListener(bind.eventName, wrap, options.capture || false);
372         }
373
374         if (dom == document && eventName == 'mousedown') {
375             this.stoppedMouseDownEvent.addListener(wrap);
376         }
377
378         // add all required data into the event cache
379         this.getEventListenerCache(dom, eventName).push({
380             fn: fn,
381             wrap: wrap,
382             scope: scope
383         });
384     },
385
386     /**
387     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
388     * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
389     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
390     * @param {String} eventName The name of the event.
391     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
392     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
393     * then this must refer to the same object.
394     */
395     removeListener : function(element, eventName, fn, scope) {
396         // handle our listener config object syntax
397         if (typeof eventName !== 'string') {
398             this.prepareListenerConfig(element, eventName, true);
399             return;
400         }
401
402         var dom = Ext.getDom(element),
403             cache = this.getEventListenerCache(dom, eventName),
404             bindName = this.normalizeEvent(eventName).eventName,
405             i = cache.length, j,
406             listener, wrap, tasks;
407
408
409         while (i--) {
410             listener = cache[i];
411
412             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
413                 wrap = listener.wrap;
414
415                 // clear buffered calls
416                 if (wrap.task) {
417                     clearTimeout(wrap.task);
418                     delete wrap.task;
419                 }
420
421                 // clear delayed calls
422                 j = wrap.tasks && wrap.tasks.length;
423                 if (j) {
424                     while (j--) {
425                         clearTimeout(wrap.tasks[j]);
426                     }
427                     delete wrap.tasks;
428                 }
429
430                 if (dom.detachEvent) {
431                     dom.detachEvent('on' + bindName, wrap);
432                 } else {
433                     dom.removeEventListener(bindName, wrap, false);
434                 }
435
436                 if (wrap && dom == document && eventName == 'mousedown') {
437                     this.stoppedMouseDownEvent.removeListener(wrap);
438                 }
439
440                 // remove listener from cache
441                 Ext.Array.erase(cache, i, 1);
442             }
443         }
444     },
445
446     /**
447     * Removes all event handers from an element.  Typically you will use {@link Ext.Element#removeAllListeners}
448     * directly on an Element in favor of calling this version.
449     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
450     */
451     removeAll : function(element){
452         var dom = Ext.getDom(element),
453             cache, ev;
454         if (!dom) {
455             return;
456         }
457         cache = this.getElementEventCache(dom);
458
459         for (ev in cache) {
460             if (cache.hasOwnProperty(ev)) {
461                 this.removeListener(dom, ev);
462             }
463         }
464         Ext.cache[dom.id].events = {};
465     },
466
467     /**
468      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.Element#purgeAllListeners}
469      * directly on an Element in favor of calling this version.
470      * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
471      * @param {String} eventName (optional) The name of the event.
472      */
473     purgeElement : function(element, eventName) {
474         var dom = Ext.getDom(element),
475             i = 0, len;
476
477         if(eventName) {
478             this.removeListener(dom, eventName);
479         }
480         else {
481             this.removeAll(dom);
482         }
483
484         if(dom && dom.childNodes) {
485             for(len = element.childNodes.length; i < len; i++) {
486                 this.purgeElement(element.childNodes[i], eventName);
487             }
488         }
489     },
490
491     /**
492      * Create the wrapper function for the event
493      * @private
494      * @param {HTMLElement} dom The dom element
495      * @param {String} ename The event name
496      * @param {Function} fn The function to execute
497      * @param {Object} scope The scope to execute callback in
498      * @param {Object} options The options
499      * @return {Function} the wrapper function
500      */
501     createListenerWrap : function(dom, ename, fn, scope, options) {
502         options = options || {};
503
504         var f, gen;
505
506         return function wrap(e, args) {
507             // Compile the implementation upon first firing
508             if (!gen) {
509                 f = ['if(!Ext) {return;}'];
510
511                 if(options.buffer || options.delay || options.freezeEvent) {
512                     f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
513                 } else {
514                     f.push('e = Ext.EventObject.setEvent(e);');
515                 }
516
517                 if (options.delegate) {
518                     f.push('var t = e.getTarget("' + options.delegate + '", this);');
519                     f.push('if(!t) {return;}');
520                 } else {
521                     f.push('var t = e.target;');
522                 }
523
524                 if (options.target) {
525                     f.push('if(e.target !== options.target) {return;}');
526                 }
527
528                 if(options.stopEvent) {
529                     f.push('e.stopEvent();');
530                 } else {
531                     if(options.preventDefault) {
532                         f.push('e.preventDefault();');
533                     }
534                     if(options.stopPropagation) {
535                         f.push('e.stopPropagation();');
536                     }
537                 }
538
539                 if(options.normalized === false) {
540                     f.push('e = e.browserEvent;');
541                 }
542
543                 if(options.buffer) {
544                     f.push('(wrap.task && clearTimeout(wrap.task));');
545                     f.push('wrap.task = setTimeout(function(){');
546                 }
547
548                 if(options.delay) {
549                     f.push('wrap.tasks = wrap.tasks || [];');
550                     f.push('wrap.tasks.push(setTimeout(function(){');
551                 }
552
553                 // finally call the actual handler fn
554                 f.push('fn.call(scope || dom, e, t, options);');
555
556                 if(options.single) {
557                     f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
558                 }
559
560                 if(options.delay) {
561                     f.push('}, ' + options.delay + '));');
562                 }
563
564                 if(options.buffer) {
565                     f.push('}, ' + options.buffer + ');');
566                 }
567
568                 gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
569             }
570
571             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
572         };
573     },
574
575     /**
576      * Get the event cache for a particular element for a particular event
577      * @private
578      * @param {HTMLElement} element The element
579      * @param {Object} eventName The event name
580      * @return {Array} The events for the element
581      */
582     getEventListenerCache : function(element, eventName) {
583         if (!element) {
584             return [];
585         }
586
587         var eventCache = this.getElementEventCache(element);
588         return eventCache[eventName] || (eventCache[eventName] = []);
589     },
590
591     /**
592      * Gets the event cache for the object
593      * @private
594      * @param {HTMLElement} element The element
595      * @return {Object} The event cache for the object
596      */
597     getElementEventCache : function(element) {
598         if (!element) {
599             return {};
600         }
601         var elementCache = Ext.cache[this.getId(element)];
602         return elementCache.events || (elementCache.events = {});
603     },
604
605     // --------------------- utility methods ---------------------
606     mouseLeaveRe: /(mouseout|mouseleave)/,
607     mouseEnterRe: /(mouseover|mouseenter)/,
608
609     /**
610      * Stop the event (preventDefault and stopPropagation)
611      * @param {Event} The event to stop
612      */
613     stopEvent: function(event) {
614         this.stopPropagation(event);
615         this.preventDefault(event);
616     },
617
618     /**
619      * Cancels bubbling of the event.
620      * @param {Event} The event to stop bubbling.
621      */
622     stopPropagation: function(event) {
623         event = event.browserEvent || event;
624         if (event.stopPropagation) {
625             event.stopPropagation();
626         } else {
627             event.cancelBubble = true;
628         }
629     },
630
631     /**
632      * Prevents the browsers default handling of the event.
633      * @param {Event} The event to prevent the default
634      */
635     preventDefault: function(event) {
636         event = event.browserEvent || event;
637         if (event.preventDefault) {
638             event.preventDefault();
639         } else {
640             event.returnValue = false;
641             // Some keys events require setting the keyCode to -1 to be prevented
642             try {
643               // all ctrl + X and F1 -> F12
644               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
645                   event.keyCode = -1;
646               }
647             } catch (e) {
648                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
649             }
650         }
651     },
652
653     /**
654      * Gets the related target from the event.
655      * @param {Object} event The event
656      * @return {HTMLElement} The related target.
657      */
658     getRelatedTarget: function(event) {
659         event = event.browserEvent || event;
660         var target = event.relatedTarget;
661         if (!target) {
662             if (this.mouseLeaveRe.test(event.type)) {
663                 target = event.toElement;
664             } else if (this.mouseEnterRe.test(event.type)) {
665                 target = event.fromElement;
666             }
667         }
668         return this.resolveTextNode(target);
669     },
670
671     /**
672      * Gets the x coordinate from the event
673      * @param {Object} event The event
674      * @return {Number} The x coordinate
675      */
676     getPageX: function(event) {
677         return this.getXY(event)[0];
678     },
679
680     /**
681      * Gets the y coordinate from the event
682      * @param {Object} event The event
683      * @return {Number} The y coordinate
684      */
685     getPageY: function(event) {
686         return this.getXY(event)[1];
687     },
688
689     /**
690      * Gets the x & y coordinate from the event
691      * @param {Object} event The event
692      * @return {Number[]} The x/y coordinate
693      */
694     getPageXY: function(event) {
695         event = event.browserEvent || event;
696         var x = event.pageX,
697             y = event.pageY,
698             doc = document.documentElement,
699             body = document.body;
700
701         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
702         if (!x && x !== 0) {
703             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
704             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
705         }
706         return [x, y];
707     },
708
709     /**
710      * Gets the target of the event.
711      * @param {Object} event The event
712      * @return {HTMLElement} target
713      */
714     getTarget: function(event) {
715         event = event.browserEvent || event;
716         return this.resolveTextNode(event.target || event.srcElement);
717     },
718
719     /**
720      * Resolve any text nodes accounting for browser differences.
721      * @private
722      * @param {HTMLElement} node The node
723      * @return {HTMLElement} The resolved node
724      */
725     // technically no need to browser sniff this, however it makes no sense to check this every time, for every event, whether the string is equal.
726     resolveTextNode: Ext.isGecko ?
727         function(node) {
728             if (!node) {
729                 return;
730             }
731             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
732             var s = HTMLElement.prototype.toString.call(node);
733             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
734                 return;
735             }
736                 return node.nodeType == 3 ? node.parentNode: node;
737             }: function(node) {
738                 return node && node.nodeType == 3 ? node.parentNode: node;
739             },
740
741     // --------------------- custom event binding ---------------------
742
743     // Keep track of the current width/height
744     curWidth: 0,
745     curHeight: 0,
746
747     /**
748      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
749      * passes new viewport width and height to handlers.
750      * @param {Function} fn      The handler function the window resize event invokes.
751      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
752      * @param {Boolean}  options Options object as passed to {@link Ext.Element#addListener}
753      */
754     onWindowResize: function(fn, scope, options){
755         var resize = this.resizeEvent;
756         if(!resize){
757             this.resizeEvent = resize = new Ext.util.Event();
758             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
759         }
760         resize.addListener(fn, scope, options);
761     },
762
763     /**
764      * Fire the resize event.
765      * @private
766      */
767     fireResize: function(){
768         var me = this,
769             w = Ext.Element.getViewWidth(),
770             h = Ext.Element.getViewHeight();
771
772          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
773          if(me.curHeight != h || me.curWidth != w){
774              me.curHeight = h;
775              me.curWidth = w;
776              me.resizeEvent.fire(w, h);
777          }
778     },
779
780     /**
781      * Removes the passed window resize listener.
782      * @param {Function} fn        The method the event invokes
783      * @param {Object}   scope    The scope of handler
784      */
785     removeResizeListener: function(fn, scope){
786         if (this.resizeEvent) {
787             this.resizeEvent.removeListener(fn, scope);
788         }
789     },
790
791     onWindowUnload: function() {
792         var unload = this.unloadEvent;
793         if (!unload) {
794             this.unloadEvent = unload = new Ext.util.Event();
795             this.addListener(window, 'unload', this.fireUnload, this);
796         }
797     },
798
799     /**
800      * Fires the unload event for items bound with onWindowUnload
801      * @private
802      */
803     fireUnload: function() {
804         // wrap in a try catch, could have some problems during unload
805         try {
806             this.removeUnloadListener();
807             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
808             if (Ext.isGecko3) {
809                 var gridviews = Ext.ComponentQuery.query('gridview'),
810                     i = 0,
811                     ln = gridviews.length;
812                 for (; i < ln; i++) {
813                     gridviews[i].scrollToTop();
814                 }
815             }
816             // Purge all elements in the cache
817             var el,
818                 cache = Ext.cache;
819             for (el in cache) {
820                 if (cache.hasOwnProperty(el)) {
821                     Ext.EventManager.removeAll(el);
822                 }
823             }
824         } catch(e) {
825         }
826     },
827
828     /**
829      * Removes the passed window unload listener.
830      * @param {Function} fn        The method the event invokes
831      * @param {Object}   scope    The scope of handler
832      */
833     removeUnloadListener: function(){
834         if (this.unloadEvent) {
835             this.removeListener(window, 'unload', this.fireUnload);
836         }
837     },
838
839     /**
840      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
841      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
842      * (research done by Jan Wolter at http://unixpapa.com/js/key.html)
843      * @private
844      */
845     useKeyDown: Ext.isWebKit ?
846                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
847                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
848
849     /**
850      * Indicates which event to use for getting key presses.
851      * @return {String} The appropriate event name.
852      */
853     getKeyEvent: function(){
854         return this.useKeyDown ? 'keydown' : 'keypress';
855     }
856 };
857
858 /**
859  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
860  * @member Ext
861  * @method onReady
862  */
863 Ext.onReady = function(fn, scope, options) {
864     Ext.Loader.onReady(fn, scope, true, options);
865 };
866
867 /**
868  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
869  * @member Ext
870  * @method onDocumentReady
871  */
872 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
873
874 /**
875  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
876  * @member Ext.EventManager
877  * @method on
878  */
879 Ext.EventManager.on = Ext.EventManager.addListener;
880
881 /**
882  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
883  * @member Ext.EventManager
884  * @method un
885  */
886 Ext.EventManager.un = Ext.EventManager.removeListener;
887
888 (function(){
889     var initExtCss = function() {
890         // find the body element
891         var bd = document.body || document.getElementsByTagName('body')[0],
892             baseCSSPrefix = Ext.baseCSSPrefix,
893             cls = [baseCSSPrefix + 'body'],
894             htmlCls = [],
895             html;
896
897         if (!bd) {
898             return false;
899         }
900
901         html = bd.parentNode;
902
903         function add (c) {
904             cls.push(baseCSSPrefix + c);
905         }
906
907         //Let's keep this human readable!
908         if (Ext.isIE) {
909             add('ie');
910
911             // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
912             // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
913             // additional classes:
914             //
915             //      x-ie7p      : IE7+      :  7 <= ieVer
916             //      x-ie7m      : IE7-      :  ieVer <= 7
917             //      x-ie8p      : IE8+      :  8 <= ieVer
918             //      x-ie8m      : IE8-      :  ieVer <= 8
919             //      x-ie9p      : IE9+      :  9 <= ieVer
920             //      x-ie78      : IE7 or 8  :  7 <= ieVer <= 8
921             //
922             if (Ext.isIE6) {
923                 add('ie6');
924             } else { // ignore pre-IE6 :)
925                 add('ie7p');
926
927                 if (Ext.isIE7) {
928                     add('ie7');
929                 } else {
930                     add('ie8p');
931
932                     if (Ext.isIE8) {
933                         add('ie8');
934                     } else {
935                         add('ie9p');
936
937                         if (Ext.isIE9) {
938                             add('ie9');
939                         }
940                     }
941                 }
942             }
943
944             if (Ext.isIE6 || Ext.isIE7) {
945                 add('ie7m');
946             }
947             if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
948                 add('ie8m');
949             }
950             if (Ext.isIE7 || Ext.isIE8) {
951                 add('ie78');
952             }
953         }
954         if (Ext.isGecko) {
955             add('gecko');
956             if (Ext.isGecko3) {
957                 add('gecko3');
958             }
959             if (Ext.isGecko4) {
960                 add('gecko4');
961             }
962             if (Ext.isGecko5) {
963                 add('gecko5');
964             }
965         }
966         if (Ext.isOpera) {
967             add('opera');
968         }
969         if (Ext.isWebKit) {
970             add('webkit');
971         }
972         if (Ext.isSafari) {
973             add('safari');
974             if (Ext.isSafari2) {
975                 add('safari2');
976             }
977             if (Ext.isSafari3) {
978                 add('safari3');
979             }
980             if (Ext.isSafari4) {
981                 add('safari4');
982             }
983             if (Ext.isSafari5) {
984                 add('safari5');
985             }
986         }
987         if (Ext.isChrome) {
988             add('chrome');
989         }
990         if (Ext.isMac) {
991             add('mac');
992         }
993         if (Ext.isLinux) {
994             add('linux');
995         }
996         if (!Ext.supports.CSS3BorderRadius) {
997             add('nbr');
998         }
999         if (!Ext.supports.CSS3LinearGradient) {
1000             add('nlg');
1001         }
1002         if (!Ext.scopeResetCSS) {
1003             add('reset');
1004         }
1005
1006         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
1007         if (html) {
1008             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
1009                 Ext.isBorderBox = false;
1010             }
1011             else {
1012                 Ext.isBorderBox = true;
1013             }
1014
1015             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
1016             if (!Ext.isStrict) {
1017                 htmlCls.push(baseCSSPrefix + 'quirks');
1018             }
1019             Ext.fly(html, '_internal').addCls(htmlCls);
1020         }
1021
1022         Ext.fly(bd, '_internal').addCls(cls);
1023         return true;
1024     };
1025
1026     Ext.onReady(initExtCss);
1027 })();
1028