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