Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / src / adapter / core / ext-base-event.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 Ext.lib.Event = function() {\r
8     var loadComplete = false,\r
9         listeners = [],\r
10         unloadListeners = [],\r
11         retryCount = 0,\r
12         onAvailStack = [],\r
13         _interval,\r
14         locked = false,\r
15         win = window,\r
16         doc = document,\r
17         \r
18         // constants            \r
19         POLL_RETRYS = 200,\r
20         POLL_INTERVAL = 20,\r
21         EL = 0,\r
22         TYPE = 1,\r
23         FN = 2,\r
24         WFN = 3,\r
25         OBJ = 3,\r
26         ADJ_SCOPE = 4,   \r
27         SCROLLLEFT = 'scrollLeft',\r
28         SCROLLTOP = 'scrollTop',\r
29         UNLOAD = 'unload',\r
30         MOUSEOVER = 'mouseover',\r
31         MOUSEOUT = 'mouseout',\r
32         // private\r
33         doAdd = function() {\r
34             var ret;\r
35             if (win.addEventListener) {\r
36                 ret = function(el, eventName, fn, capture) {\r
37                     if (eventName == 'mouseenter') {\r
38                         fn = fn.createInterceptor(checkRelatedTarget);\r
39                         el.addEventListener(MOUSEOVER, fn, (capture));\r
40                     } else if (eventName == 'mouseleave') {\r
41                         fn = fn.createInterceptor(checkRelatedTarget);\r
42                         el.addEventListener(MOUSEOUT, fn, (capture));\r
43                     } else {\r
44                         el.addEventListener(eventName, fn, (capture));\r
45                     }\r
46                     return fn;\r
47                 };\r
48             } else if (win.attachEvent) {\r
49                 ret = function(el, eventName, fn, capture) {\r
50                     el.attachEvent("on" + eventName, fn);\r
51                     return fn;\r
52                 };\r
53             } else {\r
54                 ret = function(){};\r
55             }\r
56             return ret;\r
57         }(),    \r
58         // private\r
59         doRemove = function(){\r
60             var ret;\r
61             if (win.removeEventListener) {\r
62                 ret = function (el, eventName, fn, capture) {\r
63                     if (eventName == 'mouseenter') {\r
64                         eventName = MOUSEOVER;\r
65                     } else if (eventName == 'mouseleave') {\r
66                         eventName = MOUSEOUT;\r
67                     }                        \r
68                     el.removeEventListener(eventName, fn, (capture));\r
69                 };\r
70             } else if (win.detachEvent) {\r
71                 ret = function (el, eventName, fn) {\r
72                     el.detachEvent("on" + eventName, fn);\r
73                 };\r
74             } else {\r
75                 ret = function(){};\r
76             }\r
77             return ret;\r
78         }();        \r
79 \r
80     var isXUL = Ext.isGecko ? function(node){ \r
81         return Object.prototype.toString.call(node) == '[object XULElement]';\r
82     } : function(){};\r
83         \r
84     var isTextNode = Ext.isGecko ? function(node){\r
85         try{\r
86             return node.nodeType == 3;\r
87         }catch(e) {\r
88             return false;\r
89         }\r
90 \r
91     } : function(node){\r
92         return node.nodeType == 3;\r
93     };\r
94         \r
95     function checkRelatedTarget(e) {\r
96         var related = pub.getRelatedTarget(e);\r
97         return !(isXUL(related) || elContains(e.currentTarget,related));\r
98     }\r
99 \r
100     function elContains(parent, child) {\r
101        if(parent && parent.firstChild){  \r
102          while(child) {\r
103             if(child === parent) {\r
104                 return true;\r
105             }\r
106             try {\r
107                 child = child.parentNode;\r
108             } catch(e) {\r
109                 // In FF if you mouseout an text input element\r
110                 // thats inside a div sometimes it randomly throws\r
111                 // Permission denied to get property HTMLDivElement.parentNode\r
112                 // See https://bugzilla.mozilla.org/show_bug.cgi?id=208427\r
113                 \r
114                 return false;\r
115             }                \r
116             if(child && (child.nodeType != 1)) {\r
117                 child = null;\r
118             }\r
119           }\r
120         }\r
121         return false;\r
122     }\r
123 \r
124         \r
125     // private  \r
126     function _getCacheIndex(el, eventName, fn) {\r
127         var index = -1;\r
128         Ext.each(listeners, function (v,i) {\r
129             if(v && v[FN] == fn && v[EL] == el && v[TYPE] == eventName) {\r
130                 index = i;\r
131             }\r
132         });\r
133         return index;\r
134     }\r
135                     \r
136     // private\r
137     function _tryPreloadAttach() {\r
138         var ret = false,                \r
139             notAvail = [],\r
140             element,\r
141             tryAgain = !loadComplete || (retryCount > 0);                       \r
142         \r
143         if (!locked) {\r
144             locked = true;\r
145             \r
146             Ext.each(onAvailStack, function (v,i,a){\r
147                 if(v && (element = doc.getElementById(v.id))){\r
148                     if(!v.checkReady || loadComplete || element.nextSibling || (doc && doc.body)) {\r
149                         element = v.override ? (v.override === true ? v.obj : v.override) : element;\r
150                         v.fn.call(element, v.obj);\r
151                         onAvailStack[i] = null;\r
152                     } else {\r
153                         notAvail.push(v);\r
154                     }\r
155                 }   \r
156             });\r
157 \r
158             retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;\r
159 \r
160             if (tryAgain) { \r
161                 startInterval();\r
162             } else {\r
163                 clearInterval(_interval);\r
164                 _interval = null;\r
165             }\r
166 \r
167             ret = !(locked = false);\r
168         }\r
169         return ret;\r
170     }\r
171     \r
172     // private              \r
173     function startInterval() {            \r
174         if(!_interval){                    \r
175             var callback = function() {\r
176                 _tryPreloadAttach();\r
177             };\r
178             _interval = setInterval(callback, POLL_INTERVAL);\r
179         }\r
180     }\r
181     \r
182     // private \r
183     function getScroll() {\r
184         var dd = doc.documentElement, \r
185             db = doc.body;\r
186         if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){\r
187             return [dd[SCROLLLEFT], dd[SCROLLTOP]];\r
188         }else if(db){\r
189             return [db[SCROLLLEFT], db[SCROLLTOP]];\r
190         }else{\r
191             return [0, 0];\r
192         }\r
193     }\r
194         \r
195     // private\r
196     function getPageCoord (ev, xy) {\r
197         ev = ev.browserEvent || ev;\r
198         var coord  = ev['page' + xy];\r
199         if (!coord && coord !== 0) {\r
200             coord = ev['client' + xy] || 0;\r
201 \r
202             if (Ext.isIE) {\r
203                 coord += getScroll()[xy == "X" ? 0 : 1];\r
204             }\r
205         }\r
206 \r
207         return coord;\r
208     }\r
209 \r
210     var pub =  {\r
211         onAvailable : function(p_id, p_fn, p_obj, p_override) {             \r
212             onAvailStack.push({ \r
213                 id:         p_id,\r
214                 fn:         p_fn,\r
215                 obj:        p_obj,\r
216                 override:   p_override,\r
217                 checkReady: false });\r
218 \r
219             retryCount = POLL_RETRYS;\r
220             startInterval();\r
221         },\r
222 \r
223 \r
224         addListener: function(el, eventName, fn) {\r
225             var ret;                \r
226             el = Ext.getDom(el);                \r
227             if (el && fn) {\r
228                 if (UNLOAD == eventName) {\r
229                     ret = !!(unloadListeners[unloadListeners.length] = [el, eventName, fn]);                    \r
230                 } else {\r
231                     listeners.push([el, eventName, fn, ret = doAdd(el, eventName, fn, false)]);\r
232                 }\r
233             }\r
234             return !!ret;\r
235         },\r
236 \r
237         removeListener: function(el, eventName, fn) {\r
238             var ret = false,\r
239                 index, \r
240                 cacheItem;\r
241 \r
242             el = Ext.getDom(el);\r
243 \r
244             if(!fn) {                   \r
245                 ret = this.purgeElement(el, false, eventName);\r
246             } else if (UNLOAD == eventName) {   \r
247                 Ext.each(unloadListeners, function(v, i, a) {\r
248                     if( v && v[0] == el && v[1] == eventName && v[2] == fn) {\r
249                         unloadListeners.splice(i, 1);\r
250                         ret = true;\r
251                     }\r
252                 });\r
253             } else {    \r
254                 index = arguments[3] || _getCacheIndex(el, eventName, fn);\r
255                 cacheItem = listeners[index];\r
256                 \r
257                 if (el && cacheItem) {\r
258                     doRemove(el, eventName, cacheItem[WFN], false);     \r
259                     cacheItem[WFN] = cacheItem[FN] = null;                       \r
260                     listeners.splice(index, 1);     \r
261                     ret = true;\r
262                 }\r
263             }\r
264             return ret;\r
265         },\r
266 \r
267         getTarget : function(ev) {\r
268             ev = ev.browserEvent || ev;                \r
269             return this.resolveTextNode(ev.target || ev.srcElement);\r
270         },\r
271 \r
272         resolveTextNode : function(node) {\r
273             return node && !isXUL(node) && isTextNode(node) ? node.parentNode : node;\r
274         },\r
275 \r
276         getRelatedTarget : function(ev) {\r
277             ev = ev.browserEvent || ev;\r
278             return this.resolveTextNode(ev.relatedTarget || \r
279                     (ev.type == MOUSEOUT ? ev.toElement :\r
280                      ev.type == MOUSEOVER ? ev.fromElement : null));\r
281         },\r
282         \r
283         getPageX : function(ev) {\r
284             return getPageCoord(ev, "X");\r
285         },\r
286 \r
287         getPageY : function(ev) {\r
288             return getPageCoord(ev, "Y");\r
289         },\r
290 \r
291 \r
292         getXY : function(ev) {                             \r
293             return [this.getPageX(ev), this.getPageY(ev)];\r
294         },\r
295 \r
296 // Is this useful?  Removing to save space unless use case exists.\r
297 //             getTime: function(ev) {\r
298 //                 ev = ev.browserEvent || ev;\r
299 //                 if (!ev.time) {\r
300 //                     var t = new Date().getTime();\r
301 //                     try {\r
302 //                         ev.time = t;\r
303 //                     } catch(ex) {\r
304 //                         return t;\r
305 //                     }\r
306 //                 }\r
307 \r
308 //                 return ev.time;\r
309 //             },\r
310 \r
311         stopEvent : function(ev) {                            \r
312             this.stopPropagation(ev);\r
313             this.preventDefault(ev);\r
314         },\r
315 \r
316         stopPropagation : function(ev) {\r
317             ev = ev.browserEvent || ev;\r
318             if (ev.stopPropagation) {\r
319                 ev.stopPropagation();\r
320             } else {\r
321                 ev.cancelBubble = true;\r
322             }\r
323         },\r
324 \r
325         preventDefault : function(ev) {\r
326             ev = ev.browserEvent || ev;\r
327             if (ev.preventDefault) {\r
328                 ev.preventDefault();\r
329             } else {\r
330                 ev.returnValue = false;\r
331             }\r
332         },\r
333         \r
334         getEvent : function(e) {\r
335             e = e || win.event;\r
336             if (!e) {\r
337                 var c = this.getEvent.caller;\r
338                 while (c) {\r
339                     e = c.arguments[0];\r
340                     if (e && Event == e.constructor) {\r
341                         break;\r
342                     }\r
343                     c = c.caller;\r
344                 }\r
345             }\r
346             return e;\r
347         },\r
348 \r
349         getCharCode : function(ev) {\r
350             ev = ev.browserEvent || ev;\r
351             return ev.charCode || ev.keyCode || 0;\r
352         },\r
353 \r
354         //clearCache: function() {},\r
355 \r
356         _load : function(e) {\r
357             loadComplete = true;\r
358             var EU = Ext.lib.Event;    \r
359             if (Ext.isIE && e !== true) {\r
360         // IE8 complains that _load is null or not an object\r
361         // so lets remove self via arguments.callee\r
362                 doRemove(win, "load", arguments.callee);\r
363             }\r
364         },            \r
365         \r
366         purgeElement : function(el, recurse, eventName) {\r
367             var me = this;\r
368             Ext.each( me.getListeners(el, eventName), function(v){\r
369                 if(v){\r
370                     me.removeListener(el, v.type, v.fn);\r
371                 }\r
372             });\r
373 \r
374             if (recurse && el && el.childNodes) {\r
375                 Ext.each(el.childNodes, function(v){\r
376                     me.purgeElement(v, recurse, eventName);\r
377                 });\r
378             }\r
379         },\r
380 \r
381         getListeners : function(el, eventName) {\r
382             var me = this,\r
383                 results = [], \r
384                 searchLists;\r
385 \r
386             if (eventName){  \r
387                 searchLists = eventName == UNLOAD ? unloadListeners : listeners;\r
388             }else{\r
389                 searchLists = listeners.concat(unloadListeners);\r
390             }\r
391 \r
392             Ext.each(searchLists, function(v, i){\r
393                 if (v && v[EL] == el && (!eventName || eventName == v[TYPE])) {\r
394                     results.push({\r
395                                 type:   v[TYPE],\r
396                                 fn:     v[FN],\r
397                                 obj:    v[OBJ],\r
398                                 adjust: v[ADJ_SCOPE],\r
399                                 index:  i\r
400                             });\r
401                 }   \r
402             });                \r
403 \r
404             return results.length ? results : null;\r
405         },\r
406 \r
407         _unload : function(e) {\r
408              var EU = Ext.lib.Event, \r
409                 i, \r
410                 j, \r
411                 l, \r
412                 len, \r
413                 index,\r
414                 scope;\r
415                 \r
416 \r
417             Ext.each(unloadListeners, function(v) {\r
418                 if (v) {\r
419                     try{\r
420                         scope =  v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) :  win; \r
421                         v[FN].call(scope, EU.getEvent(e), v[OBJ]);\r
422                     }catch(ex){}\r
423                 }   \r
424             });     \r
425 \r
426             unloadListeners = null;\r
427 \r
428             if(listeners && (j = listeners.length)){                    \r
429                 while(j){                        \r
430                     if((l = listeners[index = --j])){\r
431                         EU.removeListener(l[EL], l[TYPE], l[FN], index);\r
432                     }                        \r
433                 }\r
434                 //EU.clearCache();\r
435             }\r
436 \r
437             doRemove(win, UNLOAD, EU._unload);\r
438         }            \r
439     };        \r
440     \r
441     // Initialize stuff.\r
442     pub.on = pub.addListener;\r
443     pub.un = pub.removeListener;\r
444     if (doc && doc.body) {\r
445         pub._load(true);\r
446     } else {\r
447         doAdd(win, "load", pub._load);\r
448     }\r
449     doAdd(win, UNLOAD, pub._unload);    \r
450     _tryPreloadAttach();\r
451     \r
452     return pub;\r
453 }();