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.
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 ) {
15 // For whatever reason, IE has trouble passing the window object
16 // around, causing it to be cloned in the process
17 if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
21 // Make sure that the function being executed has a unique ID
22 if ( !handler.guid ) {
23 handler.guid = this.guid++;
26 // if data is passed, bind to handler
27 if ( data !== undefined ) {
28 // Create temporary function pointer to original handler
31 // Create unique handler function, wrapped around original handler
32 handler = this.proxy( fn );
34 // Store data in unique handler
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 eventHandle() {
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( eventHandle.elem, arguments ) :
47 // Add elem as a property of the handle function
48 // This is to prevent a memory leak with non-native
52 // Handle multiple events separated by a space
53 // jQuery(...).bind("mouseover mouseout", fn);
54 types = types.split( /\s+/ );
56 while ( (type = types[ i++ ]) ) {
57 // Namespaced event handlers
58 var namespaces = type.split(".");
59 type = namespaces.shift();
60 handler.type = namespaces.slice(0).sort().join(".");
62 // Get the current list of functions bound to this event
63 var handlers = events[ type ],
64 special = this.special[ type ] || {};
68 // Init the event handler queue
70 handlers = events[ type ] = {};
72 // Check for a special event handler
73 // Only use addEventListener/attachEvent if the special
74 // events handler returns false
75 if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) {
76 // Bind the global event handler to the element
77 if ( elem.addEventListener ) {
78 elem.addEventListener( type, handle, false );
79 } else if ( elem.attachEvent ) {
80 elem.attachEvent( "on" + type, handle );
86 var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers );
87 if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
88 modifiedHandler.guid = modifiedHandler.guid || handler.guid;
89 handler = modifiedHandler;
93 // Add the function to the element's handler list
94 handlers[ handler.guid ] = handler;
96 // Keep track of which events have been used, for global triggering
97 this.global[ type ] = true;
100 // Nullify elem to prevent memory leaks in IE
107 // Detach an event or set of events from an element
108 remove: function( elem, types, handler ) {
109 // don't do events on text and comment nodes
110 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
114 var events = jQuery.data( elem, "events" ), ret, type, fn;
117 // Unbind all events for the element
118 if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) {
119 for ( type in events ) {
120 this.remove( elem, type + (types || "") );
123 // types is actually an event object here
125 handler = types.handler;
129 // Handle multiple events separated by a space
130 // jQuery(...).unbind("mouseover mouseout", fn);
131 types = types.split(/\s+/);
133 while ( (type = types[ i++ ]) ) {
134 // Namespaced event handlers
135 var namespaces = type.split(".");
136 type = namespaces.shift();
137 var all = !namespaces.length,
138 cleaned = jQuery.map( namespaces.slice(0).sort() , function(nm){ return nm.replace(/[^\w\s\.\|`]/g, function(ch){return "\\"+ch; }); }),
139 namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
140 special = this.special[ type ] || {};
142 if ( events[ type ] ) {
143 // remove the given handler for the given type
145 fn = events[ type ][ handler.guid ];
146 delete events[ type ][ handler.guid ];
148 // remove all handlers for the given type
150 for ( var handle in events[ type ] ) {
151 // Handle the removal of namespaced events
152 if ( all || namespace.test( events[ type ][ handle ].type ) ) {
153 delete events[ type ][ handle ];
158 if ( special.remove ) {
159 special.remove.call( elem, namespaces, fn);
162 // remove generic event handler if no more handlers exist
163 for ( ret in events[ type ] ) {
167 if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
168 if ( elem.removeEventListener ) {
169 elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
170 } else if ( elem.detachEvent ) {
171 elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) );
175 delete events[ type ];
181 // Remove the expando if it's no longer used
182 for ( ret in events ) {
186 var handle = jQuery.data( elem, "handle" );
190 jQuery.removeData( elem, "events" );
191 jQuery.removeData( elem, "handle" );
196 // bubbling is internal
197 trigger: function( event, data, elem /*, bubbling */ ) {
198 // Event object or event type
199 var type = event.type || event,
200 bubbling = arguments[3];
203 event = typeof event === "object" ?
204 // jQuery.Event object
205 event[expando] ? event :
207 jQuery.extend( jQuery.Event(type), event ) :
208 // Just the event type (string)
211 if ( type.indexOf("!") >= 0 ) {
212 event.type = type = type.slice(0, -1);
213 event.exclusive = true;
216 // Handle a global trigger
218 // Don't bubble custom events when global (to avoid too much overhead)
219 event.stopPropagation();
220 // Only trigger if we've ever bound an event for it
221 if ( this.global[ type ] ) {
222 jQuery.each( jQuery.cache, function() {
223 if ( this.events && this.events[type] ) {
224 jQuery.event.trigger( event, data, this.handle.elem );
230 // Handle triggering a single element
232 // don't do events on text and comment nodes
233 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
237 // Clean up in case it is reused
238 event.result = undefined;
241 // Clone the incoming data, if any
242 data = jQuery.makeArray( data );
243 data.unshift( event );
246 event.currentTarget = elem;
248 // Trigger the event, it is assumed that "handle" is a function
249 var handle = jQuery.data( elem, "handle" );
251 handle.apply( elem, data );
254 var nativeFn, nativeHandler;
256 nativeFn = elem[ type ];
257 nativeHandler = elem[ "on" + type ];
258 // prevent IE from throwing an error for some elements with some event types, see #3533
261 var isClick = jQuery.nodeName(elem, "a") && type === "click";
263 // Trigger the native events (except for clicks on links)
264 if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
265 this.triggered = true;
268 // prevent IE from throwing an error for some hidden elements
271 // Handle triggering native .onfoo handlers
272 } else if ( nativeHandler && nativeHandler.apply( elem, data ) === false ) {
273 event.result = false;
276 this.triggered = false;
278 if ( !event.isPropagationStopped() ) {
279 var parent = elem.parentNode || elem.ownerDocument;
281 jQuery.event.trigger( event, data, parent, true );
286 handle: function( event ) {
287 // returned undefined or false
290 event = arguments[0] = jQuery.event.fix( event || window.event );
291 event.currentTarget = this;
293 // Namespaced event handlers
294 var namespaces = event.type.split(".");
295 event.type = namespaces.shift();
297 // Cache this now, all = true means, any handler
298 all = !namespaces.length && !event.exclusive;
300 var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
302 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
304 for ( var j in handlers ) {
305 var handler = handlers[ j ];
307 // Filter the functions by class
308 if ( all || namespace.test(handler.type) ) {
309 // Pass in a reference to the handler function itself
310 // So that we can later remove it
311 event.handler = handler;
312 event.data = handler.data;
314 var ret = handler.apply( this, arguments );
316 if ( ret !== undefined ) {
318 if ( ret === false ) {
319 event.preventDefault();
320 event.stopPropagation();
324 if ( event.isImmediatePropagationStopped() ) {
334 props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
336 fix: function( event ) {
337 if ( event[ expando ] ) {
341 // store a copy of the original event object
342 // and "clone" to set read-only properties
343 var originalEvent = event;
344 event = jQuery.Event( originalEvent );
346 for ( var i = this.props.length, prop; i; ) {
347 prop = this.props[ --i ];
348 event[ prop ] = originalEvent[ prop ];
351 // Fix target property, if necessary
352 if ( !event.target ) {
353 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
356 // check if target is a textnode (safari)
357 if ( event.target.nodeType === 3 ) {
358 event.target = event.target.parentNode;
361 // Add relatedTarget, if necessary
362 if ( !event.relatedTarget && event.fromElement ) {
363 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
366 // Calculate pageX/Y if missing and clientX/Y available
367 if ( event.pageX == null && event.clientX != null ) {
368 var doc = document.documentElement, body = document.body;
369 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
370 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
373 // Add which for key events
374 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
375 event.which = event.charCode || event.keyCode;
378 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
379 if ( !event.metaKey && event.ctrlKey ) {
380 event.metaKey = event.ctrlKey;
383 // Add which for click: 1 == left; 2 == middle; 3 == right
384 // Note: button is not normalized, so don't use it
385 if ( !event.which && event.button !== undefined ) {
386 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
392 proxy: function( fn, proxy, thisObject ) {
393 if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) {
397 // FIXME: Should proxy be redefined to be applied with thisObject if defined?
398 proxy = proxy || function() { return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); };
399 // Set the guid of unique handler to the same of original handler, so it can be removed
400 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
401 // So proxy can be declared as an argument
407 // Make sure the ready event is setup
408 setup: jQuery.bindReady,
409 teardown: function() {}
413 add: function( proxy, data, namespaces, live ) {
414 jQuery.extend( proxy, data || {} );
416 proxy.guid += data.selector + data.live;
417 jQuery.event.add( this, data.live, liveHandler, data );
421 remove: function( namespaces ) {
422 if ( namespaces.length ) {
423 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
425 jQuery.each( (jQuery.data(this, "events").live || {}), function() {
426 if ( name.test(this.type) ) {
432 jQuery.event.remove( this, namespaces[0], liveHandler );
439 setup: function( data, namespaces, fn ) {
440 // We only want to do this special case on windows
441 if ( this.setInterval ) {
442 this.onbeforeunload = fn;
447 teardown: function( namespaces, fn ) {
448 if ( this.onbeforeunload === fn ) {
449 this.onbeforeunload = null;
456 jQuery.Event = function( src ){
457 // Allow instantiation without the 'new' keyword
458 if ( !this.preventDefault ) {
459 return new jQuery.Event( src );
463 if ( src && src.type ) {
464 this.originalEvent = src;
465 this.type = src.type;
471 // timeStamp is buggy for some events on Firefox(#3843)
472 // So we won't rely on the native value
473 this.timeStamp = now();
476 this[ expando ] = true;
479 function returnFalse() {
482 function returnTrue() {
486 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
487 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
488 jQuery.Event.prototype = {
489 preventDefault: function() {
490 this.isDefaultPrevented = returnTrue;
492 var e = this.originalEvent;
497 // if preventDefault exists run it on the original event
498 if ( e.preventDefault ) {
501 // otherwise set the returnValue property of the original event to false (IE)
502 e.returnValue = false;
504 stopPropagation: function() {
505 this.isPropagationStopped = returnTrue;
507 var e = this.originalEvent;
511 // if stopPropagation exists run it on the original event
512 if ( e.stopPropagation ) {
515 // otherwise set the cancelBubble property of the original event to true (IE)
516 e.cancelBubble = true;
518 stopImmediatePropagation: function(){
519 this.isImmediatePropagationStopped = returnTrue;
520 this.stopPropagation();
522 isDefaultPrevented: returnFalse,
523 isPropagationStopped: returnFalse,
524 isImmediatePropagationStopped: returnFalse
526 // Checks if an event happened on an element within another element
527 // Used in jQuery.event.special.mouseenter and mouseleave handlers
528 var withinElement = function( event ) {
529 // Check if mouse(over|out) are still within the same parent element
530 var parent = event.relatedTarget;
531 // Traverse up the tree
532 while ( parent && parent != this ) {
533 // Firefox sometimes assigns relatedTarget a XUL element
534 // which we cannot access the parentNode property of
535 try { parent = parent.parentNode; }
536 // assuming we've left the element since we most likely mousedover a xul element
540 if ( parent != this ) {
541 // set the correct event type
542 event.type = event.data;
543 // handle event if we actually just moused on to a non sub-element
544 jQuery.event.handle.apply( this, arguments );
549 // In case of event delegation, we only need to rename the event.type,
550 // liveHandler will take care of the rest.
551 delegate = function( event ) {
552 event.type = event.data;
553 jQuery.event.handle.apply( this, arguments );
556 // Create mouseenter and mouseleave events
558 mouseenter: "mouseover",
559 mouseleave: "mouseout"
560 }, function( orig, fix ) {
561 jQuery.event.special[ orig ] = {
562 setup: function(data){
563 jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
565 teardown: function(data){
566 jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
572 if ( !jQuery.support.submitBubbles ) {
574 jQuery.event.special.submit = {
575 setup: function( data, namespaces, fn ) {
576 if ( this.nodeName.toLowerCase() !== "form" ) {
577 jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
578 var elem = e.target, type = elem.type;
580 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
581 return trigger( "submit", this, arguments );
585 jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
586 var elem = e.target, type = elem.type;
588 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
589 return trigger( "submit", this, arguments );
595 remove: function( namespaces, fn ) {
596 jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
597 jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
603 // change delegation, happens here so we have bind.
604 if ( !jQuery.support.changeBubbles ) {
606 jQuery.event.special.change = {
608 click: function( e ) {
611 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) {
612 return trigger( "change", this, arguments );
615 return changeFilters.keyup.call( this, e );
617 keyup: function( e ) {
618 var elem = e.target, data, index = elem.selectedIndex + "";
620 if ( elem.nodeName.toLowerCase() === "select" ) {
621 data = jQuery.data( elem, "_change_data" );
622 jQuery.data( elem, "_change_data", index );
624 if ( (elem.type === "select-multiple" || data != null) && data !== index ) {
625 return trigger( "change", this, arguments );
629 beforeactivate: function( e ) {
632 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) {
633 return trigger( "change", this, arguments );
636 blur: function( e ) {
637 var elem = e.target, nodeName = elem.nodeName.toLowerCase();
639 if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password")))
640 && jQuery.data(elem, "_change_data") !== elem.value ) {
642 return trigger( "change", this, arguments );
645 focus: function( e ) {
646 var elem = e.target, nodeName = elem.nodeName.toLowerCase();
648 if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) {
649 jQuery.data( elem, "_change_data", elem.value );
653 setup: function( data, namespaces, fn ) {
654 for ( var type in changeFilters ) {
655 jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
658 // always want to listen for change for trigger
661 remove: function( namespaces, fn ) {
662 for ( var type in changeFilters ) {
663 jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
668 var changeFilters = jQuery.event.special.change.filters;
672 function trigger( type, elem, args ) {
674 return jQuery.event.handle.apply( elem, args );
677 // Create "bubbling" focus and blur events
678 if ( !jQuery.support.focusBubbles ) {
680 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){
681 event.special[ orig ] = {
683 jQuery.event.add( this, fix, ieHandler );
685 teardown: function() {
686 jQuery.event.remove( this, fix, ieHandler );
690 function ieHandler() {
691 arguments[0].type = orig;
692 return jQuery.event.handle.apply(this, arguments);
699 // TODO: make bind(), unbind() and one() DRY!
700 bind: function( type, data, fn, thisObject ) {
701 // Handle object literals
702 if ( typeof type === "object" ) {
703 for ( var key in type ) {
704 this.bind(key, data, type[key], fn);
709 if ( jQuery.isFunction( data ) ) {
714 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
715 return type === "unload" ? this.one(type, data, fn, thisObject) : this.each(function() {
716 jQuery.event.add( this, type, fn, data );
720 one: function( type, data, fn, thisObject ) {
721 // Handle object literals
722 if ( typeof type === "object" ) {
723 for ( var key in type ) {
724 this.one(key, data, type[key], fn);
729 if ( jQuery.isFunction( data ) ) {
734 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
735 var one = jQuery.event.proxy( fn, function( event ) {
736 jQuery( this ).unbind( event, one );
737 return fn.apply( this, arguments );
739 return this.each(function() {
740 jQuery.event.add( this, type, one, data );
744 unbind: function( type, fn ) {
745 // Handle object literals
746 if ( typeof type === "object" && !type.preventDefault ) {
747 for ( var key in type ) {
748 this.unbind(key, type[key]);
753 return this.each(function() {
754 jQuery.event.remove( this, type, fn );
758 trigger: function( type, data ) {
759 return this.each(function() {
760 jQuery.event.trigger( type, data, this );
764 triggerHandler: function( type, data ) {
766 var event = jQuery.Event( type );
767 event.preventDefault();
768 event.stopPropagation();
769 jQuery.event.trigger( event, data, this[0] );
774 toggle: function( fn ) {
775 // Save reference to arguments for access in closure
776 var args = arguments, i = 1;
778 // link all the functions, so any of them can unbind this click handler
779 while( i < args.length ) {
780 jQuery.event.proxy( fn, args[ i++ ] );
783 return this.click( jQuery.event.proxy( fn, function( event ) {
784 // Figure out which function to execute
785 var lastToggle = ( jQuery.data( this, 'lastToggle' + fn.guid ) || 0 ) % i;
786 jQuery.data( this, 'lastToggle' + fn.guid, lastToggle + 1 );
788 // Make sure that clicks stop
789 event.preventDefault();
791 // and execute the function
792 return args[ lastToggle ].apply( this, arguments ) || false;
796 hover: function( fnOver, fnOut ) {
797 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
800 live: function( type, data, fn, thisObject ) {
801 if ( jQuery.isFunction( data ) ) {
802 if ( fn !== undefined ) {
808 jQuery( this.context ).bind( liveConvert( type, this.selector ), {
809 data: data, selector: this.selector, live: type
814 die: function( type, fn ) {
815 jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
820 function liveHandler( event ) {
821 var stop = true, elems = [], selectors = [], args = arguments,
822 related, match, fn, elem, j, i, data,
823 live = jQuery.extend({}, jQuery.data( this, "events" ).live);
827 if ( fn.live === event.type ||
828 fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
831 if ( !(data.beforeFilter && data.beforeFilter[event.type] &&
832 !data.beforeFilter[event.type](event)) ) {
833 selectors.push( fn.selector );
840 match = jQuery( event.target ).closest( selectors, event.currentTarget );
842 for ( i = 0, l = match.length; i < l; i++ ) {
845 elem = match[i].elem;
848 if ( match[i].selector === fn.selector ) {
849 // Those two events require additional checking
850 if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
851 related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
854 if ( !related || related !== elem ) {
855 elems.push({ elem: elem, fn: fn });
861 for ( i = 0, l = elems.length; i < l; i++ ) {
863 event.currentTarget = match.elem;
864 event.data = match.fn.data;
865 if ( match.fn.apply( match.elem, args ) === false ) {
874 function liveConvert( type, selector ) {
875 return ["live", type, selector//.replace(/[^\w\s\.]/g, function(ch){ return "\\"+ch})
877 .replace(/ /g, "|")].join(".");
880 jQuery.each( ("blur focus load resize scroll unload click dblclick " +
881 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
882 "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
884 // Handle event binding
885 jQuery.fn[ name ] = function( fn ) {
886 return fn ? this.bind( name, fn ) : this.trigger( name );
890 // Prevent memory leaks in IE
891 // Window isn't included so as not to unbind existing unload events
893 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
895 jQuery( window ).bind( 'unload', function() {
896 for ( var id in jQuery.cache ) {
898 if ( id != 1 && jQuery.cache[ id ].handle ) {
899 jQuery.event.remove( jQuery.cache[ id ].handle.elem );