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