1 var fcleanup = function( nm ) {
2 return nm.replace(/[^\w\s\.\|`]/g, function( ch ) {
8 * A number of helper functions used for managing events.
9 * Many of the ideas behind this code originated from
10 * Dean Edwards' addEvent library.
14 // Bind an event to an element
15 // Original by Dean Edwards
16 add: function( elem, types, handler, data ) {
17 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
21 // For whatever reason, IE has trouble passing the window object
22 // around, causing it to be cloned in the process
23 if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
27 // Make sure that the function being executed has a unique ID
28 if ( !handler.guid ) {
29 handler.guid = this.guid++;
32 // if data is passed, bind to handler
33 if ( data !== undefined ) {
34 // Create temporary function pointer to original handler
37 // Create unique handler function, wrapped around original handler
38 handler = this.proxy( fn );
40 // Store data in unique handler
44 // Init the element's event structure
45 var events = jQuery.data( elem, "events" ) || jQuery.data( elem, "events", {} ),
46 handle = jQuery.data( elem, "handle" ), eventHandle;
49 eventHandle = function() {
50 // Handle the second event of a trigger and when
51 // an event is called after a page has unloaded
52 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
53 jQuery.event.handle.apply( eventHandle.elem, arguments ) :
57 handle = jQuery.data( elem, "handle", eventHandle );
60 // Add elem as a property of the handle function
61 // This is to prevent a memory leak with non-native
65 // Handle multiple events separated by a space
66 // jQuery(...).bind("mouseover mouseout", fn);
67 types = types.split( /\s+/ );
69 while ( (type = types[ i++ ]) ) {
70 // Namespaced event handlers
71 var namespaces = type.split(".");
72 type = namespaces.shift();
73 handler.type = namespaces.slice(0).sort().join(".");
75 // Get the current list of functions bound to this event
76 var handlers = events[ type ],
77 special = this.special[ type ] || {};
81 // Init the event handler queue
83 handlers = events[ type ] = {};
85 // Check for a special event handler
86 // Only use addEventListener/attachEvent if the special
87 // events handler returns false
88 if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) {
89 // Bind the global event handler to the element
90 if ( elem.addEventListener ) {
91 elem.addEventListener( type, handle, false );
92 } else if ( elem.attachEvent ) {
93 elem.attachEvent( "on" + type, handle );
99 var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers );
100 if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
101 modifiedHandler.guid = modifiedHandler.guid || handler.guid;
102 handler = modifiedHandler;
106 // Add the function to the element's handler list
107 handlers[ handler.guid ] = handler;
109 // Keep track of which events have been used, for global triggering
110 this.global[ type ] = true;
113 // Nullify elem to prevent memory leaks in IE
120 // Detach an event or set of events from an element
121 remove: function( elem, types, handler ) {
122 // don't do events on text and comment nodes
123 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
127 var events = jQuery.data( elem, "events" ), ret, type, fn;
130 // Unbind all events for the element
131 if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) {
132 for ( type in events ) {
133 this.remove( elem, type + (types || "") );
136 // types is actually an event object here
138 handler = types.handler;
142 // Handle multiple events separated by a space
143 // jQuery(...).unbind("mouseover mouseout", fn);
144 types = types.split(/\s+/);
146 while ( (type = types[ i++ ]) ) {
147 // Namespaced event handlers
148 var namespaces = type.split(".");
149 type = namespaces.shift();
150 var all = !namespaces.length,
151 cleaned = jQuery.map( namespaces.slice(0).sort(), fcleanup ),
152 namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
153 special = this.special[ type ] || {};
155 if ( events[ type ] ) {
156 // remove the given handler for the given type
158 fn = events[ type ][ handler.guid ];
159 delete events[ type ][ handler.guid ];
161 // remove all handlers for the given type
163 for ( var handle in events[ type ] ) {
164 // Handle the removal of namespaced events
165 if ( all || namespace.test( events[ type ][ handle ].type ) ) {
166 delete events[ type ][ handle ];
171 if ( special.remove ) {
172 special.remove.call( elem, namespaces, fn);
175 // remove generic event handler if no more handlers exist
176 for ( ret in events[ type ] ) {
180 if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
181 if ( elem.removeEventListener ) {
182 elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
183 } else if ( elem.detachEvent ) {
184 elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) );
188 delete events[ type ];
194 // Remove the expando if it's no longer used
195 for ( ret in events ) {
199 var handle = jQuery.data( elem, "handle" );
203 jQuery.removeData( elem, "events" );
204 jQuery.removeData( elem, "handle" );
209 // bubbling is internal
210 trigger: function( event, data, elem /*, bubbling */ ) {
211 // Event object or event type
212 var type = event.type || event,
213 bubbling = arguments[3];
216 event = typeof event === "object" ?
217 // jQuery.Event object
218 event[expando] ? event :
220 jQuery.extend( jQuery.Event(type), event ) :
221 // Just the event type (string)
224 if ( type.indexOf("!") >= 0 ) {
225 event.type = type = type.slice(0, -1);
226 event.exclusive = true;
229 // Handle a global trigger
231 // Don't bubble custom events when global (to avoid too much overhead)
232 event.stopPropagation();
234 // Only trigger if we've ever bound an event for it
235 if ( this.global[ type ] ) {
236 jQuery.each( jQuery.cache, function() {
237 if ( this.events && this.events[type] ) {
238 jQuery.event.trigger( event, data, this.handle.elem );
244 // Handle triggering a single element
246 // don't do events on text and comment nodes
247 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
251 // Clean up in case it is reused
252 event.result = undefined;
255 // Clone the incoming data, if any
256 data = jQuery.makeArray( data );
257 data.unshift( event );
260 event.currentTarget = elem;
262 // Trigger the event, it is assumed that "handle" is a function
263 var handle = jQuery.data( elem, "handle" );
265 handle.apply( elem, data );
268 var nativeFn, nativeHandler;
270 if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
271 nativeFn = elem[ type ];
272 nativeHandler = elem[ "on" + type ];
274 // prevent IE from throwing an error for some elements with some event types, see #3533
277 var isClick = jQuery.nodeName(elem, "a") && type === "click";
279 // Trigger the native events (except for clicks on links)
280 if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
281 this.triggered = true;
284 // prevent IE from throwing an error for some hidden elements
287 // Handle triggering native .onfoo handlers
288 } else if ( nativeHandler && elem[ "on" + type ].apply( elem, data ) === false ) {
289 event.result = false;
292 this.triggered = false;
294 if ( !event.isPropagationStopped() ) {
295 var parent = elem.parentNode || elem.ownerDocument;
297 jQuery.event.trigger( event, data, parent, true );
302 handle: function( event ) {
303 // returned undefined or false
306 event = arguments[0] = jQuery.event.fix( event || window.event );
307 event.currentTarget = this;
309 // Namespaced event handlers
310 var namespaces = event.type.split(".");
311 event.type = namespaces.shift();
313 // Cache this now, all = true means, any handler
314 all = !namespaces.length && !event.exclusive;
316 var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
318 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
320 for ( var j in handlers ) {
321 var handler = handlers[ j ];
323 // Filter the functions by class
324 if ( all || namespace.test(handler.type) ) {
325 // Pass in a reference to the handler function itself
326 // So that we can later remove it
327 event.handler = handler;
328 event.data = handler.data;
330 var ret = handler.apply( this, arguments );
332 if ( ret !== undefined ) {
334 if ( ret === false ) {
335 event.preventDefault();
336 event.stopPropagation();
340 if ( event.isImmediatePropagationStopped() ) {
350 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(" "),
352 fix: function( event ) {
353 if ( event[ expando ] ) {
357 // store a copy of the original event object
358 // and "clone" to set read-only properties
359 var originalEvent = event;
360 event = jQuery.Event( originalEvent );
362 for ( var i = this.props.length, prop; i; ) {
363 prop = this.props[ --i ];
364 event[ prop ] = originalEvent[ prop ];
367 // Fix target property, if necessary
368 if ( !event.target ) {
369 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
372 // check if target is a textnode (safari)
373 if ( event.target.nodeType === 3 ) {
374 event.target = event.target.parentNode;
377 // Add relatedTarget, if necessary
378 if ( !event.relatedTarget && event.fromElement ) {
379 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
382 // Calculate pageX/Y if missing and clientX/Y available
383 if ( event.pageX == null && event.clientX != null ) {
384 var doc = document.documentElement, body = document.body;
385 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
386 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
389 // Add which for key events
390 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
391 event.which = event.charCode || event.keyCode;
394 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
395 if ( !event.metaKey && event.ctrlKey ) {
396 event.metaKey = event.ctrlKey;
399 // Add which for click: 1 === left; 2 === middle; 3 === right
400 // Note: button is not normalized, so don't use it
401 if ( !event.which && event.button !== undefined ) {
402 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
408 proxy: function( fn, proxy, thisObject ) {
409 if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) {
414 // FIXME: Should proxy be redefined to be applied with thisObject if defined?
415 proxy = proxy || function() {
416 return fn.apply( thisObject !== undefined ? thisObject : this, arguments );
419 // Set the guid of unique handler to the same of original handler, so it can be removed
420 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
422 // So proxy can be declared as an argument
428 // Make sure the ready event is setup
429 setup: jQuery.bindReady,
430 teardown: function() {}
434 add: function( proxy, data, namespaces, live ) {
435 jQuery.extend( proxy, data || {} );
437 proxy.guid += data.selector + data.live;
438 jQuery.event.add( this, data.live, liveHandler, data );
442 remove: function( namespaces ) {
443 if ( namespaces.length ) {
444 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
446 jQuery.each( (jQuery.data(this, "events").live || {}), function() {
447 if ( name.test(this.type) ) {
453 jQuery.event.remove( this, namespaces[0], liveHandler );
460 setup: function( data, namespaces, fn ) {
461 // We only want to do this special case on windows
462 if ( this.setInterval ) {
463 this.onbeforeunload = fn;
468 teardown: function( namespaces, fn ) {
469 if ( this.onbeforeunload === fn ) {
470 this.onbeforeunload = null;
477 jQuery.Event = function( src ) {
478 // Allow instantiation without the 'new' keyword
479 if ( !this.preventDefault ) {
480 return new jQuery.Event( src );
484 if ( src && src.type ) {
485 this.originalEvent = src;
486 this.type = src.type;
492 // timeStamp is buggy for some events on Firefox(#3843)
493 // So we won't rely on the native value
494 this.timeStamp = now();
497 this[ expando ] = true;
500 function returnFalse() {
503 function returnTrue() {
507 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
508 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
509 jQuery.Event.prototype = {
510 preventDefault: function() {
511 this.isDefaultPrevented = returnTrue;
513 var e = this.originalEvent;
518 // if preventDefault exists run it on the original event
519 if ( e.preventDefault ) {
522 // otherwise set the returnValue property of the original event to false (IE)
523 e.returnValue = false;
525 stopPropagation: function() {
526 this.isPropagationStopped = returnTrue;
528 var e = this.originalEvent;
532 // if stopPropagation exists run it on the original event
533 if ( e.stopPropagation ) {
536 // otherwise set the cancelBubble property of the original event to true (IE)
537 e.cancelBubble = true;
539 stopImmediatePropagation: function() {
540 this.isImmediatePropagationStopped = returnTrue;
541 this.stopPropagation();
543 isDefaultPrevented: returnFalse,
544 isPropagationStopped: returnFalse,
545 isImmediatePropagationStopped: returnFalse
548 // Checks if an event happened on an element within another element
549 // Used in jQuery.event.special.mouseenter and mouseleave handlers
550 var withinElement = function( event ) {
551 // Check if mouse(over|out) are still within the same parent element
552 var parent = event.relatedTarget;
554 // Traverse up the tree
555 while ( parent && parent !== this ) {
556 // Firefox sometimes assigns relatedTarget a XUL element
557 // which we cannot access the parentNode property of
559 parent = parent.parentNode;
561 // assuming we've left the element since we most likely mousedover a xul element
567 if ( parent !== this ) {
568 // set the correct event type
569 event.type = event.data;
571 // handle event if we actually just moused on to a non sub-element
572 jQuery.event.handle.apply( this, arguments );
577 // In case of event delegation, we only need to rename the event.type,
578 // liveHandler will take care of the rest.
579 delegate = function( event ) {
580 event.type = event.data;
581 jQuery.event.handle.apply( this, arguments );
584 // Create mouseenter and mouseleave events
586 mouseenter: "mouseover",
587 mouseleave: "mouseout"
588 }, function( orig, fix ) {
589 jQuery.event.special[ orig ] = {
590 setup: function( data ) {
591 jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
593 teardown: function( data ) {
594 jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
600 if ( !jQuery.support.submitBubbles ) {
602 jQuery.event.special.submit = {
603 setup: function( data, namespaces, fn ) {
604 if ( this.nodeName.toLowerCase() !== "form" ) {
605 jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
606 var elem = e.target, type = elem.type;
608 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
609 return trigger( "submit", this, arguments );
613 jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
614 var elem = e.target, type = elem.type;
616 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
617 return trigger( "submit", this, arguments );
623 remove: function( namespaces, fn ) {
624 jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
625 jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
631 // change delegation, happens here so we have bind.
632 if ( !jQuery.support.changeBubbles ) {
634 var formElems = /textarea|input|select/i;
636 function getVal( elem ) {
637 var type = elem.type, val = elem.value;
639 if ( type === "radio" || type === "checkbox" ) {
642 } else if ( type === "select-multiple" ) {
643 val = elem.selectedIndex > -1 ?
644 jQuery.map( elem.options, function( elem ) {
645 return elem.selected;
649 } else if ( elem.nodeName.toLowerCase() === "select" ) {
650 val = elem.selectedIndex;
656 function testChange( e ) {
657 var elem = e.target, data, val;
659 if ( !formElems.test( elem.nodeName ) || elem.readOnly ) {
663 data = jQuery.data( elem, "_change_data" );
666 if ( val === data ) {
670 // the current data will be also retrieved by beforeactivate
671 if ( e.type !== "focusout" || elem.type !== "radio" ) {
672 jQuery.data( elem, "_change_data", val );
675 if ( elem.type !== "select" && (data != null || val) ) {
677 return jQuery.event.trigger( e, arguments[1], this );
681 jQuery.event.special.change = {
683 focusout: testChange,
685 click: function( e ) {
686 var elem = e.target, type = elem.type;
688 if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
689 return testChange.call( this, e );
693 // Change has to be called before submit
694 // Keydown will be called before keypress, which is used in submit-event delegation
695 keydown: function( e ) {
696 var elem = e.target, type = elem.type;
698 if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
699 (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
700 type === "select-multiple" ) {
701 return testChange.call( this, e );
705 // Beforeactivate happens also before the previous element is blurred
706 // with this event you can't trigger a change event, but you can store
707 // information/focus[in] is not needed anymore
708 beforeactivate: function( e ) {
711 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" ) {
712 jQuery.data( elem, "_change_data", getVal(elem) );
716 setup: function( data, namespaces, fn ) {
717 for ( var type in changeFilters ) {
718 jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
721 return formElems.test( this.nodeName );
723 remove: function( namespaces, fn ) {
724 for ( var type in changeFilters ) {
725 jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
728 return formElems.test( this.nodeName );
732 var changeFilters = jQuery.event.special.change.filters;
736 function trigger( type, elem, args ) {
738 return jQuery.event.handle.apply( elem, args );
741 // Create "bubbling" focus and blur events
742 if ( document.addEventListener ) {
743 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
744 jQuery.event.special[ fix ] = {
746 this.addEventListener( orig, handler, true );
748 teardown: function() {
749 this.removeEventListener( orig, handler, true );
753 function handler( e ) {
754 e = jQuery.event.fix( e );
756 return jQuery.event.handle.call( this, e );
761 jQuery.each(["bind", "one"], function( i, name ) {
762 jQuery.fn[ name ] = function( type, data, fn, thisObject ) {
763 // Handle object literals
764 if ( typeof type === "object" ) {
765 for ( var key in type ) {
766 this[ name ](key, data, type[key], fn);
771 if ( jQuery.isFunction( data ) ) {
776 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
777 var handler = name === "one" ? jQuery.event.proxy( fn, function( event ) {
778 jQuery( this ).unbind( event, handler );
779 return fn.apply( this, arguments );
781 return type === "unload" ? this.one(type, data, handler, thisObject) : this.each(function() {
782 jQuery.event.add( this, type, handler, data );
788 unbind: function( type, fn ) {
789 // Handle object literals
790 if ( typeof type === "object" && !type.preventDefault ) {
791 for ( var key in type ) {
792 this.unbind(key, type[key]);
797 return this.each(function() {
798 jQuery.event.remove( this, type, fn );
801 trigger: function( type, data ) {
802 return this.each(function() {
803 jQuery.event.trigger( type, data, this );
807 triggerHandler: function( type, data ) {
809 var event = jQuery.Event( type );
810 event.preventDefault();
811 event.stopPropagation();
812 jQuery.event.trigger( event, data, this[0] );
817 toggle: function( fn ) {
818 // Save reference to arguments for access in closure
819 var args = arguments, i = 1;
821 // link all the functions, so any of them can unbind this click handler
822 while ( i < args.length ) {
823 jQuery.event.proxy( fn, args[ i++ ] );
826 return this.click( jQuery.event.proxy( fn, function( event ) {
827 // Figure out which function to execute
828 var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
829 jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
831 // Make sure that clicks stop
832 event.preventDefault();
834 // and execute the function
835 return args[ lastToggle ].apply( this, arguments ) || false;
839 hover: function( fnOver, fnOut ) {
840 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
843 live: function( type, data, fn, thisObject ) {
844 if ( jQuery.isFunction( data ) ) {
845 if ( fn !== undefined ) {
851 jQuery( this.context ).bind( liveConvert( type, this.selector ), {
852 data: data, selector: this.selector, live: type
857 die: function( type, fn ) {
858 jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
863 function liveHandler( event ) {
864 var stop = true, elems = [], selectors = [], args = arguments,
865 related, match, fn, elem, j, i, data,
866 live = jQuery.extend({}, jQuery.data( this, "events" ).live);
870 if ( fn.live === event.type ||
871 fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
874 if ( !(data.beforeFilter && data.beforeFilter[event.type] &&
875 !data.beforeFilter[event.type](event)) ) {
876 selectors.push( fn.selector );
883 match = jQuery( event.target ).closest( selectors, event.currentTarget );
885 for ( i = 0, l = match.length; i < l; i++ ) {
888 elem = match[i].elem;
891 if ( match[i].selector === fn.selector ) {
892 // Those two events require additional checking
893 if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
894 related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
897 if ( !related || related !== elem ) {
898 elems.push({ elem: elem, fn: fn });
904 for ( i = 0, l = elems.length; i < l; i++ ) {
906 event.currentTarget = match.elem;
907 event.data = match.fn.data;
908 if ( match.fn.apply( match.elem, args ) === false ) {
917 function liveConvert( type, selector ) {
918 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "&")].join(".");
921 jQuery.each( ("blur focus load resize scroll unload click dblclick " +
922 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
923 "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
925 // Handle event binding
926 jQuery.fn[ name ] = function( fn ) {
927 return fn ? this.bind( name, fn ) : this.trigger( name );
930 if ( jQuery.fnAttr ) {
931 jQuery.fnAttr[ name ] = true;
935 // Prevent memory leaks in IE
936 // Window isn't included so as not to unbind existing unload events
938 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
939 if ( window.attachEvent && !window.addEventListener ) {
940 window.attachEvent("onunload", function() {
941 for ( var id in jQuery.cache ) {
942 if ( jQuery.cache[ id ].handle ) {
943 // Try/Catch is to handle iframes being unloaded, see #4280
945 jQuery.event.remove( jQuery.cache[ id ].handle.elem );