jquery event: Removed a needless if (old code)
[jquery.git] / src / event.js
1 /*
2  * A number of helper functions used for managing events.
3  * Many of the ideas behind this code originated from
4  * Dean Edwards' addEvent library.
5  */
6 jQuery.event = {
7
8         // Bind an event to an element
9         // Original by Dean Edwards
10         add: function(elem, types, handler, data) {
11                 if ( elem.nodeType == 3 || elem.nodeType == 8 )
12                         return;
13
14                 // For whatever reason, IE has trouble passing the window object
15                 // around, causing it to be cloned in the process
16                 if ( elem.setInterval && elem != window )
17                         elem = window;
18
19                 // Make sure that the function being executed has a unique ID
20                 if ( !handler.guid )
21                         handler.guid = this.guid++;
22
23                 // if data is passed, bind to handler
24                 if ( data !== undefined ) {
25                         // Create temporary function pointer to original handler
26                         var fn = handler;
27
28                         // Create unique handler function, wrapped around original handler
29                         handler = this.proxy( fn, function() {
30                                 // Pass arguments and context to original handler
31                                 return fn.apply(this, arguments);
32                         });
33
34                         // Store data in unique handler
35                         handler.data = data;
36                 }
37
38                 // Init the element's event structure
39                 var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
40                         handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
41                                 // Handle the second event of a trigger and when
42                                 // an event is called after a page has unloaded
43                                 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
44                                         jQuery.event.handle.apply(arguments.callee.elem, arguments) :
45                                         undefined;
46                         });
47                 // Add elem as a property of the handle function
48                 // This is to prevent a memory leak with non-native
49                 // event in IE.
50                 handle.elem = elem;
51
52                 // Handle multiple events separated by a space
53                 // jQuery(...).bind("mouseover mouseout", fn);
54                 jQuery.each(types.split(/\s+/), function(index, type) {
55                         // Namespaced event handlers
56                         var namespaces = type.split(".");
57                         type = namespaces.shift();
58                         handler.type = namespaces.slice().sort().join(".");
59
60                         // Get the current list of functions bound to this event
61                         var handlers = events[type];
62                         
63                         if ( jQuery.event.specialAll[type] )
64                                 jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
65
66                         // Init the event handler queue
67                         if (!handlers) {
68                                 handlers = events[type] = {};
69
70                                 // Check for a special event handler
71                                 // Only use addEventListener/attachEvent if the special
72                                 // events handler returns false
73                                 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
74                                         // Bind the global event handler to the element
75                                         if (elem.addEventListener)
76                                                 elem.addEventListener(type, handle, false);
77                                         else if (elem.attachEvent)
78                                                 elem.attachEvent("on" + type, handle);
79                                 }
80                         }
81
82                         // Add the function to the element's handler list
83                         handlers[handler.guid] = handler;
84
85                         // Keep track of which events have been used, for global triggering
86                         jQuery.event.global[type] = true;
87                 });
88
89                 // Nullify elem to prevent memory leaks in IE
90                 elem = null;
91         },
92
93         guid: 1,
94         global: {},
95
96         // Detach an event or set of events from an element
97         remove: function(elem, types, handler) {
98                 // don't do events on text and comment nodes
99                 if ( elem.nodeType == 3 || elem.nodeType == 8 )
100                         return;
101
102                 var events = jQuery.data(elem, "events"), ret, index;
103
104                 if ( events ) {
105                         // Unbind all events for the element
106                         if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
107                                 for ( var type in events )
108                                         this.remove( elem, type + (types || "") );
109                         else {
110                                 // types is actually an event object here
111                                 if ( types.type ) {
112                                         handler = types.handler;
113                                         types = types.type;
114                                 }
115
116                                 // Handle multiple events seperated by a space
117                                 // jQuery(...).unbind("mouseover mouseout", fn);
118                                 jQuery.each(types.split(/\s+/), function(index, type){
119                                         // Namespaced event handlers
120                                         var namespaces = type.split(".");
121                                         type = namespaces.shift();
122                                         var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
123
124                                         if ( events[type] ) {
125                                                 // remove the given handler for the given type
126                                                 if ( handler )
127                                                         delete events[type][handler.guid];
128
129                                                 // remove all handlers for the given type
130                                                 else
131                                                         for ( handler in events[type] )
132                                                                 // Handle the removal of namespaced events
133                                                                 if ( namespace.test(events[type][handler].type) )
134                                                                         delete events[type][handler];
135                                                                         
136                                                 if ( jQuery.event.specialAll[type] )
137                                                         jQuery.event.specialAll[type].teardown.call(elem, namespaces);
138
139                                                 // remove generic event handler if no more handlers exist
140                                                 for ( ret in events[type] ) break;
141                                                 if ( !ret ) {
142                                                         if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
143                                                                 if (elem.removeEventListener)
144                                                                         elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
145                                                                 else if (elem.detachEvent)
146                                                                         elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
147                                                         }
148                                                         ret = null;
149                                                         delete events[type];
150                                                 }
151                                         }
152                                 });
153                         }
154
155                         // Remove the expando if it's no longer used
156                         for ( ret in events ) break;
157                         if ( !ret ) {
158                                 var handle = jQuery.data( elem, "handle" );
159                                 if ( handle ) handle.elem = null;
160                                 jQuery.removeData( elem, "events" );
161                                 jQuery.removeData( elem, "handle" );
162                         }
163                 }
164         },
165
166         // bubbling is internal
167         trigger: function( event, data, elem, bubbling ) {
168                 // Event object or event type
169                 var type = event.type || event;
170
171                 if( !bubbling ){
172                         event = typeof event === "object" ?
173                                 // jQuery.Event object
174                                 event[expando] ? event :
175                                 // Object literal
176                                 jQuery.extend( jQuery.Event(type), event ) :
177                                 // Just the event type (string)
178                                 jQuery.Event(type);
179
180                         if ( type.indexOf("!") >= 0 ) {
181                                 event.type = type = type.slice(0, -1);
182                                 event.exclusive = true;
183                         }
184
185                         // Handle a global trigger
186                         if ( !elem ) {
187                                 // Don't bubble custom events when global (to avoid too much overhead)
188                                 event.stopPropagation();
189                                 // Only trigger if we've ever bound an event for it
190                                 if ( this.global[type] )
191                                         jQuery.each( jQuery.cache, function(){
192                                                 if ( this.events && this.events[type] )
193                                                         jQuery.event.trigger( event, data, this.handle.elem );
194                                         });
195                         }
196
197                         // Handle triggering a single element
198
199                         // don't do events on text and comment nodes
200                         if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
201                                 return undefined;
202                         
203                         // Clean up in case it is reused
204                         event.result = undefined;
205                         event.target = elem;
206                         
207                         // Clone the incoming data, if any
208                         data = jQuery.makeArray(data);
209                         data.unshift( event );
210                 }
211
212                 event.currentTarget = elem;
213
214                 // Trigger the event, it is assumed that "handle" is a function
215                 var handle = jQuery.data(elem, "handle");
216                 if ( handle )
217                         handle.apply( elem, data );
218
219                 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
220                 if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
221                         event.result = false;
222
223                 // Trigger the native events (except for clicks on links)
224                 if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
225                         this.triggered = true;
226                         try {
227                                 elem[ type ]();
228                         // prevent IE from throwing an error for some hidden elements
229                         } catch (e) {}
230                 }
231
232                 if ( !event.isPropagationStopped() ) {
233                         var parent = elem.parentNode || elem.ownerDocument;
234                         if ( parent )
235                                 jQuery.event.trigger(event, data, parent, true);
236                 }
237
238                 this.triggered = false;
239         },
240
241         handle: function(event) {
242                 // returned undefined or false
243                 var all, handlers;
244
245                 event = arguments[0] = jQuery.event.fix( event || window.event );
246
247                 // Namespaced event handlers
248                 var namespaces = event.type.split(".");
249                 event.type = namespaces.shift();
250
251                 // Cache this now, all = true means, any handler
252                 all = !namespaces.length && !event.exclusive;
253                 
254                 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
255
256                 handlers = ( jQuery.data(this, "events") || {} )[event.type];
257
258                 for ( var j in handlers ) {
259                         var handler = handlers[j];
260
261                         // Filter the functions by class
262                         if ( all || namespace.test(handler.type) ) {
263                                 // Pass in a reference to the handler function itself
264                                 // So that we can later remove it
265                                 event.handler = handler;
266                                 event.data = handler.data;
267
268                                 var ret = handler.apply(this, arguments);
269
270                                 if( ret !== undefined ){
271                                         event.result = ret;
272                                         if ( ret === false ) {
273                                                 event.preventDefault();
274                                                 event.stopPropagation();
275                                         }
276                                 }
277
278                                 if( event.isImmediatePropagationStopped() )
279                                         break;
280
281                         }
282                 }
283         },
284
285         props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
286
287         fix: function(event) {
288                 if ( event[expando] )
289                         return event;
290
291                 // store a copy of the original event object
292                 // and "clone" to set read-only properties
293                 var originalEvent = event;
294                 event = jQuery.Event( originalEvent );
295
296                 for ( var i = this.props.length, prop; i; ){
297                         prop = this.props[ --i ];
298                         event[ prop ] = originalEvent[ prop ];
299                 }
300
301                 // Fix target property, if necessary
302                 if ( !event.target )
303                         event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
304
305                 // check if target is a textnode (safari)
306                 if ( event.target.nodeType == 3 )
307                         event.target = event.target.parentNode;
308
309                 // Add relatedTarget, if necessary
310                 if ( !event.relatedTarget && event.fromElement )
311                         event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
312
313                 // Calculate pageX/Y if missing and clientX/Y available
314                 if ( event.pageX == null && event.clientX != null ) {
315                         var doc = document.documentElement, body = document.body;
316                         event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
317                         event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
318                 }
319
320                 // Add which for key events
321                 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
322                         event.which = event.charCode || event.keyCode;
323
324                 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
325                 if ( !event.metaKey && event.ctrlKey )
326                         event.metaKey = event.ctrlKey;
327
328                 // Add which for click: 1 == left; 2 == middle; 3 == right
329                 // Note: button is not normalized, so don't use it
330                 if ( !event.which && event.button )
331                         event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
332
333                 return event;
334         },
335
336         proxy: function( fn, proxy ){
337                 // Set the guid of unique handler to the same of original handler, so it can be removed
338                 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
339                 // So proxy can be declared as an argument
340                 return proxy;
341         },
342
343         special: {
344                 ready: {
345                         // Make sure the ready event is setup
346                         setup: bindReady,
347                         teardown: function() {}
348                 }
349         },
350         
351         specialAll: {
352                 live: {
353                         setup: function( selector, namespaces ){
354                                 jQuery.event.add( this, namespaces[0], liveHandler );
355                         },
356                         teardown:  function( namespaces ){
357                                 if ( namespaces.length ) {
358                                         var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
359                                         
360                                         jQuery.each( (jQuery.data(this, "events").live || {}), function(){
361                                                 if ( name.test(this.type) )
362                                                         remove++;
363                                         });
364                                         
365                                         if ( remove <= 1 )
366                                                 jQuery.event.remove( this, namespaces[0], liveHandler );
367                                 }
368                         }
369                 }
370         }
371 };
372
373 jQuery.Event = function( src ){
374         // Allow instantiation without the 'new' keyword
375         if( !this.preventDefault )
376                 return new jQuery.Event(src);
377         
378         // Event object
379         if( src && src.type ){
380                 this.originalEvent = src;
381                 this.type = src.type;
382                 this.timeStamp = src.timeStamp;
383         // Event type
384         }else
385                 this.type = src;
386
387         if( !this.timeStamp )
388                 this.timeStamp = now();
389         
390         // Mark it as fixed
391         this[expando] = true;
392 };
393
394 function returnFalse(){
395         return false;
396 }
397 function returnTrue(){
398         return true;
399 }
400
401 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
402 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
403 jQuery.Event.prototype = {
404         preventDefault: function() {
405                 this.isDefaultPrevented = returnTrue;
406
407                 var e = this.originalEvent;
408                 if( !e )
409                         return;
410                 // if preventDefault exists run it on the original event
411                 if (e.preventDefault)
412                         e.preventDefault();
413                 // otherwise set the returnValue property of the original event to false (IE)
414                 e.returnValue = false;
415         },
416         stopPropagation: function() {
417                 this.isPropagationStopped = returnTrue;
418
419                 var e = this.originalEvent;
420                 if( !e )
421                         return;
422                 // if stopPropagation exists run it on the original event
423                 if (e.stopPropagation)
424                         e.stopPropagation();
425                 // otherwise set the cancelBubble property of the original event to true (IE)
426                 e.cancelBubble = true;
427         },
428         stopImmediatePropagation:function(){
429                 this.isImmediatePropagationStopped = returnTrue;
430                 this.stopPropagation();
431         },
432         isDefaultPrevented: returnFalse,
433         isPropagationStopped: returnFalse,
434         isImmediatePropagationStopped: returnFalse
435 };
436 // Checks if an event happened on an element within another element
437 // Used in jQuery.event.special.mouseenter and mouseleave handlers
438 var withinElement = function(event) {
439         // Check if mouse(over|out) are still within the same parent element
440         var parent = event.relatedTarget;
441         // Traverse up the tree
442         while ( parent && parent != this )
443                 try { parent = parent.parentNode; }
444                 catch(e) { parent = this; }
445         
446         if( parent != this ){
447                 // set the correct event type
448                 event.type = event.data;
449                 // handle event if we actually just moused on to a non sub-element
450                 jQuery.event.handle.apply( this, arguments );
451         }
452 };
453         
454 jQuery.each({ 
455         mouseover: 'mouseenter', 
456         mouseout: 'mouseleave'
457 }, function( orig, fix ){
458         jQuery.event.special[ fix ] = {
459                 setup: function(){
460                         jQuery.event.add( this, orig, withinElement, fix );
461                 },
462                 teardown: function(){
463                         jQuery.event.remove( this, orig, withinElement );
464                 }
465         };                         
466 });
467
468 jQuery.fn.extend({
469         bind: function( type, data, fn ) {
470                 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
471                         jQuery.event.add( this, type, fn || data, fn && data );
472                 });
473         },
474
475         one: function( type, data, fn ) {
476                 var one = jQuery.event.proxy( fn || data, function(event) {
477                         jQuery(this).unbind(event, one);
478                         return (fn || data).apply( this, arguments );
479                 });
480                 return this.each(function(){
481                         jQuery.event.add( this, type, one, fn && data);
482                 });
483         },
484
485         unbind: function( type, fn ) {
486                 return this.each(function(){
487                         jQuery.event.remove( this, type, fn );
488                 });
489         },
490
491         trigger: function( type, data ) {
492                 return this.each(function(){
493                         jQuery.event.trigger( type, data, this );
494                 });
495         },
496
497         triggerHandler: function( type, data ) {
498                 if( this[0] ){
499                         var event = jQuery.Event(type);
500                         event.preventDefault();
501                         event.stopPropagation();
502                         jQuery.event.trigger( event, data, this[0] );
503                         return event.result;
504                 }               
505         },
506
507         toggle: function( fn ) {
508                 // Save reference to arguments for access in closure
509                 var args = arguments, i = 1;
510
511                 // link all the functions, so any of them can unbind this click handler
512                 while( i < args.length )
513                         jQuery.event.proxy( fn, args[i++] );
514
515                 return this.click( jQuery.event.proxy( fn, function(event) {
516                         // Figure out which function to execute
517                         this.lastToggle = ( this.lastToggle || 0 ) % i;
518
519                         // Make sure that clicks stop
520                         event.preventDefault();
521
522                         // and execute the function
523                         return args[ this.lastToggle++ ].apply( this, arguments ) || false;
524                 }));
525         },
526
527         hover: function(fnOver, fnOut) {
528                 return this.mouseenter(fnOver).mouseleave(fnOut);
529         },
530
531         ready: function(fn) {
532                 // Attach the listeners
533                 bindReady();
534
535                 // If the DOM is already ready
536                 if ( jQuery.isReady )
537                         // Execute the function immediately
538                         fn.call( document, jQuery );
539
540                 // Otherwise, remember the function for later
541                 else
542                         // Add the function to the wait list
543                         jQuery.readyList.push( fn );
544
545                 return this;
546         },
547         
548         live: function( type, fn ){
549                 jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
550                 return this;
551         },
552         
553         die: function( type, fn ){
554                 jQuery(document).unbind( liveConvert(type, this.selector), fn );
555                 return this;
556         }
557 });
558
559 function liveHandler( event ){
560         var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
561                 stop = true;
562
563         jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
564                 if ( !event.isImmediatePropagationStopped() && check.test(fn.type) ) {
565                         var elem = jQuery(event.target).closest(fn.data)[0];
566                         if ( elem && fn.call(elem, event, fn.data) === false )
567                                 stop = false;
568                 }
569         });
570         return stop;
571 }
572
573 function liveConvert(type, selector){
574         return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
575 }
576
577 jQuery.extend({
578         isReady: false,
579         readyList: [],
580         // Handle when the DOM is ready
581         ready: function() {
582                 // Make sure that the DOM is not already loaded
583                 if ( !jQuery.isReady ) {
584                         // Remember that the DOM is ready
585                         jQuery.isReady = true;
586
587                         // If there are functions bound, to execute
588                         if ( jQuery.readyList ) {
589                                 // Execute all of them
590                                 jQuery.each( jQuery.readyList, function(){
591                                         this.call( document, jQuery );
592                                 });
593
594                                 // Reset the list of functions
595                                 jQuery.readyList = null;
596                         }
597
598                         // Trigger any bound ready events
599                         jQuery(document).triggerHandler("ready");
600                 }
601         }
602 });
603
604 var readyBound = false;
605
606 function bindReady(){
607         if ( readyBound ) return;
608         readyBound = true;
609
610         // Mozilla, Opera and webkit nightlies currently support this event
611         if ( document.addEventListener ) {
612                 // Use the handy event callback
613                 document.addEventListener( "DOMContentLoaded", function(){
614                         document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
615                         jQuery.ready();
616                 }, false );
617
618         // If IE event model is used
619         } else if ( document.attachEvent ) {
620                 // ensure firing before onload,
621                 // maybe late but safe also for iframes
622                 document.attachEvent("onreadystatechange", function(){
623                         if ( document.readyState === "complete" ) {
624                                 document.detachEvent( "onreadystatechange", arguments.callee );
625                                 jQuery.ready();
626                         }
627                 });
628
629                 // If IE and not an iframe
630                 // continually check to see if the document is ready
631                 if ( document.documentElement.doScroll && !window.frameElement ) (function(){
632                         if ( jQuery.isReady ) return;
633
634                         try {
635                                 // If IE is used, use the trick by Diego Perini
636                                 // http://javascript.nwbox.com/IEContentLoaded/
637                                 document.documentElement.doScroll("left");
638                         } catch( error ) {
639                                 setTimeout( arguments.callee, 0 );
640                                 return;
641                         }
642
643                         // and execute any waiting functions
644                         jQuery.ready();
645                 })();
646         }
647
648         // A fallback to window.onload, that will always work
649         jQuery.event.add( window, "load", jQuery.ready );
650 }
651
652 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
653         "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
654         "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
655
656         // Handle event binding
657         jQuery.fn[name] = function(fn){
658                 return fn ? this.bind(name, fn) : this.trigger(name);
659         };
660 });
661
662 // Prevent memory leaks in IE
663 // And prevent errors on refresh with events like mouseover in other browsers
664 // Window isn't included so as not to unbind existing unload events
665 jQuery( window ).bind( 'unload', function(){ 
666         for ( var id in jQuery.cache )
667                 // Skip the window
668                 if ( id != 1 && jQuery.cache[ id ].handle )
669                         jQuery.event.remove( jQuery.cache[ id ].handle.elem );
670 });