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