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 = jQuery.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 = jQuery.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
119 // Detach an event or set of events from an element
120 remove: function( elem, types, handler ) {
121 // don't do events on text and comment nodes
122 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
126 var events = jQuery.data( elem, "events" ), ret, type, fn;
129 // Unbind all events for the element
130 if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) {
131 for ( type in events ) {
132 this.remove( elem, type + (types || "") );
135 // types is actually an event object here
137 handler = types.handler;
141 // Handle multiple events separated by a space
142 // jQuery(...).unbind("mouseover mouseout", fn);
143 types = types.split(/\s+/);
145 while ( (type = types[ i++ ]) ) {
146 // Namespaced event handlers
147 var namespaces = type.split(".");
148 type = namespaces.shift();
149 var all = !namespaces.length,
150 cleaned = jQuery.map( namespaces.slice(0).sort(), fcleanup ),
151 namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
152 special = this.special[ type ] || {};
154 if ( events[ type ] ) {
155 // remove the given handler for the given type
157 fn = events[ type ][ handler.guid ];
158 delete events[ type ][ handler.guid ];
160 // remove all handlers for the given type
162 for ( var handle in events[ type ] ) {
163 // Handle the removal of namespaced events
164 if ( all || namespace.test( events[ type ][ handle ].type ) ) {
165 delete events[ type ][ handle ];
170 if ( special.remove ) {
171 special.remove.call( elem, namespaces, fn);
174 // remove generic event handler if no more handlers exist
175 for ( ret in events[ type ] ) {
179 if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
180 if ( elem.removeEventListener ) {
181 elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
182 } else if ( elem.detachEvent ) {
183 elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) );
187 delete events[ type ];
193 // Remove the expando if it's no longer used
194 for ( ret in events ) {
198 var handle = jQuery.data( elem, "handle" );
202 jQuery.removeData( elem, "events" );
203 jQuery.removeData( elem, "handle" );
208 // bubbling is internal
209 trigger: function( event, data, elem /*, bubbling */ ) {
210 // Event object or event type
211 var type = event.type || event,
212 bubbling = arguments[3];
215 event = typeof event === "object" ?
216 // jQuery.Event object
217 event[expando] ? event :
219 jQuery.extend( jQuery.Event(type), event ) :
220 // Just the event type (string)
223 if ( type.indexOf("!") >= 0 ) {
224 event.type = type = type.slice(0, -1);
225 event.exclusive = true;
228 // Handle a global trigger
230 // Don't bubble custom events when global (to avoid too much overhead)
231 event.stopPropagation();
233 // Only trigger if we've ever bound an event for it
234 if ( this.global[ type ] ) {
235 jQuery.each( jQuery.cache, function() {
236 if ( this.events && this.events[type] ) {
237 jQuery.event.trigger( event, data, this.handle.elem );
243 // Handle triggering a single element
245 // don't do events on text and comment nodes
246 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
250 // Clean up in case it is reused
251 event.result = undefined;
254 // Clone the incoming data, if any
255 data = jQuery.makeArray( data );
256 data.unshift( event );
259 event.currentTarget = elem;
261 // Trigger the event, it is assumed that "handle" is a function
262 var handle = jQuery.data( elem, "handle" );
264 handle.apply( elem, data );
267 var nativeFn, nativeHandler;
269 if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
270 nativeFn = elem[ type ];
271 nativeHandler = elem[ "on" + type ];
273 // prevent IE from throwing an error for some elements with some event types, see #3533
276 var isClick = jQuery.nodeName(elem, "a") && type === "click";
278 // Trigger the native events (except for clicks on links)
279 if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
280 this.triggered = true;
283 // prevent IE from throwing an error for some hidden elements
286 // Handle triggering native .onfoo handlers
287 } else if ( nativeHandler && elem[ "on" + type ].apply( elem, data ) === false ) {
288 event.result = false;
291 this.triggered = false;
293 if ( !event.isPropagationStopped() ) {
294 var parent = elem.parentNode || elem.ownerDocument;
296 jQuery.event.trigger( event, data, parent, true );
301 handle: function( event ) {
302 // returned undefined or false
305 event = arguments[0] = jQuery.event.fix( event || window.event );
306 event.currentTarget = this;
308 // Namespaced event handlers
309 var namespaces = event.type.split(".");
310 event.type = namespaces.shift();
312 // Cache this now, all = true means, any handler
313 all = !namespaces.length && !event.exclusive;
315 var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
317 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
319 for ( var j in handlers ) {
320 var handler = handlers[ j ];
322 // Filter the functions by class
323 if ( all || namespace.test(handler.type) ) {
324 // Pass in a reference to the handler function itself
325 // So that we can later remove it
326 event.handler = handler;
327 event.data = handler.data;
329 var ret = handler.apply( this, arguments );
331 if ( ret !== undefined ) {
333 if ( ret === false ) {
334 event.preventDefault();
335 event.stopPropagation();
339 if ( event.isImmediatePropagationStopped() ) {
349 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(" "),
351 fix: function( event ) {
352 if ( event[ expando ] ) {
356 // store a copy of the original event object
357 // and "clone" to set read-only properties
358 var originalEvent = event;
359 event = jQuery.Event( originalEvent );
361 for ( var i = this.props.length, prop; i; ) {
362 prop = this.props[ --i ];
363 event[ prop ] = originalEvent[ prop ];
366 // Fix target property, if necessary
367 if ( !event.target ) {
368 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
371 // check if target is a textnode (safari)
372 if ( event.target.nodeType === 3 ) {
373 event.target = event.target.parentNode;
376 // Add relatedTarget, if necessary
377 if ( !event.relatedTarget && event.fromElement ) {
378 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
381 // Calculate pageX/Y if missing and clientX/Y available
382 if ( event.pageX == null && event.clientX != null ) {
383 var doc = document.documentElement, body = document.body;
384 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
385 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
388 // Add which for key events
389 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
390 event.which = event.charCode || event.keyCode;
393 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
394 if ( !event.metaKey && event.ctrlKey ) {
395 event.metaKey = event.ctrlKey;
398 // Add which for click: 1 === left; 2 === middle; 3 === right
399 // Note: button is not normalized, so don't use it
400 if ( !event.which && event.button !== undefined ) {
401 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
411 // Make sure the ready event is setup
412 setup: jQuery.bindReady,
413 teardown: function() {}
417 add: function( proxy, data, namespaces, live ) {
418 jQuery.extend( proxy, data || {} );
420 proxy.guid += data.selector + data.live;
421 jQuery.event.add( this, data.live, liveHandler, data );
425 remove: function( namespaces ) {
426 if ( namespaces.length ) {
427 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
429 jQuery.each( (jQuery.data(this, "events").live || {}), function() {
430 if ( name.test(this.type) ) {
436 jQuery.event.remove( this, namespaces[0], liveHandler );
443 setup: function( data, namespaces, fn ) {
444 // We only want to do this special case on windows
445 if ( this.setInterval ) {
446 this.onbeforeunload = fn;
451 teardown: function( namespaces, fn ) {
452 if ( this.onbeforeunload === fn ) {
453 this.onbeforeunload = null;
460 jQuery.Event = function( src ) {
461 // Allow instantiation without the 'new' keyword
462 if ( !this.preventDefault ) {
463 return new jQuery.Event( src );
467 if ( src && src.type ) {
468 this.originalEvent = src;
469 this.type = src.type;
475 // timeStamp is buggy for some events on Firefox(#3843)
476 // So we won't rely on the native value
477 this.timeStamp = now();
480 this[ expando ] = true;
483 function returnFalse() {
486 function returnTrue() {
490 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
491 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
492 jQuery.Event.prototype = {
493 preventDefault: function() {
494 this.isDefaultPrevented = returnTrue;
496 var e = this.originalEvent;
501 // if preventDefault exists run it on the original event
502 if ( e.preventDefault ) {
505 // otherwise set the returnValue property of the original event to false (IE)
506 e.returnValue = false;
508 stopPropagation: function() {
509 this.isPropagationStopped = returnTrue;
511 var e = this.originalEvent;
515 // if stopPropagation exists run it on the original event
516 if ( e.stopPropagation ) {
519 // otherwise set the cancelBubble property of the original event to true (IE)
520 e.cancelBubble = true;
522 stopImmediatePropagation: function() {
523 this.isImmediatePropagationStopped = returnTrue;
524 this.stopPropagation();
526 isDefaultPrevented: returnFalse,
527 isPropagationStopped: returnFalse,
528 isImmediatePropagationStopped: returnFalse
531 // Checks if an event happened on an element within another element
532 // Used in jQuery.event.special.mouseenter and mouseleave handlers
533 var withinElement = function( event ) {
534 // Check if mouse(over|out) are still within the same parent element
535 var parent = event.relatedTarget;
537 // Traverse up the tree
538 while ( parent && parent !== this ) {
539 // Firefox sometimes assigns relatedTarget a XUL element
540 // which we cannot access the parentNode property of
542 parent = parent.parentNode;
544 // assuming we've left the element since we most likely mousedover a xul element
550 if ( parent !== this ) {
551 // set the correct event type
552 event.type = event.data;
554 // handle event if we actually just moused on to a non sub-element
555 jQuery.event.handle.apply( this, arguments );
560 // In case of event delegation, we only need to rename the event.type,
561 // liveHandler will take care of the rest.
562 delegate = function( event ) {
563 event.type = event.data;
564 jQuery.event.handle.apply( this, arguments );
567 // Create mouseenter and mouseleave events
569 mouseenter: "mouseover",
570 mouseleave: "mouseout"
571 }, function( orig, fix ) {
572 jQuery.event.special[ orig ] = {
573 setup: function( data ) {
574 jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
576 teardown: function( data ) {
577 jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
583 if ( !jQuery.support.submitBubbles ) {
585 jQuery.event.special.submit = {
586 setup: function( data, namespaces, fn ) {
587 if ( this.nodeName.toLowerCase() !== "form" ) {
588 jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
589 var elem = e.target, type = elem.type;
591 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
592 return trigger( "submit", this, arguments );
596 jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
597 var elem = e.target, type = elem.type;
599 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
600 return trigger( "submit", this, arguments );
606 remove: function( namespaces, fn ) {
607 jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
608 jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
614 // change delegation, happens here so we have bind.
615 if ( !jQuery.support.changeBubbles ) {
617 var formElems = /textarea|input|select/i;
619 function getVal( elem ) {
620 var type = elem.type, val = elem.value;
622 if ( type === "radio" || type === "checkbox" ) {
625 } else if ( type === "select-multiple" ) {
626 val = elem.selectedIndex > -1 ?
627 jQuery.map( elem.options, function( elem ) {
628 return elem.selected;
632 } else if ( elem.nodeName.toLowerCase() === "select" ) {
633 val = elem.selectedIndex;
639 function testChange( e ) {
640 var elem = e.target, data, val;
642 if ( !formElems.test( elem.nodeName ) || elem.readOnly ) {
646 data = jQuery.data( elem, "_change_data" );
649 if ( val === data ) {
653 // the current data will be also retrieved by beforeactivate
654 if ( e.type !== "focusout" || elem.type !== "radio" ) {
655 jQuery.data( elem, "_change_data", val );
658 if ( elem.type !== "select" && (data != null || val) ) {
660 return jQuery.event.trigger( e, arguments[1], this );
664 jQuery.event.special.change = {
666 focusout: testChange,
668 click: function( e ) {
669 var elem = e.target, type = elem.type;
671 if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
672 return testChange.call( this, e );
676 // Change has to be called before submit
677 // Keydown will be called before keypress, which is used in submit-event delegation
678 keydown: function( e ) {
679 var elem = e.target, type = elem.type;
681 if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
682 (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
683 type === "select-multiple" ) {
684 return testChange.call( this, e );
688 // Beforeactivate happens also before the previous element is blurred
689 // with this event you can't trigger a change event, but you can store
690 // information/focus[in] is not needed anymore
691 beforeactivate: function( e ) {
694 if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" ) {
695 jQuery.data( elem, "_change_data", getVal(elem) );
699 setup: function( data, namespaces, fn ) {
700 for ( var type in changeFilters ) {
701 jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
704 return formElems.test( this.nodeName );
706 remove: function( namespaces, fn ) {
707 for ( var type in changeFilters ) {
708 jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
711 return formElems.test( this.nodeName );
715 var changeFilters = jQuery.event.special.change.filters;
719 function trigger( type, elem, args ) {
721 return jQuery.event.handle.apply( elem, args );
724 // Create "bubbling" focus and blur events
725 if ( document.addEventListener ) {
726 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
727 jQuery.event.special[ fix ] = {
729 this.addEventListener( orig, handler, true );
731 teardown: function() {
732 this.removeEventListener( orig, handler, true );
736 function handler( e ) {
737 e = jQuery.event.fix( e );
739 return jQuery.event.handle.call( this, e );
744 jQuery.each(["bind", "one"], function( i, name ) {
745 jQuery.fn[ name ] = function( type, data, fn ) {
746 // Handle object literals
747 if ( typeof type === "object" ) {
748 for ( var key in type ) {
749 this[ name ](key, data, type[key], fn);
754 if ( jQuery.isFunction( data ) ) {
760 var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
761 jQuery( this ).unbind( event, handler );
762 return fn.apply( this, arguments );
765 return type === "unload" ? this.one(type, data, handler) : this.each(function() {
766 jQuery.event.add( this, type, handler, data );
772 unbind: function( type, fn ) {
773 // Handle object literals
774 if ( typeof type === "object" && !type.preventDefault ) {
775 for ( var key in type ) {
776 this.unbind(key, type[key]);
781 return this.each(function() {
782 jQuery.event.remove( this, type, fn );
785 trigger: function( type, data ) {
786 return this.each(function() {
787 jQuery.event.trigger( type, data, this );
791 triggerHandler: function( type, data ) {
793 var event = jQuery.Event( type );
794 event.preventDefault();
795 event.stopPropagation();
796 jQuery.event.trigger( event, data, this[0] );
801 toggle: function( fn ) {
802 // Save reference to arguments for access in closure
803 var args = arguments, i = 1;
805 // link all the functions, so any of them can unbind this click handler
806 while ( i < args.length ) {
807 jQuery.proxy( fn, args[ i++ ] );
810 return this.click( jQuery.proxy( fn, function( event ) {
811 // Figure out which function to execute
812 var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
813 jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
815 // Make sure that clicks stop
816 event.preventDefault();
818 // and execute the function
819 return args[ lastToggle ].apply( this, arguments ) || false;
823 hover: function( fnOver, fnOut ) {
824 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
827 live: function( type, data, fn ) {
828 if ( jQuery.isFunction( data ) ) {
833 jQuery( this.context ).bind( liveConvert( type, this.selector ), {
834 data: data, selector: this.selector, live: type
840 die: function( type, fn ) {
841 jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
846 function liveHandler( event ) {
847 var stop = true, elems = [], selectors = [], args = arguments,
848 related, match, fn, elem, j, i, data,
849 live = jQuery.extend({}, jQuery.data( this, "events" ).live);
853 if ( fn.live === event.type ||
854 fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
857 if ( !(data.beforeFilter && data.beforeFilter[event.type] &&
858 !data.beforeFilter[event.type](event)) ) {
859 selectors.push( fn.selector );
866 match = jQuery( event.target ).closest( selectors, event.currentTarget );
868 for ( i = 0, l = match.length; i < l; i++ ) {
871 elem = match[i].elem;
874 if ( match[i].selector === fn.selector ) {
875 // Those two events require additional checking
876 if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
877 related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
880 if ( !related || related !== elem ) {
881 elems.push({ elem: elem, fn: fn });
887 for ( i = 0, l = elems.length; i < l; i++ ) {
889 event.currentTarget = match.elem;
890 event.data = match.fn.data;
891 if ( match.fn.apply( match.elem, args ) === false ) {
900 function liveConvert( type, selector ) {
901 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "&")].join(".");
904 jQuery.each( ("blur focus load resize scroll unload click dblclick " +
905 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
906 "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
908 // Handle event binding
909 jQuery.fn[ name ] = function( fn ) {
910 return fn ? this.bind( name, fn ) : this.trigger( name );
913 if ( jQuery.fnAttr ) {
914 jQuery.fnAttr[ name ] = true;
918 // Prevent memory leaks in IE
919 // Window isn't included so as not to unbind existing unload events
921 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
922 if ( window.attachEvent && !window.addEventListener ) {
923 window.attachEvent("onunload", function() {
924 for ( var id in jQuery.cache ) {
925 if ( jQuery.cache[ id ].handle ) {
926 // Try/Catch is to handle iframes being unloaded, see #4280
928 jQuery.event.remove( jQuery.cache[ id ].handle.elem );