X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=src%2Fevent.js;h=588a7979ac5a5443b5834d667faad68b1ae88267;hb=b3ccf2f2881d9ad988aba1cf3ccffb810063ab29;hp=018d4d9809929e27bf36224f0721c08251412414;hpb=638c093b857197c0b25bd8b1ba5a9b00f82b39d5;p=jquery.git diff --git a/src/event.js b/src/event.js index 018d4d9..588a797 100644 --- a/src/event.js +++ b/src/event.js @@ -1,3 +1,10 @@ +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; + /* * A number of helper functions used for managing events. * Many of the ideas behind this code originated from @@ -18,177 +25,238 @@ jQuery.event = { elem = window; } - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) { - handler.guid = this.guid++; + if ( handler === false ) { + handler = returnFalse; } - // if data is passed, bind to handler - if ( data !== undefined ) { - // Create temporary function pointer to original handler - var fn = handler; + var handleObjIn, handleObj; - // Create unique handler function, wrapped around original handler - handler = this.proxy( fn ); + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } - // Store data in unique handler - handler.data = data; + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; } // Init the element's event structure - var events = jQuery.data( elem, "events" ) || jQuery.data( elem, "events", {} ), - handle = jQuery.data( elem, "handle" ) || jQuery.data( elem, "handle", function eventHandle() { + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { // Handle the second event of a trigger and when // an event is called after a page has unloaded return typeof jQuery !== "undefined" && !jQuery.event.triggered ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; - }); + }; + } + // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native - // event in IE. - handle.elem = elem; + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); - types = types.split( /\s+/ ); - var type, i=0; + types = types.split(" "); + + var type, i = 0, namespaces; + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + // Namespaced event handlers - var namespaces = type.split("."); - type = namespaces.shift(); - handler.type = namespaces.slice(0).sort().join("."); + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } // Get the current list of functions bound to this event var handlers = events[ type ], - special = this.special[ type ] || {}; - - + special = jQuery.event.special[ type ] || {}; // Init the event handler queue if ( !handlers ) { - handlers = events[ type ] = {}; + handlers = events[ type ] = []; // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) { + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { - elem.addEventListener( type, handle, false ); + elem.addEventListener( type, eventHandle, false ); + } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, handle ); + elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { - var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers ); - if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) { - modifiedHandler.guid = modifiedHandler.guid || handler.guid; - handler = modifiedHandler; - } - } - + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + // Add the function to the element's handler list - handlers[ handler.guid ] = handler; + handlers.push( handleObj ); // Keep track of which events have been used, for global triggering - this.global[ type ] = true; + jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE elem = null; }, - guid: 1, global: {}, // Detach an event or set of events from an element - remove: function( elem, types, handler ) { + remove: function( elem, types, handler, pos ) { // don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } - var events = jQuery.data( elem, "events" ), ret, type, fn; + if ( handler === false ) { + handler = returnFalse; + } - if ( events ) { - // Unbind all events for the element - if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) { - for ( type in events ) { - this.remove( elem, type + (types || "") ); - } - } else { - // types is actually an event object here - if ( types.type ) { - handler = types.handler; - types = types.type; + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } } - // Handle multiple events separated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(/\s+/); - var i = 0; - while ( (type = types[ i++ ]) ) { - // Namespaced event handlers - var namespaces = type.split("."); - type = namespaces.shift(); - var all = !namespaces.length, - cleaned = jQuery.map( namespaces.slice(0).sort() , function(nm){ return nm.replace(/[^\w\s\.\|`]/g, function(ch){return "\\"+ch; }); }), - namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"), - special = this.special[ type ] || {}; - - if ( events[ type ] ) { - // remove the given handler for the given type - if ( handler ) { - fn = events[ type ][ handler.guid ]; - delete events[ type ][ handler.guid ]; - - // remove all handlers for the given type - } else { - for ( var handle in events[ type ] ) { - // Handle the removal of namespaced events - if ( all || namespace.test( events[ type ][ handle ].type ) ) { - delete events[ type ][ handle ]; - } - } + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); } if ( special.remove ) { - special.remove.call( elem, namespaces, fn); + special.remove.call( elem, handleObj ); } + } - // remove generic event handler if no more handlers exist - for ( ret in events[ type ] ) { - break; - } - if ( !ret ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, jQuery.data( elem, "handle" ), false ); - } else if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) ); - } - } - ret = null; - delete events[ type ]; - } + if ( pos != null ) { + break; } } } - // Remove the expando if it's no longer used - for ( ret in events ) { - break; - } - if ( !ret ) { - var handle = jQuery.data( elem, "handle" ); - if ( handle ) { - handle.elem = null; + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); } - jQuery.removeData( elem, "events" ); - jQuery.removeData( elem, "handle" ); + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); } } }, @@ -217,8 +285,9 @@ jQuery.event = { if ( !elem ) { // Don't bubble custom events when global (to avoid too much overhead) event.stopPropagation(); + // Only trigger if we've ever bound an event for it - if ( this.global[ type ] ) { + if ( jQuery.event.global[ type ] ) { jQuery.each( jQuery.cache, function() { if ( this.events && this.events[type] ) { jQuery.event.trigger( event, data, this.handle.elem ); @@ -251,82 +320,105 @@ jQuery.event = { handle.apply( elem, data ); } - var nativeFn, nativeHandler; + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script try { if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - nativeFn = elem[ type ]; - nativeHandler = elem[ "on" + type ]; + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } } + // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (e) {} + } catch (inlineError) {} - var isClick = jQuery.nodeName(elem, "a") && type === "click"; + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); - // Trigger the native events (except for clicks on links) - if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) { - this.triggered = true; - try { - elem[ type ](); - // prevent IE from throwing an error for some hidden elements - } catch (e) {} + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, targetType = type.replace(/\..*$/, ""), + isClick = jQuery.nodeName(target, "a") && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; - // Handle triggering native .onfoo handlers - } else if ( nativeHandler && elem[ "on" + type ].apply( elem, data ) === false ) { - event.result = false; - } + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { - this.triggered = false; + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; - if ( !event.isPropagationStopped() ) { - var parent = elem.parentNode || elem.ownerDocument; - if ( parent ) { - jQuery.event.trigger( event, data, parent, true ); + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; } } }, handle: function( event ) { - // returned undefined or false - var all, handlers; + var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments ); - event = arguments[0] = jQuery.event.fix( event || window.event ); + event = args[0] = jQuery.event.fix( event || window.event ); event.currentTarget = this; // Namespaced event handlers - var namespaces = event.type.split("."); - event.type = namespaces.shift(); - - // Cache this now, all = true means, any handler - all = !namespaces.length && !event.exclusive; + all = event.type.indexOf(".") < 0 && !event.exclusive; - var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); - - handlers = ( jQuery.data(this, "events") || {} )[ event.type ]; - - for ( var j in handlers ) { - var handler = handlers[ j ]; - - // Filter the functions by class - if ( all || namespace.test(handler.type) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handler; - event.data = handler.data; - - var ret = handler.apply( this, arguments ); + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery.data(this, "events"); + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } } - } - if ( event.isImmediatePropagationStopped() ) { - break; + if ( event.isImmediatePropagationStopped() ) { + break; + } } - } } @@ -382,7 +474,7 @@ jQuery.event = { event.metaKey = event.ctrlKey; } - // Add which for click: 1 == left; 2 == middle; 3 == right + // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it if ( !event.which && event.button !== undefined ) { event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); @@ -391,63 +483,41 @@ jQuery.event = { return event; }, - proxy: function( fn, proxy, thisObject ) { - if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - // FIXME: Should proxy be redefined to be applied with thisObject if defined? - proxy = proxy || function() { return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); }; - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++; - // So proxy can be declared as an argument - return proxy; - }, + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, special: { ready: { // Make sure the ready event is setup setup: jQuery.bindReady, - teardown: function() {} + teardown: jQuery.noop }, live: { - add: function( proxy, data, namespaces, live ) { - jQuery.extend( proxy, data || {} ); - - proxy.guid += data.selector + data.live; - jQuery.event.add( this, data.live, liveHandler, data ); - + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); }, - remove: function( namespaces ) { - if ( namespaces.length ) { - var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)"); - - jQuery.each( (jQuery.data(this, "events").live || {}), function() { - if ( name.test(this.type) ) { - remove++; - } - }); - - if ( remove < 1 ) { - jQuery.event.remove( this, namespaces[0], liveHandler ); - } - } - }, - special: {} + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } }, + beforeunload: { - setup: function( data, namespaces, fn ) { + setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( this.setInterval ) { - this.onbeforeunload = fn; + this.onbeforeunload = eventHandle; } - - return false; }, - teardown: function( namespaces, fn ) { - if ( this.onbeforeunload === fn ) { + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } @@ -455,7 +525,19 @@ jQuery.event = { } }; -jQuery.Event = function( src ){ +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { // Allow instantiation without the 'new' keyword if ( !this.preventDefault ) { return new jQuery.Event( src ); @@ -472,7 +554,7 @@ jQuery.Event = function( src ){ // timeStamp is buggy for some events on Firefox(#3843) // So we won't rely on the native value - this.timeStamp = now(); + this.timeStamp = jQuery.now(); // Mark it as fixed this[ expando ] = true; @@ -517,7 +599,7 @@ jQuery.Event.prototype = { // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, - stopImmediatePropagation: function(){ + stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, @@ -525,27 +607,31 @@ jQuery.Event.prototype = { isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; + // Checks if an event happened on an element within another element // Used in jQuery.event.special.mouseenter and mouseleave handlers var withinElement = function( event ) { // Check if mouse(over|out) are still within the same parent element var parent = event.relatedTarget; - // Traverse up the tree - while ( parent && parent != this ) { - // Firefox sometimes assigns relatedTarget a XUL element - // which we cannot access the parentNode property of - try { parent = parent.parentNode; } - // assuming we've left the element since we most likely mousedover a xul element - catch(e) { break; } - } - if ( parent != this ) { - // set the correct event type - event.type = event.data; - // handle event if we actually just moused on to a non sub-element - jQuery.event.handle.apply( this, arguments ); - } + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } }, // In case of event delegation, we only need to rename the event.type, @@ -561,10 +647,10 @@ jQuery.each({ mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { - setup: function(data){ + setup: function( data ) { jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); }, - teardown: function(data){ + teardown: function( data ) { jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); } }; @@ -573,102 +659,142 @@ jQuery.each({ // submit delegation if ( !jQuery.support.submitBubbles ) { -jQuery.event.special.submit = { - setup: function( data, namespaces, fn ) { - if ( this.nodeName.toLowerCase() !== "form" ) { - jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) { - var elem = e.target, type = elem.type; + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - return trigger( "submit", this, arguments ); - } - }); + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); - jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) { - var elem = e.target, type = elem.type; + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - return trigger( "submit", this, arguments ); - } - }); - } - }, + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); - remove: function( namespaces, fn ) { - jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") ); - jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") ); - } -}; + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; } // change delegation, happens here so we have bind. if ( !jQuery.support.changeBubbles ) { -jQuery.event.special.change = { - filters: { - click: function( e ) { - var elem = e.target; + var formElems = /textarea|input|select/i, - if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) { - return trigger( "change", this, arguments ); - } + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; - return changeFilters.keyup.call( this, e ); - }, - keyup: function( e ) { - var elem = e.target, data, index = elem.selectedIndex + ""; + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; - if ( elem.nodeName.toLowerCase() === "select" ) { - data = jQuery.data( elem, "_change_data" ); - jQuery.data( elem, "_change_data", index ); + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, - if ( (elem.type === "select-multiple" || data != null) && data !== index ) { - return trigger( "change", this, arguments ); + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); } - } - }, - beforeactivate: function( e ) { - var elem = e.target; + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, - if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) { - return trigger( "change", this, arguments ); + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); } }, - blur: function( e ) { - var elem = e.target, nodeName = elem.nodeName.toLowerCase(); - if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password"))) - && jQuery.data(elem, "_change_data") !== elem.value ) { + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } - return trigger( "change", this, arguments ); + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); } + + return formElems.test( this.nodeName ); }, - focus: function( e ) { - var elem = e.target, nodeName = elem.nodeName.toLowerCase(); - if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) { - jQuery.data( elem, "_change_data", elem.value ); - } - } - }, - setup: function( data, namespaces, fn ) { - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] ); - } - - // always want to listen for change for trigger - return false; - }, - remove: function( namespaces, fn ) { - for ( var type in changeFilters ) { - jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] ); - } - } -}; + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); -var changeFilters = jQuery.event.special.change.filters; + return formElems.test( this.nodeName ); + } + }; + changeFilters = jQuery.event.special.change.filters; } function trigger( type, elem, args ) { @@ -677,28 +803,27 @@ function trigger( type, elem, args ) { } // Create "bubbling" focus and blur events -if ( !jQuery.support.focusBubbles ) { +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; -jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){ - jQuery.event.special[ orig ] = { - setup: function() { - jQuery.event.add( this, fix, ieHandler ); - }, - teardown: function() { - jQuery.event.remove( this, fix, ieHandler ); + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); } - }; - - function ieHandler() { - arguments[0].type = orig; - return jQuery.event.handle.apply(this, arguments); - } -}); - + }); } -jQuery.each(["bind", "one"], function(i, name) { - jQuery.fn[ name ] = function( type, data, fn, thisObject ) { +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { @@ -707,19 +832,26 @@ jQuery.each(["bind", "one"], function(i, name) { return this; } - if ( jQuery.isFunction( data ) ) { - thisObject = fn; + if ( jQuery.isFunction( data ) || data === false ) { fn = data; data = undefined; } - fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject ); - var handler = name == "one" ? jQuery.event.proxy( fn, function( event ) { + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { jQuery( this ).unbind( event, handler ); return fn.apply( this, arguments ); }) : fn; - return type === "unload" ? this.one(type, data, handler, thisObject) : this.each(function() { - jQuery.event.add( this, type, handler, data ); - }); + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; }; }); @@ -730,13 +862,29 @@ jQuery.fn.extend({ for ( var key in type ) { this.unbind(key, type[key]); } - return this; + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } } - return this.each(function() { - jQuery.event.remove( this, type, fn ); - }); + return this; }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); @@ -758,14 +906,14 @@ jQuery.fn.extend({ var args = arguments, i = 1; // link all the functions, so any of them can unbind this click handler - while( i < args.length ) { - jQuery.event.proxy( fn, args[ i++ ] ); + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); } - return this.click( jQuery.event.proxy( fn, function( event ) { + return this.click( jQuery.proxy( fn, function( event ) { // Figure out which function to execute - var lastToggle = ( jQuery.data( this, 'lastToggle' + fn.guid ) || 0 ) % i; - jQuery.data( this, 'lastToggle' + fn.guid, lastToggle + 1 ); + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); @@ -777,64 +925,119 @@ jQuery.fn.extend({ hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - }, + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); - live: function( type, data, fn, thisObject ) { if ( jQuery.isFunction( data ) ) { - if ( fn !== undefined ) { - thisObject = fn; - } fn = data; data = undefined; } - jQuery( this.context ).bind( liveConvert( type, this.selector ), { - data: data, selector: this.selector, live: type - }, fn, thisObject ); - return this; - }, - die: function( type, fn ) { - jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null ); + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + return this; - } + }; }); function liveHandler( event ) { - var stop = true, elems = [], selectors = [], args = arguments, - related, match, fn, elem, j, i, data, - live = jQuery.extend({}, jQuery.data( this, "events" ).live); + var stop, maxLevel, elems = [], selectors = [], + related, match, handleObj, elem, j, i, l, data, close, namespace, + events = jQuery.data( this, "events" ); - for ( j in live ) { - fn = live[j]; - if ( fn.live === event.type || - fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) { + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); - data = fn.data; - if ( !(data.beforeFilter && data.beforeFilter[event.type] && - !data.beforeFilter[event.type](event)) ) { - selectors.push( fn.selector ); - } } else { - delete live[j]; + live.splice( j--, 1 ); } } match = jQuery( event.target ).closest( selectors, event.currentTarget ); for ( i = 0, l = match.length; i < l; i++ ) { - for ( j in live ) { - fn = live[j]; - elem = match[i].elem; - related = null; + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + elem = close.elem; + related = null; - if ( match[i].selector === fn.selector ) { // Those two events require additional checking - if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) { - related = jQuery( event.relatedTarget ).closest( fn.selector )[0]; + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; } if ( !related || related !== elem ) { - elems.push({ elem: elem, fn: fn }); + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); } } } @@ -842,32 +1045,51 @@ function liveHandler( event ) { for ( i = 0, l = elems.length; i < l; i++ ) { match = elems[i]; - event.currentTarget = match.elem; - event.data = match.fn.data; - if ( match.fn.apply( match.elem, args ) === false ) { - stop = false; + + if ( maxLevel && match.level > maxLevel ) { break; } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + } } return stop; } function liveConvert( type, selector ) { - return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "&")].join("."); + return (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); } -jQuery.each( ("blur focus load resize scroll unload click dblclick " + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { // Handle event binding - jQuery.fn[ name ] = function( fn ) { - return fn ? this.bind( name, fn ) : this.trigger( name ); + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); }; - if ( jQuery.fnAttr ) { - jQuery.fnAttr[ name ] = true; + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; } }); @@ -875,16 +1097,15 @@ jQuery.each( ("blur focus load resize scroll unload click dblclick " + // Window isn't included so as not to unbind existing unload events // More info: // - http://isaacschlueter.com/2006/10/msie-memory-leaks/ -/*@cc_on -jQuery( window ).bind( 'unload', function() { - for ( var id in jQuery.cache ) { - // Skip the window - if ( id != 1 && jQuery.cache[ id ].handle ) { - // Try/Catch is to handle iframes being unloaded, see #4280 - try { - jQuery.event.remove( jQuery.cache[ id ].handle.elem ); - } catch(e) {} +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } } - } -}); -@*/ + }); +}