Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / adapter / core / ext-base-event.js
1 /*!
2  * Ext JS Library 3.0.3
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     function checkRelatedTarget(e) {\r
81         return !elContains(e.currentTarget, pub.getRelatedTarget(e));\r
82     }\r
83 \r
84     function elContains(parent, child) {\r
85        if(parent && parent.firstChild){  \r
86          while(child) {\r
87             if(child === parent) {\r
88                 return true;\r
89             }\r
90             child = child.parentNode;            \r
91             if(child && (child.nodeType != 1)) {\r
92                 child = null;\r
93             }\r
94           }\r
95         }\r
96         return false;\r
97     }\r
98 \r
99         \r
100     // private  \r
101     function _getCacheIndex(el, eventName, fn) {\r
102         for(var v, index = -1, len = listeners.length, i = len - 1; i >= 0; --i){\r
103             v = listeners[i];\r
104             if (v && v[FN] == fn && v[EL] == el && v[TYPE] == eventName) {\r
105                 index = i;\r
106                 break;\r
107             }\r
108         }\r
109         return index;\r
110     }\r
111                     \r
112     // private\r
113     function _tryPreloadAttach() {\r
114         var ret = false,                \r
115             notAvail = [],\r
116             element,\r
117             tryAgain = !loadComplete || (retryCount > 0);                       \r
118         \r
119         if (!locked) {\r
120             locked = true;\r
121             \r
122             Ext.each(onAvailStack, function (v,i,a){\r
123                 if(v && (element = doc.getElementById(v.id))){\r
124                     if(!v.checkReady || loadComplete || element.nextSibling || (doc && doc.body)) {\r
125                         element = v.override ? (v.override === true ? v.obj : v.override) : element;\r
126                         v.fn.call(element, v.obj);\r
127                         onAvailStack[i] = null;\r
128                     } else {\r
129                         notAvail.push(v);\r
130                     }\r
131                 }   \r
132             });\r
133 \r
134             retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;\r
135 \r
136             if (tryAgain) { \r
137                 startInterval();\r
138             } else {\r
139                 clearInterval(_interval);\r
140                 _interval = null;\r
141             }\r
142 \r
143             ret = !(locked = false);\r
144         }\r
145         return ret;\r
146     }\r
147     \r
148     // private              \r
149     function startInterval() {            \r
150         if(!_interval){                    \r
151             var callback = function() {\r
152                 _tryPreloadAttach();\r
153             };\r
154             _interval = setInterval(callback, POLL_INTERVAL);\r
155         }\r
156     }\r
157     \r
158     // private \r
159     function getScroll() {\r
160         var dd = doc.documentElement, \r
161             db = doc.body;\r
162         if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){\r
163             return [dd[SCROLLLEFT], dd[SCROLLTOP]];\r
164         }else if(db){\r
165             return [db[SCROLLLEFT], db[SCROLLTOP]];\r
166         }else{\r
167             return [0, 0];\r
168         }\r
169     }\r
170         \r
171     // private\r
172     function getPageCoord (ev, xy) {\r
173         ev = ev.browserEvent || ev;\r
174         var coord  = ev['page' + xy];\r
175         if (!coord && coord !== 0) {\r
176             coord = ev['client' + xy] || 0;\r
177 \r
178             if (Ext.isIE) {\r
179                 coord += getScroll()[xy == "X" ? 0 : 1];\r
180             }\r
181         }\r
182 \r
183         return coord;\r
184     }\r
185 \r
186     var pub =  {\r
187         onAvailable : function(p_id, p_fn, p_obj, p_override) {             \r
188             onAvailStack.push({ \r
189                 id:         p_id,\r
190                 fn:         p_fn,\r
191                 obj:        p_obj,\r
192                 override:   p_override,\r
193                 checkReady: false });\r
194 \r
195             retryCount = POLL_RETRYS;\r
196             startInterval();\r
197         },\r
198 \r
199 \r
200         addListener: function(el, eventName, fn) {\r
201             var ret;                \r
202             el = Ext.getDom(el);                \r
203             if (el && fn) {\r
204                 if (UNLOAD == eventName) {\r
205                     ret = !!(unloadListeners[unloadListeners.length] = [el, eventName, fn]);                    \r
206                 } else {\r
207                     listeners.push([el, eventName, fn, ret = doAdd(el, eventName, fn, false)]);\r
208                 }\r
209             }\r
210             return !!ret;\r
211         },\r
212 \r
213         removeListener: function(el, eventName, fn) {\r
214             var ret = false,\r
215                 index, \r
216                 cacheItem;\r
217 \r
218             el = Ext.getDom(el);\r
219 \r
220             if(!fn) {                   \r
221                 ret = this.purgeElement(el, false, eventName);\r
222             } else if (UNLOAD == eventName) {   \r
223                 Ext.each(unloadListeners, function(v, i, a) {\r
224                     if( v && v[0] == el && v[1] == eventName && v[2] == fn) {\r
225                         unloadListeners.splice(i, 1);\r
226                         ret = true;\r
227                     }\r
228                 });\r
229             } else {    \r
230                 index = arguments[3] || _getCacheIndex(el, eventName, fn);\r
231                 cacheItem = listeners[index];\r
232                 \r
233                 if (el && cacheItem) {\r
234                     doRemove(el, eventName, cacheItem[WFN], false);     \r
235                     cacheItem[WFN] = cacheItem[FN] = null;                       \r
236                     listeners.splice(index, 1);     \r
237                     ret = true;\r
238                 }\r
239             }\r
240             return ret;\r
241         },\r
242 \r
243         getTarget : function(ev) {\r
244             ev = ev.browserEvent || ev;                \r
245             return this.resolveTextNode(ev.target || ev.srcElement);\r
246         },\r
247 \r
248         resolveTextNode : Ext.isGecko ? function(node){\r
249             if(!node){\r
250                 return;\r
251             }\r
252             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197\r
253             var s = HTMLElement.prototype.toString.call(node);\r
254             if(s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]'){\r
255                 return;\r
256             }\r
257             return node.nodeType == 3 ? node.parentNode : node;\r
258         } : function(node){\r
259             return node && node.nodeType == 3 ? node.parentNode : node;\r
260         },\r
261 \r
262         getRelatedTarget : function(ev) {\r
263             ev = ev.browserEvent || ev;\r
264             return this.resolveTextNode(ev.relatedTarget || \r
265                     (ev.type == MOUSEOUT ? ev.toElement :\r
266                      ev.type == MOUSEOVER ? ev.fromElement : null));\r
267         },\r
268         \r
269         getPageX : function(ev) {\r
270             return getPageCoord(ev, "X");\r
271         },\r
272 \r
273         getPageY : function(ev) {\r
274             return getPageCoord(ev, "Y");\r
275         },\r
276 \r
277 \r
278         getXY : function(ev) {                             \r
279             return [this.getPageX(ev), this.getPageY(ev)];\r
280         },\r
281 \r
282 // Is this useful?  Removing to save space unless use case exists.\r
283 //             getTime: function(ev) {\r
284 //                 ev = ev.browserEvent || ev;\r
285 //                 if (!ev.time) {\r
286 //                     var t = new Date().getTime();\r
287 //                     try {\r
288 //                         ev.time = t;\r
289 //                     } catch(ex) {\r
290 //                         return t;\r
291 //                     }\r
292 //                 }\r
293 \r
294 //                 return ev.time;\r
295 //             },\r
296 \r
297         stopEvent : function(ev) {                            \r
298             this.stopPropagation(ev);\r
299             this.preventDefault(ev);\r
300         },\r
301 \r
302         stopPropagation : function(ev) {\r
303             ev = ev.browserEvent || ev;\r
304             if (ev.stopPropagation) {\r
305                 ev.stopPropagation();\r
306             } else {\r
307                 ev.cancelBubble = true;\r
308             }\r
309         },\r
310 \r
311         preventDefault : function(ev) {\r
312             ev = ev.browserEvent || ev;\r
313             if (ev.preventDefault) {\r
314                 ev.preventDefault();\r
315             } else {\r
316                 ev.returnValue = false;\r
317             }\r
318         },\r
319         \r
320         getEvent : function(e) {\r
321             e = e || win.event;\r
322             if (!e) {\r
323                 var c = this.getEvent.caller;\r
324                 while (c) {\r
325                     e = c.arguments[0];\r
326                     if (e && Event == e.constructor) {\r
327                         break;\r
328                     }\r
329                     c = c.caller;\r
330                 }\r
331             }\r
332             return e;\r
333         },\r
334 \r
335         getCharCode : function(ev) {\r
336             ev = ev.browserEvent || ev;\r
337             return ev.charCode || ev.keyCode || 0;\r
338         },\r
339 \r
340         //clearCache: function() {},\r
341 \r
342         _load : function(e) {\r
343             loadComplete = true;\r
344             var EU = Ext.lib.Event;    \r
345             if (Ext.isIE && e !== true) {\r
346         // IE8 complains that _load is null or not an object\r
347         // so lets remove self via arguments.callee\r
348                 doRemove(win, "load", arguments.callee);\r
349             }\r
350         },            \r
351         \r
352         purgeElement : function(el, recurse, eventName) {\r
353             var me = this;\r
354             Ext.each( me.getListeners(el, eventName), function(v){\r
355                 if(v){\r
356                     me.removeListener(el, v.type, v.fn, v.index);\r
357                 }\r
358             });\r
359 \r
360             if (recurse && el && el.childNodes) {\r
361                 Ext.each(el.childNodes, function(v){\r
362                     me.purgeElement(v, recurse, eventName);\r
363                 });\r
364             }\r
365         },\r
366 \r
367         getListeners : function(el, eventName) {\r
368             var me = this,\r
369                 results = [], \r
370                 searchLists;\r
371 \r
372             if (eventName){  \r
373                 searchLists = eventName == UNLOAD ? unloadListeners : listeners;\r
374             }else{\r
375                 searchLists = listeners.concat(unloadListeners);\r
376             }\r
377 \r
378             Ext.each(searchLists, function(v, i){\r
379                 if (v && v[EL] == el && (!eventName || eventName == v[TYPE])) {\r
380                     results.push({\r
381                                 type:   v[TYPE],\r
382                                 fn:     v[FN],\r
383                                 obj:    v[OBJ],\r
384                                 adjust: v[ADJ_SCOPE],\r
385                                 index:  i\r
386                             });\r
387                 }   \r
388             });                \r
389 \r
390             return results.length ? results : null;\r
391         },\r
392 \r
393         _unload : function(e) {\r
394              var EU = Ext.lib.Event, \r
395                 i, \r
396                 j, \r
397                 l, \r
398                 len, \r
399                 index,\r
400                 scope;\r
401                 \r
402 \r
403             Ext.each(unloadListeners, function(v) {\r
404                 if (v) {\r
405                     try{\r
406                         scope =  v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) :  win; \r
407                         v[FN].call(scope, EU.getEvent(e), v[OBJ]);\r
408                     }catch(ex){}\r
409                 }   \r
410             });     \r
411 \r
412             unloadListeners = null;\r
413 \r
414             if(listeners && (j = listeners.length)){                    \r
415                 while(j){                        \r
416                     if((l = listeners[index = --j])){\r
417                         EU.removeListener(l[EL], l[TYPE], l[FN], index);\r
418                     }                        \r
419                 }\r
420                 //EU.clearCache();\r
421             }\r
422 \r
423             doRemove(win, UNLOAD, EU._unload);\r
424         }            \r
425     };        \r
426     \r
427     // Initialize stuff.\r
428     pub.on = pub.addListener;\r
429     pub.un = pub.removeListener;\r
430     if (doc && doc.body) {\r
431         pub._load(true);\r
432     } else {\r
433         doAdd(win, "load", pub._load);\r
434     }\r
435     doAdd(win, UNLOAD, pub._unload);    \r
436     _tryPreloadAttach();\r
437     \r
438     return pub;\r
439 }();