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