12c9cf2b282ce5fa1df22d09c3dfa75bc7ad60e7
[extjs.git] / src / ext-core / src / adapter / ext-base-event.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 Ext.lib.Event = function() {
8     var loadComplete = false,
9         unloadListeners = {},
10         retryCount = 0,
11         onAvailStack = [],
12         _interval,
13         locked = false,
14         win = window,
15         doc = document,
16
17         // constants
18         POLL_RETRYS = 200,
19         POLL_INTERVAL = 20,
20         EL = 0,
21         TYPE = 0,
22         FN = 1,
23         WFN = 2,
24         OBJ = 2,
25         ADJ_SCOPE = 3,
26         SCROLLLEFT = 'scrollLeft',
27         SCROLLTOP = 'scrollTop',
28         UNLOAD = 'unload',
29         MOUSEOVER = 'mouseover',
30         MOUSEOUT = 'mouseout',
31         // private
32         doAdd = function() {
33             var ret;
34             if (win.addEventListener) {
35                 ret = function(el, eventName, fn, capture) {
36                     if (eventName == 'mouseenter') {
37                         fn = fn.createInterceptor(checkRelatedTarget);
38                         el.addEventListener(MOUSEOVER, fn, (capture));
39                     } else if (eventName == 'mouseleave') {
40                         fn = fn.createInterceptor(checkRelatedTarget);
41                         el.addEventListener(MOUSEOUT, fn, (capture));
42                     } else {
43                         el.addEventListener(eventName, fn, (capture));
44                     }
45                     return fn;
46                 };
47             } else if (win.attachEvent) {
48                 ret = function(el, eventName, fn, capture) {
49                     el.attachEvent("on" + eventName, fn);
50                     return fn;
51                 };
52             } else {
53                 ret = function(){};
54             }
55             return ret;
56         }(),
57         // private
58         doRemove = function(){
59             var ret;
60             if (win.removeEventListener) {
61                 ret = function (el, eventName, fn, capture) {
62                     if (eventName == 'mouseenter') {
63                         eventName = MOUSEOVER;
64                     } else if (eventName == 'mouseleave') {
65                         eventName = MOUSEOUT;
66                     }
67                     el.removeEventListener(eventName, fn, (capture));
68                 };
69             } else if (win.detachEvent) {
70                 ret = function (el, eventName, fn) {
71                     el.detachEvent("on" + eventName, fn);
72                 };
73             } else {
74                 ret = function(){};
75             }
76             return ret;
77         }();
78
79     function checkRelatedTarget(e) {
80         return !elContains(e.currentTarget, pub.getRelatedTarget(e));
81     }
82
83     function elContains(parent, child) {
84        if(parent && parent.firstChild){
85          while(child) {
86             if(child === parent) {
87                 return true;
88             }
89             child = child.parentNode;
90             if(child && (child.nodeType != 1)) {
91                 child = null;
92             }
93           }
94         }
95         return false;
96     }
97
98     // private
99     function _tryPreloadAttach() {
100         var ret = false,
101             notAvail = [],
102             element, i, v, override,
103             tryAgain = !loadComplete || (retryCount > 0);
104
105         if(!locked){
106             locked = true;
107             
108             for(i = 0; i < onAvailStack.length; ++i){
109                 v = onAvailStack[i];
110                 if(v && (element = doc.getElementById(v.id))){
111                     if(!v.checkReady || loadComplete || element.nextSibling || (doc && doc.body)) {
112                         override = v.override;
113                         element = override ? (override === true ? v.obj : override) : element;
114                         v.fn.call(element, v.obj);
115                         onAvailStack.remove(v);
116                         --i;
117                     }else{
118                         notAvail.push(v);
119                     }
120                 }
121             }
122
123             retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;
124
125             if (tryAgain) {
126                 startInterval();
127             } else {
128                 clearInterval(_interval);
129                 _interval = null;
130             }
131             ret = !(locked = false);
132         }
133         return ret;
134     }
135
136     // private
137     function startInterval() {
138         if(!_interval){
139             var callback = function() {
140                 _tryPreloadAttach();
141             };
142             _interval = setInterval(callback, POLL_INTERVAL);
143         }
144     }
145
146     // private
147     function getScroll() {
148         var dd = doc.documentElement,
149             db = doc.body;
150         if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){
151             return [dd[SCROLLLEFT], dd[SCROLLTOP]];
152         }else if(db){
153             return [db[SCROLLLEFT], db[SCROLLTOP]];
154         }else{
155             return [0, 0];
156         }
157     }
158
159     // private
160     function getPageCoord (ev, xy) {
161         ev = ev.browserEvent || ev;
162         var coord  = ev['page' + xy];
163         if (!coord && coord !== 0) {
164             coord = ev['client' + xy] || 0;
165
166             if (Ext.isIE) {
167                 coord += getScroll()[xy == "X" ? 0 : 1];
168             }
169         }
170
171         return coord;
172     }
173
174     var pub =  {
175         extAdapter: true,
176         onAvailable : function(p_id, p_fn, p_obj, p_override) {
177             onAvailStack.push({
178                 id:         p_id,
179                 fn:         p_fn,
180                 obj:        p_obj,
181                 override:   p_override,
182                 checkReady: false });
183
184             retryCount = POLL_RETRYS;
185             startInterval();
186         },
187
188         // This function should ALWAYS be called from Ext.EventManager
189         addListener: function(el, eventName, fn) {
190             el = Ext.getDom(el);
191             if (el && fn) {
192                 if (eventName == UNLOAD) {
193                     if (unloadListeners[el.id] === undefined) {
194                         unloadListeners[el.id] = [];
195                     }
196                     unloadListeners[el.id].push([eventName, fn]);
197                     return fn;
198                 }
199                 return doAdd(el, eventName, fn, false);
200             }
201             return false;
202         },
203
204         // This function should ALWAYS be called from Ext.EventManager
205         removeListener: function(el, eventName, fn) {
206             el = Ext.getDom(el);
207             var i, len, li, lis;
208             if (el && fn) {
209                 if(eventName == UNLOAD){
210                     if((lis = unloadListeners[el.id]) !== undefined){
211                         for(i = 0, len = lis.length; i < len; i++){
212                             if((li = lis[i]) && li[TYPE] == eventName && li[FN] == fn){
213                                 unloadListeners[el.id].splice(i, 1);
214                             }
215                         }
216                     }
217                     return;
218                 }
219                 doRemove(el, eventName, fn, false);
220             }
221         },
222
223         getTarget : function(ev) {
224             ev = ev.browserEvent || ev;
225             return this.resolveTextNode(ev.target || ev.srcElement);
226         },
227
228         resolveTextNode : Ext.isGecko ? function(node){
229             if(!node){
230                 return;
231             }
232             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
233             var s = HTMLElement.prototype.toString.call(node);
234             if(s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]'){
235                 return;
236             }
237             return node.nodeType == 3 ? node.parentNode : node;
238         } : function(node){
239             return node && node.nodeType == 3 ? node.parentNode : node;
240         },
241
242         getRelatedTarget : function(ev) {
243             ev = ev.browserEvent || ev;
244             return this.resolveTextNode(ev.relatedTarget ||
245                     (ev.type == MOUSEOUT ? ev.toElement :
246                      ev.type == MOUSEOVER ? ev.fromElement : null));
247         },
248
249         getPageX : function(ev) {
250             return getPageCoord(ev, "X");
251         },
252
253         getPageY : function(ev) {
254             return getPageCoord(ev, "Y");
255         },
256
257
258         getXY : function(ev) {
259             return [this.getPageX(ev), this.getPageY(ev)];
260         },
261
262         stopEvent : function(ev) {
263             this.stopPropagation(ev);
264             this.preventDefault(ev);
265         },
266
267         stopPropagation : function(ev) {
268             ev = ev.browserEvent || ev;
269             if (ev.stopPropagation) {
270                 ev.stopPropagation();
271             } else {
272                 ev.cancelBubble = true;
273             }
274         },
275
276         preventDefault : function(ev) {
277             ev = ev.browserEvent || ev;
278             if (ev.preventDefault) {
279                 ev.preventDefault();
280             } else {
281                 ev.returnValue = false;
282             }
283         },
284
285         getEvent : function(e) {
286             e = e || win.event;
287             if (!e) {
288                 var c = this.getEvent.caller;
289                 while (c) {
290                     e = c.arguments[0];
291                     if (e && Event == e.constructor) {
292                         break;
293                     }
294                     c = c.caller;
295                 }
296             }
297             return e;
298         },
299
300         getCharCode : function(ev) {
301             ev = ev.browserEvent || ev;
302             return ev.charCode || ev.keyCode || 0;
303         },
304
305         //clearCache: function() {},
306         // deprecated, call from EventManager
307         getListeners : function(el, eventName) {
308             Ext.EventManager.getListeners(el, eventName);
309         },
310
311         // deprecated, call from EventManager
312         purgeElement : function(el, recurse, eventName) {
313             Ext.EventManager.purgeElement(el, recurse, eventName);
314         },
315
316         _load : function(e) {
317             loadComplete = true;
318             var EU = Ext.lib.Event;
319             if (Ext.isIE && e !== true) {
320         // IE8 complains that _load is null or not an object
321         // so lets remove self via arguments.callee
322                 doRemove(win, "load", arguments.callee);
323             }
324         },
325
326         _unload : function(e) {
327              var EU = Ext.lib.Event,
328                 i, j, l, v, ul, id, len, index, scope;
329
330
331             for (id in unloadListeners) {
332                 ul = unloadListeners[id];
333                 for (i = 0, len = ul.length; i < len; i++) {
334                     v = ul[i];
335                     if (v) {
336                         try{
337                             scope = v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) :  win;
338                             v[FN].call(scope, EU.getEvent(e), v[OBJ]);
339                         }catch(ex){}
340                     }
341                 }
342             };
343
344             Ext.EventManager._unload();
345
346             doRemove(win, UNLOAD, EU._unload);
347         }
348     };
349
350     // Initialize stuff.
351     pub.on = pub.addListener;
352     pub.un = pub.removeListener;
353     if (doc && doc.body) {
354         pub._load(true);
355     } else {
356         doAdd(win, "load", pub._load);
357     }
358     doAdd(win, UNLOAD, pub._unload);
359     _tryPreloadAttach();
360
361     return pub;
362 }();