Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / ext-core / src / core / EventManager.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /**
8  * @class Ext.EventManager
9  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
10  * several useful events directly.
11  * See {@link Ext.EventObject} for more details on normalized event objects.
12  * @singleton
13  */
14 Ext.EventManager = function(){
15     var docReadyEvent,
16         docReadyProcId,
17         docReadyState = false,
18         DETECT_NATIVE = Ext.isGecko || Ext.isWebKit || Ext.isSafari,
19         E = Ext.lib.Event,
20         D = Ext.lib.Dom,
21         DOC = document,
22         WINDOW = window,
23         DOMCONTENTLOADED = "DOMContentLoaded",
24         COMPLETE = 'complete',
25         propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
26         /*
27          * This cache is used to hold special js objects, the document and window, that don't have an id. We need to keep
28          * a reference to them so we can look them up at a later point.
29          */
30         specialElCache = [];
31
32      function getId(el){
33         var id = false,
34             i = 0,
35             len = specialElCache.length,
36             skip = false,
37             o;
38             
39         if (el) {
40             if (el.getElementById || el.navigator) {
41                 // look up the id
42                 for(; i < len; ++i){
43                     o = specialElCache[i];
44                     if(o.el === el){
45                         id = o.id;
46                         break;
47                     }
48                 }
49                 if(!id){
50                     // for browsers that support it, ensure that give the el the same id
51                     id = Ext.id(el);
52                     specialElCache.push({
53                         id: id,
54                         el: el
55                     });
56                     skip = true;
57                 }
58             }else{
59                 id = Ext.id(el);
60             }
61             if(!Ext.elCache[id]){
62                 Ext.Element.addToCache(new Ext.Element(el), id);
63                 if(skip){
64                     Ext.elCache[id].skipGC = true;
65                 }
66             }
67         }
68         return id;
69      }
70
71     /// There is some jquery work around stuff here that isn't needed in Ext Core.
72     function addListener(el, ename, fn, task, wrap, scope){
73         el = Ext.getDom(el);
74         var id = getId(el),
75             es = Ext.elCache[id].events,
76             wfn;
77
78         wfn = E.on(el, ename, wrap);
79         es[ename] = es[ename] || [];
80
81         /* 0 = Original Function,
82            1 = Event Manager Wrapped Function,
83            2 = Scope,
84            3 = Adapter Wrapped Function,
85            4 = Buffered Task
86         */
87         es[ename].push([fn, wrap, scope, wfn, task]);
88
89         // this is a workaround for jQuery and should somehow be removed from Ext Core in the future
90         // without breaking ExtJS.
91
92         // workaround for jQuery
93         if(el.addEventListener && ename == "mousewheel"){
94             var args = ["DOMMouseScroll", wrap, false];
95             el.addEventListener.apply(el, args);
96             Ext.EventManager.addListener(WINDOW, 'unload', function(){
97                 el.removeEventListener.apply(el, args);
98             });
99         }
100
101         // fix stopped mousedowns on the document
102         if(el == DOC && ename == "mousedown"){
103             Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);
104         }
105     }
106
107     function doScrollChk(){
108         /* Notes:
109              'doScroll' will NOT work in a IFRAME/FRAMESET.
110              The method succeeds but, a DOM query done immediately after -- FAILS.
111           */
112         if(window != top){
113             return false;
114         }
115
116         try{
117             DOC.documentElement.doScroll('left');
118         }catch(e){
119              return false;
120         }
121
122         fireDocReady();
123         return true;
124     }
125     /**
126      * @return {Boolean} True if the document is in a 'complete' state (or was determined to
127      * be true by other means). If false, the state is evaluated again until canceled.
128      */
129     function checkReadyState(e){
130
131         if(Ext.isIE && doScrollChk()){
132             return true;
133         }
134         if(DOC.readyState == COMPLETE){
135             fireDocReady();
136             return true;
137         }
138         docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
139         return false;
140     }
141
142     var styles;
143     function checkStyleSheets(e){
144         styles || (styles = Ext.query('style, link[rel=stylesheet]'));
145         if(styles.length == DOC.styleSheets.length){
146             fireDocReady();
147             return true;
148         }
149         docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
150         return false;
151     }
152
153     function OperaDOMContentLoaded(e){
154         DOC.removeEventListener(DOMCONTENTLOADED, arguments.callee, false);
155         checkStyleSheets();
156     }
157
158     function fireDocReady(e){
159         if(!docReadyState){
160             docReadyState = true; //only attempt listener removal once
161
162             if(docReadyProcId){
163                 clearTimeout(docReadyProcId);
164             }
165             if(DETECT_NATIVE) {
166                 DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
167             }
168             if(Ext.isIE && checkReadyState.bindIE){  //was this was actually set ??
169                 DOC.detachEvent('onreadystatechange', checkReadyState);
170             }
171             E.un(WINDOW, "load", arguments.callee);
172         }
173         if(docReadyEvent && !Ext.isReady){
174             Ext.isReady = true;
175             docReadyEvent.fire();
176             docReadyEvent.listeners = [];
177         }
178
179     }
180
181     function initDocReady(){
182         docReadyEvent || (docReadyEvent = new Ext.util.Event());
183         if (DETECT_NATIVE) {
184             DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
185         }
186         /*
187          * Handle additional (exceptional) detection strategies here
188          */
189         if (Ext.isIE){
190             //Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME
191             //See if page is already loaded
192             if(!checkReadyState()){
193                 checkReadyState.bindIE = true;
194                 DOC.attachEvent('onreadystatechange', checkReadyState);
195             }
196
197         }else if(Ext.isOpera ){
198             /* Notes:
199                Opera needs special treatment needed here because CSS rules are NOT QUITE
200                available after DOMContentLoaded is raised.
201             */
202
203             //See if page is already loaded and all styleSheets are in place
204             (DOC.readyState == COMPLETE && checkStyleSheets()) ||
205                 DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
206
207         }else if (Ext.isWebKit){
208             //Fallback for older Webkits without DOMCONTENTLOADED support
209             checkReadyState();
210         }
211         // no matter what, make sure it fires on load
212         E.on(WINDOW, "load", fireDocReady);
213     }
214
215     function createTargeted(h, o){
216         return function(){
217             var args = Ext.toArray(arguments);
218             if(o.target == Ext.EventObject.setEvent(args[0]).target){
219                 h.apply(this, args);
220             }
221         };
222     }
223
224     function createBuffered(h, o, task){
225         return function(e){
226             // create new event object impl so new events don't wipe out properties
227             task.delay(o.buffer, h, null, [new Ext.EventObjectImpl(e)]);
228         };
229     }
230
231     function createSingle(h, el, ename, fn, scope){
232         return function(e){
233             Ext.EventManager.removeListener(el, ename, fn, scope);
234             h(e);
235         };
236     }
237
238     function createDelayed(h, o, fn){
239         return function(e){
240             var task = new Ext.util.DelayedTask(h);
241             if(!fn.tasks) {
242                 fn.tasks = [];
243             }
244             fn.tasks.push(task);
245             task.delay(o.delay || 10, h, null, [new Ext.EventObjectImpl(e)]);
246         };
247     }
248
249     function listen(element, ename, opt, fn, scope){
250         var o = (!opt || typeof opt == "boolean") ? {} : opt,
251             el = Ext.getDom(element), task;
252
253         fn = fn || o.fn;
254         scope = scope || o.scope;
255
256         if(!el){
257             throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';
258         }
259         function h(e){
260             // prevent errors while unload occurring
261             if(!Ext){// !window[xname]){  ==> can't we do this?
262                 return;
263             }
264             e = Ext.EventObject.setEvent(e);
265             var t;
266             if (o.delegate) {
267                 if(!(t = e.getTarget(o.delegate, el))){
268                     return;
269                 }
270             } else {
271                 t = e.target;
272             }
273             if (o.stopEvent) {
274                 e.stopEvent();
275             }
276             if (o.preventDefault) {
277                e.preventDefault();
278             }
279             if (o.stopPropagation) {
280                 e.stopPropagation();
281             }
282             if (o.normalized === false) {
283                 e = e.browserEvent;
284             }
285
286             fn.call(scope || el, e, t, o);
287         }
288         if(o.target){
289             h = createTargeted(h, o);
290         }
291         if(o.delay){
292             h = createDelayed(h, o, fn);
293         }
294         if(o.single){
295             h = createSingle(h, el, ename, fn, scope);
296         }
297         if(o.buffer){
298             task = new Ext.util.DelayedTask(h);
299             h = createBuffered(h, o, task);
300         }
301
302         addListener(el, ename, fn, task, h, scope);
303         return h;
304     }
305
306     var pub = {
307         /**
308          * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
309          * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
310          * @param {String/HTMLElement} el The html element or id to assign the event handler to.
311          * @param {String} eventName The name of the event to listen for.
312          * @param {Function} handler The handler function the event invokes. This function is passed
313          * the following parameters:<ul>
314          * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
315          * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
316          * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
317          * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
318          * </ul>
319          * @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>.
320          * @param {Object} options (optional) An object containing handler configuration properties.
321          * This may contain any of the following properties:<ul>
322          * <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>
323          * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
324          * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
325          * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
326          * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
327          * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
328          * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
329          * <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>
330          * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
331          * by the specified number of milliseconds. If the event fires again within that time, the original
332          * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
333          * <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>
334          * </ul><br>
335          * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
336          */
337         addListener : function(element, eventName, fn, scope, options){
338             if(typeof eventName == 'object'){
339                 var o = eventName, e, val;
340                 for(e in o){
341                     val = o[e];
342                     if(!propRe.test(e)){
343                         if(Ext.isFunction(val)){
344                             // shared options
345                             listen(element, e, o, val, o.scope);
346                         }else{
347                             // individual options
348                             listen(element, e, val);
349                         }
350                     }
351                 }
352             } else {
353                 listen(element, eventName, options, fn, scope);
354             }
355         },
356
357         /**
358          * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
359          * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
360          * @param {String/HTMLElement} el The id or html element from which to remove the listener.
361          * @param {String} eventName The name of the event.
362          * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
363          * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
364          * then this must refer to the same object.
365          */
366         removeListener : function(el, eventName, fn, scope){
367             el = Ext.getDom(el);
368             var id = getId(el),
369                 f = el && (Ext.elCache[id].events)[eventName] || [],
370                 wrap, i, l, k, len, fnc;
371
372             for (i = 0, len = f.length; i < len; i++) {
373
374                 /* 0 = Original Function,
375                    1 = Event Manager Wrapped Function,
376                    2 = Scope,
377                    3 = Adapter Wrapped Function,
378                    4 = Buffered Task
379                 */
380                 if (Ext.isArray(fnc = f[i]) && fnc[0] == fn && (!scope || fnc[2] == scope)) {
381                     if(fnc[4]) {
382                         fnc[4].cancel();
383                     }
384                     k = fn.tasks && fn.tasks.length;
385                     if(k) {
386                         while(k--) {
387                             fn.tasks[k].cancel();
388                         }
389                         delete fn.tasks;
390                     }
391                     wrap = fnc[1];
392                     E.un(el, eventName, E.extAdapter ? fnc[3] : wrap);
393
394                     // jQuery workaround that should be removed from Ext Core
395                     if(wrap && el.addEventListener && eventName == "mousewheel"){
396                         el.removeEventListener("DOMMouseScroll", wrap, false);
397                     }
398
399                     // fix stopped mousedowns on the document
400                     if(wrap && el == DOC && eventName == "mousedown"){
401                         Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
402                     }
403
404                     f.splice(i, 1);
405                     if (f.length === 0) {
406                         delete Ext.elCache[id].events[eventName];
407                     }
408                     for (k in Ext.elCache[id].events) {
409                         return false;
410                     }
411                     Ext.elCache[id].events = {};
412                     return false;
413                 }
414             }
415         },
416
417         /**
418          * Removes all event handers from an element.  Typically you will use {@link Ext.Element#removeAllListeners}
419          * directly on an Element in favor of calling this version.
420          * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
421          */
422         removeAll : function(el){
423             el = Ext.getDom(el);
424             var id = getId(el),
425                 ec = Ext.elCache[id] || {},
426                 es = ec.events || {},
427                 f, i, len, ename, fn, k, wrap;
428
429             for(ename in es){
430                 if(es.hasOwnProperty(ename)){
431                     f = es[ename];
432                     /* 0 = Original Function,
433                        1 = Event Manager Wrapped Function,
434                        2 = Scope,
435                        3 = Adapter Wrapped Function,
436                        4 = Buffered Task
437                     */
438                     for (i = 0, len = f.length; i < len; i++) {
439                         fn = f[i];
440                         if(fn[4]) {
441                             fn[4].cancel();
442                         }
443                         if(fn[0].tasks && (k = fn[0].tasks.length)) {
444                             while(k--) {
445                                 fn[0].tasks[k].cancel();
446                             }
447                             delete fn.tasks;
448                         }
449                         wrap =  fn[1];
450                         E.un(el, ename, E.extAdapter ? fn[3] : wrap);
451
452                         // jQuery workaround that should be removed from Ext Core
453                         if(el.addEventListener && wrap && ename == "mousewheel"){
454                             el.removeEventListener("DOMMouseScroll", wrap, false);
455                         }
456
457                         // fix stopped mousedowns on the document
458                         if(wrap && el == DOC &&  ename == "mousedown"){
459                             Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
460                         }
461                     }
462                 }
463             }
464             if (Ext.elCache[id]) {
465                 Ext.elCache[id].events = {};
466             }
467         },
468
469         getListeners : function(el, eventName) {
470             el = Ext.getDom(el);
471             var id = getId(el),
472                 ec = Ext.elCache[id] || {},
473                 es = ec.events || {},
474                 results = [];
475             if (es && es[eventName]) {
476                 return es[eventName];
477             } else {
478                 return null;
479             }
480         },
481
482         purgeElement : function(el, recurse, eventName) {
483             el = Ext.getDom(el);
484             var id = getId(el),
485                 ec = Ext.elCache[id] || {},
486                 es = ec.events || {},
487                 i, f, len;
488             if (eventName) {
489                 if (es && es.hasOwnProperty(eventName)) {
490                     f = es[eventName];
491                     for (i = 0, len = f.length; i < len; i++) {
492                         Ext.EventManager.removeListener(el, eventName, f[i][0]);
493                     }
494                 }
495             } else {
496                 Ext.EventManager.removeAll(el);
497             }
498             if (recurse && el && el.childNodes) {
499                 for (i = 0, len = el.childNodes.length; i < len; i++) {
500                     Ext.EventManager.purgeElement(el.childNodes[i], recurse, eventName);
501                 }
502             }
503         },
504
505         _unload : function() {
506             var el;
507             for (el in Ext.elCache) {
508                 Ext.EventManager.removeAll(el);
509             }
510             delete Ext.elCache;
511             delete Ext.Element._flyweights;
512
513             // Abort any outstanding Ajax requests
514             var c,
515                 conn,
516                 tid,
517                 ajax = Ext.lib.Ajax;
518             (typeof ajax.conn == 'object') ? conn = ajax.conn : conn = {};
519             for (tid in conn) {
520                 c = conn[tid];
521                 if (c) {
522                     ajax.abort({conn: c, tId: tid});
523                 }
524             }
525         },
526         /**
527          * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
528          * accessed shorthanded as Ext.onReady().
529          * @param {Function} fn The method the event invokes.
530          * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
531          * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
532          * <code>{single: true}</code> be used so that the handler is removed on first invocation.
533          */
534         onDocumentReady : function(fn, scope, options){
535             if (Ext.isReady) { // if it already fired or document.body is present
536                 docReadyEvent || (docReadyEvent = new Ext.util.Event());
537                 docReadyEvent.addListener(fn, scope, options);
538                 docReadyEvent.fire();
539                 docReadyEvent.listeners = [];
540             } else {
541                 if (!docReadyEvent) {
542                     initDocReady();
543                 }
544                 options = options || {};
545                 options.delay = options.delay || 1;
546                 docReadyEvent.addListener(fn, scope, options);
547             }
548         },
549
550         /**
551          * Forces a document ready state transition for the framework.  Used when Ext is loaded
552          * into a DOM structure AFTER initial page load (Google API or other dynamic load scenario.
553          * Any pending 'onDocumentReady' handlers will be fired (if not already handled).
554          */
555         fireDocReady  : fireDocReady
556     };
557      /**
558      * Appends an event handler to an element.  Shorthand for {@link #addListener}.
559      * @param {String/HTMLElement} el The html element or id to assign the event handler to
560      * @param {String} eventName The name of the event to listen for.
561      * @param {Function} handler The handler function the event invokes.
562      * @param {Object} scope (optional) (<code>this</code> reference) in which the handler function executes. <b>Defaults to the Element</b>.
563      * @param {Object} options (optional) An object containing standard {@link #addListener} options
564      * @member Ext.EventManager
565      * @method on
566      */
567     pub.on = pub.addListener;
568     /**
569      * Removes an event handler from an element.  Shorthand for {@link #removeListener}.
570      * @param {String/HTMLElement} el The id or html element from which to remove the listener.
571      * @param {String} eventName The name of the event.
572      * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #on} call.</b>
573      * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
574      * then this must refer to the same object.
575      * @member Ext.EventManager
576      * @method un
577      */
578     pub.un = pub.removeListener;
579
580     pub.stoppedMouseDownEvent = new Ext.util.Event();
581     return pub;
582 }();
583 /**
584   * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}.
585   * @param {Function} fn The method the event invokes.
586   * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
587   * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
588   * <code>{single: true}</code> be used so that the handler is removed on first invocation.
589   * @member Ext
590   * @method onReady
591  */
592 Ext.onReady = Ext.EventManager.onDocumentReady;
593
594
595 //Initialize doc classes
596 (function(){
597     var initExtCss = function() {
598         // find the body element
599         var bd = document.body || document.getElementsByTagName('body')[0];
600         if (!bd) {
601             return false;
602         }
603         
604         var cls = [' ',
605                 Ext.isIE ? "ext-ie " + (Ext.isIE6 ? 'ext-ie6' : (Ext.isIE7 ? 'ext-ie7' : 'ext-ie8'))
606                 : Ext.isGecko ? "ext-gecko " + (Ext.isGecko2 ? 'ext-gecko2' : 'ext-gecko3')
607                 : Ext.isOpera ? "ext-opera"
608                 : Ext.isWebKit ? "ext-webkit" : ""];
609
610         if (Ext.isSafari) {
611             cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
612         } else if(Ext.isChrome) {
613             cls.push("ext-chrome");
614         }
615
616         if (Ext.isMac) {
617             cls.push("ext-mac");
618         }
619         if (Ext.isLinux) {
620             cls.push("ext-linux");
621         }
622
623         // add to the parent to allow for selectors like ".ext-strict .ext-ie"
624         if (Ext.isStrict || Ext.isBorderBox) {
625             var p = bd.parentNode;
626             if (p) {
627                 Ext.fly(p, '_internal').addClass(((Ext.isStrict && Ext.isIE ) || (!Ext.enableForcedBoxModel && !Ext.isIE)) ? ' ext-strict' : ' ext-border-box');
628             }
629         }
630         // Forced border box model class applied to all elements. Bypassing javascript based box model adjustments
631         // in favor of css.  This is for non-IE browsers.
632         if (Ext.enableForcedBoxModel && !Ext.isIE) {
633             Ext.isForcedBorderBox = true;
634             cls.push("ext-forced-border-box");
635         }
636         
637         Ext.fly(bd, '_internal').addClass(cls);
638         return true;
639     };
640     
641     if (!initExtCss()) {
642         Ext.onReady(initExtCss);
643     }
644 })();
645
646 /**
647  * Code used to detect certain browser feature/quirks/bugs at startup.
648  */
649 (function(){
650     var supports = Ext.apply(Ext.supports, {
651         /**
652          * In Webkit, there is an issue with getting the margin right property, see
653          * https://bugs.webkit.org/show_bug.cgi?id=13343
654          */
655         correctRightMargin: true,
656         
657         /**
658          * Webkit browsers return rgba(0, 0, 0) when a transparent color is used
659          */
660         correctTransparentColor: true,
661         
662         /**
663          * IE uses styleFloat, not cssFloat for the float property.
664          */
665         cssFloat: true
666     });
667     
668     var supportTests = function(){
669             var div = document.createElement('div'),
670                 doc = document,
671                 view,
672                 last;
673                 
674             div.innerHTML = '<div style="height:30px;width:50px;"><div style="height:20px;width:20px;"></div></div><div style="float:left;background-color:transparent;">';
675             doc.body.appendChild(div);
676             last = div.lastChild;
677             
678             if((view = doc.defaultView)){
679                 if(view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px'){
680                     supports.correctRightMargin = false;
681                 }
682                 if(view.getComputedStyle(last, null).backgroundColor != 'transparent'){
683                     supports.correctTransparentColor = false;
684                 }
685             }
686             supports.cssFloat = !!last.style.cssFloat;
687             doc.body.removeChild(div);
688     };
689     
690     if (Ext.isReady) {
691         supportTests();    
692     } else {
693         Ext.onReady(supportTests);
694     }
695 })();
696
697
698 /**
699  * @class Ext.EventObject
700  * Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
701  * wraps the browser's native event-object normalizing cross-browser differences,
702  * such as which mouse button is clicked, keys pressed, mechanisms to stop
703  * event-propagation along with a method to prevent default actions from taking place.
704  * <p>For example:</p>
705  * <pre><code>
706 function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
707     e.preventDefault();
708     var target = e.getTarget(); // same as t (the target HTMLElement)
709     ...
710 }
711 var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.Element}
712 myDiv.on(         // 'on' is shorthand for addListener
713     "click",      // perform an action on click of myDiv
714     handleClick   // reference to the action handler
715 );
716 // other methods to do the same:
717 Ext.EventManager.on("myDiv", 'click', handleClick);
718 Ext.EventManager.addListener("myDiv", 'click', handleClick);
719  </code></pre>
720  * @singleton
721  */
722 Ext.EventObject = function(){
723     var E = Ext.lib.Event,
724         clickRe = /(dbl)?click/,
725         // safari keypress events for special keys return bad keycodes
726         safariKeys = {
727             3 : 13, // enter
728             63234 : 37, // left
729             63235 : 39, // right
730             63232 : 38, // up
731             63233 : 40, // down
732             63276 : 33, // page up
733             63277 : 34, // page down
734             63272 : 46, // delete
735             63273 : 36, // home
736             63275 : 35  // end
737         },
738         // normalize button clicks
739         btnMap = Ext.isIE ? {1:0,4:1,2:2} : {0:0,1:1,2:2};
740
741     Ext.EventObjectImpl = function(e){
742         if(e){
743             this.setEvent(e.browserEvent || e);
744         }
745     };
746
747     Ext.EventObjectImpl.prototype = {
748            /** @private */
749         setEvent : function(e){
750             var me = this;
751             if(e == me || (e && e.browserEvent)){ // already wrapped
752                 return e;
753             }
754             me.browserEvent = e;
755             if(e){
756                 // normalize buttons
757                 me.button = e.button ? btnMap[e.button] : (e.which ? e.which - 1 : -1);
758                 if(clickRe.test(e.type) && me.button == -1){
759                     me.button = 0;
760                 }
761                 me.type = e.type;
762                 me.shiftKey = e.shiftKey;
763                 // mac metaKey behaves like ctrlKey
764                 me.ctrlKey = e.ctrlKey || e.metaKey || false;
765                 me.altKey = e.altKey;
766                 // in getKey these will be normalized for the mac
767                 me.keyCode = e.keyCode;
768                 me.charCode = e.charCode;
769                 // cache the target for the delayed and or buffered events
770                 me.target = E.getTarget(e);
771                 // same for XY
772                 me.xy = E.getXY(e);
773             }else{
774                 me.button = -1;
775                 me.shiftKey = false;
776                 me.ctrlKey = false;
777                 me.altKey = false;
778                 me.keyCode = 0;
779                 me.charCode = 0;
780                 me.target = null;
781                 me.xy = [0, 0];
782             }
783             return me;
784         },
785
786         /**
787          * Stop the event (preventDefault and stopPropagation)
788          */
789         stopEvent : function(){
790             var me = this;
791             if(me.browserEvent){
792                 if(me.browserEvent.type == 'mousedown'){
793                     Ext.EventManager.stoppedMouseDownEvent.fire(me);
794                 }
795                 E.stopEvent(me.browserEvent);
796             }
797         },
798
799         /**
800          * Prevents the browsers default handling of the event.
801          */
802         preventDefault : function(){
803             if(this.browserEvent){
804                 E.preventDefault(this.browserEvent);
805             }
806         },
807
808         /**
809          * Cancels bubbling of the event.
810          */
811         stopPropagation : function(){
812             var me = this;
813             if(me.browserEvent){
814                 if(me.browserEvent.type == 'mousedown'){
815                     Ext.EventManager.stoppedMouseDownEvent.fire(me);
816                 }
817                 E.stopPropagation(me.browserEvent);
818             }
819         },
820
821         /**
822          * Gets the character code for the event.
823          * @return {Number}
824          */
825         getCharCode : function(){
826             return this.charCode || this.keyCode;
827         },
828
829         /**
830          * Returns a normalized keyCode for the event.
831          * @return {Number} The key code
832          */
833         getKey : function(){
834             return this.normalizeKey(this.keyCode || this.charCode);
835         },
836
837         // private
838         normalizeKey: function(k){
839             return Ext.isSafari ? (safariKeys[k] || k) : k;
840         },
841
842         /**
843          * Gets the x coordinate of the event.
844          * @return {Number}
845          */
846         getPageX : function(){
847             return this.xy[0];
848         },
849
850         /**
851          * Gets the y coordinate of the event.
852          * @return {Number}
853          */
854         getPageY : function(){
855             return this.xy[1];
856         },
857
858         /**
859          * Gets the page coordinates of the event.
860          * @return {Array} The xy values like [x, y]
861          */
862         getXY : function(){
863             return this.xy;
864         },
865
866         /**
867          * Gets the target for the event.
868          * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
869          * @param {Number/Mixed} maxDepth (optional) The max depth to
870                 search as a number or element (defaults to 10 || document.body)
871          * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
872          * @return {HTMLelement}
873          */
874         getTarget : function(selector, maxDepth, returnEl){
875             return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
876         },
877
878         /**
879          * Gets the related target.
880          * @return {HTMLElement}
881          */
882         getRelatedTarget : function(){
883             return this.browserEvent ? E.getRelatedTarget(this.browserEvent) : null;
884         },
885
886         /**
887          * Normalizes mouse wheel delta across browsers
888          * @return {Number} The delta
889          */
890         getWheelDelta : function(){
891             var e = this.browserEvent;
892             var delta = 0;
893             if(e.wheelDelta){ /* IE/Opera. */
894                 delta = e.wheelDelta/120;
895             }else if(e.detail){ /* Mozilla case. */
896                 delta = -e.detail/3;
897             }
898             return delta;
899         },
900
901         /**
902         * Returns true if the target of this event is a child of el.  Unless the allowEl parameter is set, it will return false if if the target is el.
903         * Example usage:<pre><code>
904         // Handle click on any child of an element
905         Ext.getBody().on('click', function(e){
906             if(e.within('some-el')){
907                 alert('Clicked on a child of some-el!');
908             }
909         });
910
911         // Handle click directly on an element, ignoring clicks on child nodes
912         Ext.getBody().on('click', function(e,t){
913             if((t.id == 'some-el') && !e.within(t, true)){
914                 alert('Clicked directly on some-el!');
915             }
916         });
917         </code></pre>
918          * @param {Mixed} el The id, DOM element or Ext.Element to check
919          * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
920          * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
921          * @return {Boolean}
922          */
923         within : function(el, related, allowEl){
924             if(el){
925                 var t = this[related ? "getRelatedTarget" : "getTarget"]();
926                 return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t));
927             }
928             return false;
929         }
930      };
931
932     return new Ext.EventObjectImpl();
933 }();