X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=src%2Fevent.js;h=db08bc093fed271f434d0fe3b5886af8ddbe8012;hb=26b0e913dda386b6b2848196689a02a2da6aa40d;hp=929a4aaa2d55c9b9502b874033f45a0dfa34ecb6;hpb=e7912805d6ee290071fb15fbca752e9f47fcd032;p=jquery.git diff --git a/src/event.js b/src/event.js index 929a4aa..db08bc0 100644 --- a/src/event.js +++ b/src/event.js @@ -1,8 +1,9 @@ -var fcleanup = function( nm ) { - return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { - return "\\" + ch; - }); -}; +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; /* * A number of helper functions used for managing events. @@ -24,6 +25,13 @@ jQuery.event = { elem = window; } + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + // Make sure that the function being executed has a unique ID if ( !handler.guid ) { handler.guid = jQuery.guid++; @@ -62,12 +70,9 @@ jQuery.event = { var type, i = 0, namespaces; while ( (type = types[ i++ ]) ) { - var handleObj = { - handler: handler, - data: data, - namespace: "", - guid: handler.guid - }; + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; // Namespaced event handlers if ( type.indexOf(".") > -1 ) { @@ -77,9 +82,11 @@ jQuery.event = { } else { namespaces = []; + handleObj.namespace = ""; } handleObj.type = type; + handleObj.guid = handler.guid; // Get the current list of functions bound to this event var handlers = events[ type ], @@ -92,7 +99,7 @@ jQuery.event = { // 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, eventHandle, false ); @@ -105,8 +112,12 @@ jQuery.event = { if ( special.add ) { special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } } - + // Add the function to the element's handler list handlers.push( handleObj ); @@ -198,14 +209,12 @@ jQuery.event = { if ( handler.guid === handleObj.guid ) { // remove the given handler for the given type if ( all || namespace.test( handleObj.namespace ) ) { - fn = handleObj.handler; - if ( pos == null ) { eventType.splice( j--, 1 ); } if ( special.remove ) { - special.remove.call( elem, namespaces, handleObj ); + special.remove.call( elem, handleObj ); } } @@ -216,7 +225,7 @@ jQuery.event = { } // remove generic event handler if no more handlers exist - if ( jQuery.isEmptyObject( events[ type ] ) ) { + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { removeEvent( elem, type, elemData.handle ); } @@ -357,7 +366,7 @@ jQuery.event = { event.currentTarget = this; // Namespaced event handlers - all = event.type.indexOf(".") < 0; + all = event.type.indexOf(".") < 0 && !event.exclusive; if ( !all ) { namespaces = event.type.split("."); @@ -375,11 +384,12 @@ jQuery.event = { var handleObj = handlers[ j ]; // Filter the functions by class - if ( (all && !event.exclusive) || namespace.test( handleObj.namespace ) ) { + if ( all || namespace.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, arguments ); @@ -473,44 +483,39 @@ jQuery.event = { }, live: { - add: function( proxy, data, namespaces, live ) { - jQuery.extend( proxy, data || {} ); - - proxy.guid += data.selector + data.live; - data.liveProxy = proxy; - - jQuery.event.add( this, data.live, liveHandler, data ); - + add: function( handleObj ) { + jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); }, - 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 ); + remove: function( handleObj ) { + var remove = true, + type = handleObj.origType.replace(rnamespaces, ""); + + jQuery.each( jQuery.data(this, "events").live || [], function() { + if ( type === this.origType.replace(rnamespaces, "") ) { + remove = false; + return false; } + }); + + if ( remove ) { + jQuery.event.remove( this, handleObj.origType, liveHandler ); } - }, - special: {} + } + }, + 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; } } @@ -603,27 +608,24 @@ 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 { + // 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; - - // 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; + 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 ); - } + // 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, @@ -651,64 +653,65 @@ 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 ); + } + }); - } else { - return false; - } - }, + } else { + return false; + } + }, - remove: function( namespaces, fn ) { - jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") ); - jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") ); - } -}; + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; } // change delegation, happens here so we have bind. if ( !jQuery.support.changeBubbles ) { -var formElems = /textarea|input|select/i; + var formElems = /textarea|input|select/i, -function getVal( elem ) { - var type = elem.type, val = elem.value; + changeFilters, - if ( type === "radio" || type === "checkbox" ) { - val = elem.checked; + getVal = function( elem ) { + var type = elem.type, val = elem.value; - } else if ( type === "select-multiple" ) { - val = elem.selectedIndex > -1 ? - jQuery.map( elem.options, function( elem ) { - return elem.selected; - }).join("-") : - ""; + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; - } else if ( elem.nodeName.toLowerCase() === "select" ) { - val = elem.selectedIndex; - } + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; - return val; -} + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, -function testChange( e ) { + testChange = function testChange( e ) { var elem = e.target, data, val; if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { @@ -731,58 +734,61 @@ function testChange( e ) { 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 ); + } + }, -jQuery.event.special.change = { - filters: { - focusout: testChange, + // 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; - click: 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 ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { - return testChange.call( this, e ); + // 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) ); } }, - // 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; + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } - if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - return testChange.call( this, e ); + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); } + + return formElems.test( this.nodeName ); }, - // 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) ); - } - }, - setup: function( data, namespaces, fn ) { - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] ); - } + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); - return formElems.test( this.nodeName ); - }, - remove: function( namespaces, fn ) { - for ( var type in changeFilters ) { - jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] ); + return formElems.test( this.nodeName ); } + }; - return formElems.test( this.nodeName ); - } -}; - -var changeFilters = jQuery.event.special.change.filters; - + changeFilters = jQuery.event.special.change.filters; } function trigger( type, elem, args ) { @@ -916,9 +922,16 @@ jQuery.fn.extend({ } }); +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, + var type, i = 0, match, namespaces, preType, selector = origSelector || this.selector, context = origSelector ? this : jQuery( this.context ); @@ -927,23 +940,42 @@ jQuery.each(["live", "die"], function( i, name ) { data = undefined; } - types = (types || "").split( /\s+/ ); + types = (types || "").split(" "); while ( (type = types[ i++ ]) != null ) { - type = type === "focus" ? "focusin" : // focus --> focusin - type === "blur" ? "focusout" : // blur --> focusout - type === "hover" ? types.push("mouseleave") && "mouseenter" : // hover support - type; - + 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 - context.bind( liveConvert( type, selector ), { - data: data, selector: selector, live: type - }, fn ); + context.each(function(){ + jQuery.event.add( this, liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + }); } else { // unbind live handler - context.unbind( liveConvert( type, selector ), fn ? { guid: fn.guid + selector + type } : null ); + context.unbind( liveConvert( type, selector ), fn ); } } @@ -952,46 +984,49 @@ jQuery.each(["live", "die"], function( i, name ) { }); function liveHandler( event ) { - var stop, elems = [], selectors = [], args = arguments, - related, match, fn, elem, j, i, l, data, - live = jQuery.extend({}, jQuery.data( this, "events" ).live); + var stop, maxLevel, elems = [], selectors = [], + related, match, handleObj, elem, j, i, l, data, close, + events = jQuery.data( this, "events" ); // Make sure we avoid non-left-click bubbling in Firefox (#3861) - if ( event.button && event.type === "click" ) { + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { return; } - for ( j in live ) { - fn = live[j]; - if ( fn.live === event.type || - fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) { + 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 ) { + 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" ) { + 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 }); } } } @@ -999,12 +1034,24 @@ 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;