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 if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
257 nativeFn = elem[ type ];
258 nativeHandler = elem[ "on" + type ];
260 // prevent IE from throwing an error for some elements with some event types, see #3533
263 var isClick = jQuery.nodeName(elem, "a") && type === "click";
265 // Trigger the native events (except for clicks on links)
266 if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
267 this.triggered = true;
270 // prevent IE from throwing an error for some hidden elements
273 // Handle triggering native .onfoo handlers
274 } else if ( nativeHandler && elem[ "on" + type ].apply( elem, data ) === false ) {
275 event.result = false;
278 this.triggered = false;
280 if ( !event.isPropagationStopped() ) {
281 var parent = elem.parentNode || elem.ownerDocument;
283 jQuery.event.trigger( event, data, parent, true );
288 handle: function( event ) {
289 // returned undefined or false
292 event = arguments[0] = jQuery.event.fix( event || window.event );
293 event.currentTarget = this;
295 // Namespaced event handlers
296 var namespaces = event.type.split(".");
297 event.type = namespaces.shift();
299 // Cache this now, all = true means, any handler
300 all = !namespaces.length && !event.exclusive;
302 var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
304 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
306 for ( var j in handlers ) {
307 var handler = handlers[ j ];
309 // Filter the functions by class
310 if ( all || namespace.test(handler.type) ) {
311 // Pass in a reference to the handler function itself
312 // So that we can later remove it
313 event.handler = handler;
314 event.data = handler.data;
316 var ret = handler.apply( this, arguments );
318 if ( ret !== undefined ) {
320 if ( ret === false ) {
321 event.preventDefault();
322 event.stopPropagation();
326 if ( event.isImmediatePropagationStopped() ) {
336 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(" "),
338 fix: function( event ) {
339 if ( event[ expando ] ) {
343 // store a copy of the original event object
344 // and "clone" to set read-only properties
345 var originalEvent = event;
346 event = jQuery.Event( originalEvent );
348 for ( var i = this.props.length, prop; i; ) {
349 prop = this.props[ --i ];
350 event[ prop ] = originalEvent[ prop ];
353 // Fix target property, if necessary
354 if ( !event.target ) {
355 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
358 // check if target is a textnode (safari)
359 if ( event.target.nodeType === 3 ) {
360 event.target = event.target.parentNode;
363 // Add relatedTarget, if necessary
364 if ( !event.relatedTarget && event.fromElement ) {
365 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
368 // Calculate pageX/Y if missing and clientX/Y available
369 if ( event.pageX == null && event.clientX != null ) {
370 var doc = document.documentElement, body = document.body;
371 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
372 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
375 // Add which for key events
376 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
377 event.which = event.charCode || event.keyCode;
380 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
381 if ( !event.metaKey && event.ctrlKey ) {
382 event.metaKey = event.ctrlKey;
385 // Add which for click: 1 == left; 2 == middle; 3 == right
386 // Note: button is not normalized, so don't use it
387 if ( !event.which && event.button !== undefined ) {
388 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
394 proxy: function( fn, proxy, thisObject ) {
395 if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) {
399 // FIXME: Should proxy be redefined to be applied with thisObject if defined?
400 proxy = proxy || function() { return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); };
401 // Set the guid of unique handler to the same of original handler, so it can be removed
402 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
403 // So proxy can be declared as an argument
409 // Make sure the ready event is setup
410 setup: jQuery.bindReady,
411 teardown: function() {}
415 add: function( proxy, data, namespaces, live ) {
416 jQuery.extend( proxy, data || {} );
418 proxy.guid += data.selector + data.live;
419 jQuery.event.add( this, data.live, liveHandler, data );
423 remove: function( namespaces ) {
424 if ( namespaces.length ) {
425 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
427 jQuery.each( (jQuery.data(this, "events").live || {}), function() {
428 if ( name.test(this.type) ) {
434 jQuery.event.remove( this, namespaces[0], liveHandler );
441 setup: function( data, namespaces, fn ) {
442 // We only want to do this special case on windows
443 if ( this.setInterval ) {
444 this.onbeforeunload = fn;
449 teardown: function( namespaces, fn ) {
450 if ( this.onbeforeunload === fn ) {
451 this.onbeforeunload = null;
458 jQuery.Event = function( src ){
459 // Allow instantiation without the 'new' keyword
460 if ( !this.preventDefault ) {
461 return new jQuery.Event( src );
465 if ( src && src.type ) {
466 this.originalEvent = src;
467 this.type = src.type;
473 // timeStamp is buggy for some events on Firefox(#3843)
474 // So we won't rely on the native value
475 this.timeStamp = now();
478 this[ expando ] = true;
481 function returnFalse() {
484 function returnTrue() {
488 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
489 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
490 jQuery.Event.prototype = {
491 preventDefault: function() {
492 this.isDefaultPrevented = returnTrue;
494 var e = this.originalEvent;
499 // if preventDefault exists run it on the original event
500 if ( e.preventDefault ) {
503 // otherwise set the returnValue property of the original event to false (IE)
504 e.returnValue = false;
506 stopPropagation: function() {
507 this.isPropagationStopped = returnTrue;
509 var e = this.originalEvent;
513 // if stopPropagation exists run it on the original event
514 if ( e.stopPropagation ) {
517 // otherwise set the cancelBubble property of the original event to true (IE)
518 e.cancelBubble = true;
520 stopImmediatePropagation: function(){
521 this.isImmediatePropagationStopped = returnTrue;
522 this.stopPropagation();
524 isDefaultPrevented: returnFalse,
525 isPropagationStopped: returnFalse,
526 isImmediatePropagationStopped: returnFalse
528 // Checks if an event happened on an element within another element
529 // Used in jQuery.event.special.mouseenter and mouseleave handlers
530 var withinElement = function( event ) {
531 // Check if mouse(over|out) are still within the same parent element
532 var parent = event.relatedTarget;
533 // Traverse up the tree
534 while ( parent && parent != this ) {
535 // Firefox sometimes assigns relatedTarget a XUL element
536 // which we cannot access the parentNode property of
537 try { parent = parent.parentNode; }
538 // assuming we've left the element since we most likely mousedover a xul element
542 if ( parent != this ) {
543 // set the correct event type
544 event.type = event.data;
545 // handle event if we actually just moused on to a non sub-element
546 jQuery.event.handle.apply( this, arguments );
551 // In case of event delegation, we only need to rename the event.type,
552 // liveHandler will take care of the rest.
553 delegate = function( event ) {
554 event.type = event.data;
555 jQuery.event.handle.apply( this, arguments );
558 // Create mouseenter and mouseleave events
560 mouseenter: "mouseover",
561 mouseleave: "mouseout"
562 }, function( orig, fix ) {
563 jQuery.event.special[ orig ] = {
564 setup: function(data){
565 jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
567 teardown: function(data){
568 jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
574 if ( !jQuery.support.submitBubbles ) {
576 jQuery.event.special.submit = {
577 setup: function( data, namespaces, fn ) {
578 if ( this.nodeName.toLowerCase() !== "form" ) {
579 jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
580 var elem = e.target, type = elem.type;
582 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
583 return trigger( "submit", this, arguments );
587 jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
588 var elem = e.target, type = elem.type;
590 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
591 return trigger( "submit", this, arguments );
597 remove: function( namespaces, fn ) {
598 jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
599 jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
605 // change delegation, happens here so we have bind.
606 if ( !jQuery.support.changeBubbles ) {
608 jQuery.event.special.change = {
610 click: function( e ) {
613 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) {
614 return trigger( "change", this, arguments );
617 return changeFilters.keyup.call( this, e );
619 keyup: function( e ) {
620 var elem = e.target, data, index = elem.selectedIndex + "";
622 if ( elem.nodeName.toLowerCase() === "select" ) {
623 data = jQuery.data( elem, "_change_data" );
624 jQuery.data( elem, "_change_data", index );
626 if ( (elem.type === "select-multiple" || data != null) && data !== index ) {
627 return trigger( "change", this, arguments );
631 beforeactivate: function( e ) {
634 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) {
635 return trigger( "change", this, arguments );
638 blur: function( e ) {
639 var elem = e.target, nodeName = elem.nodeName.toLowerCase();
641 if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password")))
642 && jQuery.data(elem, "_change_data") !== elem.value ) {
644 return trigger( "change", this, arguments );
647 focus: function( e ) {
648 var elem = e.target, nodeName = elem.nodeName.toLowerCase();
650 if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) {
651 jQuery.data( elem, "_change_data", elem.value );
655 setup: function( data, namespaces, fn ) {
656 for ( var type in changeFilters ) {
657 jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
660 // always want to listen for change for trigger
663 remove: function( namespaces, fn ) {
664 for ( var type in changeFilters ) {
665 jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
670 var changeFilters = jQuery.event.special.change.filters;
674 function trigger( type, elem, args ) {
676 return jQuery.event.handle.apply( elem, args );
679 // Create "bubbling" focus and blur events
680 if ( !jQuery.support.focusBubbles ) {
682 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){
683 event.special[ orig ] = {
685 jQuery.event.add( this, fix, ieHandler );
687 teardown: function() {
688 jQuery.event.remove( this, fix, ieHandler );
692 function ieHandler() {
693 arguments[0].type = orig;
694 return jQuery.event.handle.apply(this, arguments);
701 // TODO: make bind(), unbind() and one() DRY!
702 bind: function( type, data, fn, thisObject ) {
703 // Handle object literals
704 if ( typeof type === "object" ) {
705 for ( var key in type ) {
706 this.bind(key, data, type[key], fn);
711 if ( jQuery.isFunction( data ) ) {
716 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
717 return type === "unload" ? this.one(type, data, fn, thisObject) : this.each(function() {
718 jQuery.event.add( this, type, fn, data );
722 one: function( type, data, fn, thisObject ) {
723 // Handle object literals
724 if ( typeof type === "object" ) {
725 for ( var key in type ) {
726 this.one(key, data, type[key], fn);
731 if ( jQuery.isFunction( data ) ) {
736 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
737 var one = jQuery.event.proxy( fn, function( event ) {
738 jQuery( this ).unbind( event, one );
739 return fn.apply( this, arguments );
741 return this.each(function() {
742 jQuery.event.add( this, type, one, data );
746 unbind: function( type, fn ) {
747 // Handle object literals
748 if ( typeof type === "object" && !type.preventDefault ) {
749 for ( var key in type ) {
750 this.unbind(key, type[key]);
755 return this.each(function() {
756 jQuery.event.remove( this, type, fn );
760 trigger: function( type, data ) {
761 return this.each(function() {
762 jQuery.event.trigger( type, data, this );
766 triggerHandler: function( type, data ) {
768 var event = jQuery.Event( type );
769 event.preventDefault();
770 event.stopPropagation();
771 jQuery.event.trigger( event, data, this[0] );
776 toggle: function( fn ) {
777 // Save reference to arguments for access in closure
778 var args = arguments, i = 1;
780 // link all the functions, so any of them can unbind this click handler
781 while( i < args.length ) {
782 jQuery.event.proxy( fn, args[ i++ ] );
785 return this.click( jQuery.event.proxy( fn, function( event ) {
786 // Figure out which function to execute
787 var lastToggle = ( jQuery.data( this, 'lastToggle' + fn.guid ) || 0 ) % i;
788 jQuery.data( this, 'lastToggle' + fn.guid, lastToggle + 1 );
790 // Make sure that clicks stop
791 event.preventDefault();
793 // and execute the function
794 return args[ lastToggle ].apply( this, arguments ) || false;
798 hover: function( fnOver, fnOut ) {
799 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
802 live: function( type, data, fn, thisObject ) {
803 if ( jQuery.isFunction( data ) ) {
804 if ( fn !== undefined ) {
810 jQuery( this.context ).bind( liveConvert( type, this.selector ), {
811 data: data, selector: this.selector, live: type
816 die: function( type, fn ) {
817 jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
822 function liveHandler( event ) {
823 var stop = true, elems = [], selectors = [], args = arguments,
824 related, match, fn, elem, j, i, data,
825 live = jQuery.extend({}, jQuery.data( this, "events" ).live);
829 if ( fn.live === event.type ||
830 fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
833 if ( !(data.beforeFilter && data.beforeFilter[event.type] &&
834 !data.beforeFilter[event.type](event)) ) {
835 selectors.push( fn.selector );
842 match = jQuery( event.target ).closest( selectors, event.currentTarget );
844 for ( i = 0, l = match.length; i < l; i++ ) {
847 elem = match[i].elem;
850 if ( match[i].selector === fn.selector ) {
851 // Those two events require additional checking
852 if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
853 related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
856 if ( !related || related !== elem ) {
857 elems.push({ elem: elem, fn: fn });
863 for ( i = 0, l = elems.length; i < l; i++ ) {
865 event.currentTarget = match.elem;
866 event.data = match.fn.data;
867 if ( match.fn.apply( match.elem, args ) === false ) {
876 function liveConvert( type, selector ) {
877 return ["live", type, selector.replace(/\./g, "`").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 );
889 if ( jQuery.fnAttr ) {
890 jQuery.fnAttr[ name ] = true;
894 // Prevent memory leaks in IE
895 // Window isn't included so as not to unbind existing unload events
897 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
899 jQuery( window ).bind( 'unload', function() {
900 for ( var id in jQuery.cache ) {
902 if ( id != 1 && jQuery.cache[ id ].handle ) {
903 // Try/Catch is to handle iframes being unloaded, see #4280
905 jQuery.event.remove( jQuery.cache[ id ].handle.elem );