From be2407e233f1731e3f0ec79bdc8efaadd1f32f9f Mon Sep 17 00:00:00 2001 From: jeresig Date: Thu, 4 Feb 2010 21:36:32 -0500 Subject: [PATCH] Rewrote the live event handling to use the new event logic. Also added in support for live namespaced events. Fixes #5945. --- src/event.js | 153 ++++++++++++++++++++++++++++------------------------ test/unit/event.js | 90 +++++++++++++++++++++++-------- 2 files changed, 152 insertions(+), 91 deletions(-) diff --git a/src/event.js b/src/event.js index 929a4aa..f7c69f8 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 ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); @@ -106,7 +113,7 @@ jQuery.event = { if ( special.add ) { special.add.call( elem, handleObj ); } - + // Add the function to the element's handler list handlers.push( handleObj ); @@ -198,14 +205,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 ); } } @@ -380,6 +385,7 @@ jQuery.event = { // 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,33 +479,28 @@ 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 ) { // We only want to do this special case on windows @@ -918,7 +919,7 @@ jQuery.fn.extend({ 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, selector = origSelector || this.selector, context = origSelector ? this : jQuery( this.context ); @@ -927,23 +928,34 @@ jQuery.each(["live", "die"], function( i, name ) { data = undefined; } - types = (types || "").split( /\s+/ ); + types = (types || "").split(" "); while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + type = type === "focus" ? "focusin" : // focus --> focusin type === "blur" ? "focusout" : // blur --> focusout - type === "hover" ? types.push("mouseleave") && "mouseenter" : // hover support + type === "hover" ? types.push("mouseleave" + namespaces) && "mouseenter" : // hover support 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 } ); + }); } else { // unbind live handler - context.unbind( liveConvert( type, selector ), fn ? { guid: fn.guid + selector + type } : null ); + context.unbind( liveConvert( type, selector ), fn ); } } @@ -953,45 +965,46 @@ 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); + related, match, handleObj, elem, j, i, l, data, + 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 || 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; + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( match[i].selector === handleObj.selector ) { + elem = match[i].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.origType === "mouseenter" || handleObj.origType === "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 }); } } } @@ -1000,8 +1013,10 @@ 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 ) { + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { stop = false; break; } diff --git a/test/unit/event.js b/test/unit/event.js index 2937b1c..e85c4bd 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -772,6 +772,7 @@ test(".live()/.die()", function() { equals( liveb, 0, "Click on body" ); // This should trigger two events + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendiv").trigger("click"); equals( submit, 0, "Click on div" ); equals( div, 1, "Click on div" ); @@ -779,55 +780,62 @@ test(".live()/.die()", function() { equals( liveb, 0, "Click on div" ); // This should trigger three events (w/ bubbling) + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendivchild").trigger("click"); equals( submit, 0, "Click on inner div" ); - equals( div, 2, "Click on inner div" ); - equals( livea, 2, "Click on inner div" ); + equals( div, 1, "Click on inner div" ); + equals( livea, 1, "Click on inner div" ); equals( liveb, 1, "Click on inner div" ); // This should trigger one submit + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendivchild").trigger("submit"); equals( submit, 1, "Submit on div" ); - equals( div, 2, "Submit on div" ); - equals( livea, 2, "Submit on div" ); - equals( liveb, 1, "Submit on div" ); + equals( div, 0, "Submit on div" ); + equals( livea, 0, "Submit on div" ); + equals( liveb, 0, "Submit on div" ); // Make sure no other events were removed in the process + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendivchild").trigger("click"); - equals( submit, 1, "die Click on inner div" ); - equals( div, 3, "die Click on inner div" ); - equals( livea, 3, "die Click on inner div" ); - equals( liveb, 2, "die Click on inner div" ); + equals( submit, 0, "die Click on inner div" ); + equals( div, 1, "die Click on inner div" ); + equals( livea, 1, "die Click on inner div" ); + equals( liveb, 1, "die Click on inner div" ); // Now make sure that the removal works + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendivchild").die("click"); jQuery("div#nothiddendivchild").trigger("click"); - equals( submit, 1, "die Click on inner div" ); - equals( div, 4, "die Click on inner div" ); - equals( livea, 4, "die Click on inner div" ); - equals( liveb, 2, "die Click on inner div" ); + equals( submit, 0, "die Click on inner div" ); + equals( div, 1, "die Click on inner div" ); + equals( livea, 1, "die Click on inner div" ); + equals( liveb, 0, "die Click on inner div" ); // Make sure that the click wasn't removed too early + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendiv").trigger("click"); - equals( submit, 1, "die Click on inner div" ); - equals( div, 5, "die Click on inner div" ); - equals( livea, 5, "die Click on inner div" ); - equals( liveb, 2, "die Click on inner div" ); + equals( submit, 0, "die Click on inner div" ); + equals( div, 1, "die Click on inner div" ); + equals( livea, 1, "die Click on inner div" ); + equals( liveb, 0, "die Click on inner div" ); // Make sure that stopPropgation doesn't stop live events + submit = 0, div = 0, livea = 0, liveb = 0; jQuery("div#nothiddendivchild").live("click", function(e){ liveb++; e.stopPropagation(); }); jQuery("div#nothiddendivchild").trigger("click"); - equals( submit, 1, "stopPropagation Click on inner div" ); - equals( div, 6, "stopPropagation Click on inner div" ); - equals( livea, 6, "stopPropagation Click on inner div" ); - equals( liveb, 3, "stopPropagation Click on inner div" ); + equals( submit, 0, "stopPropagation Click on inner div" ); + equals( div, 1, "stopPropagation Click on inner div" ); + equals( livea, 1, "stopPropagation Click on inner div" ); + equals( liveb, 1, "stopPropagation Click on inner div" ); // Make sure click events only fire with primary click + submit = 0, div = 0, livea = 0, liveb = 0; var event = jQuery.Event("click"); event.button = 1; jQuery("div#nothiddendiv").trigger(event); - equals( livea, 6, "live secondary click" ); + equals( livea, 0, "live secondary click" ); jQuery("div#nothiddendivchild").die("click"); jQuery("div#nothiddendiv").die("click"); @@ -1043,6 +1051,44 @@ test("live with multiple events", function(){ equals( count, 2, "Make sure both the click and submit were triggered." ); }); +test("live with namespaces", function(){ + expect(6); + + var count1 = 0, count2 = 0; + + jQuery("#liveSpan1").live("foo.bar", function(){ + count1++; + }); + + jQuery("#liveSpan2").live("foo.zed", function(){ + count2++; + }); + + jQuery("#liveSpan1").trigger("foo.bar"); + equals( count1, 1, "Got live foo.bar" ); + + jQuery("#liveSpan2").trigger("foo.zed"); + equals( count2, 1, "Got live foo.zed" ); + + //remove one + jQuery("#liveSpan2").die("foo.zed"); + jQuery("#liveSpan1").trigger("foo.bar"); + + equals( count1, 2, "Got live foo.bar after dieing foo.zed" ); + + jQuery("#liveSpan2").trigger("foo.zed"); + equals( count2, 1, "Got live foo.zed" ); + + //remove the other + jQuery("#liveSpan1").die("foo.bar"); + + jQuery("#liveSpan1").trigger("foo.bar"); + equals( count1, 2, "Did not respond to foo.bar after dieing it" ); + + jQuery("#liveSpan2").trigger("foo.zed"); + equals( count2, 1, "Did not trigger foo.zed again" ); +}); + test("live with change", function(){ var selectChange = 0, checkboxChange = 0; -- 1.7.10.4