From f68b46d7abb54cdcd3d1ce1713bc989f992d1448 Mon Sep 17 00:00:00 2001
From: jeresig <jeresig@gmail.com>
Date: Tue, 9 Mar 2010 12:22:25 -0500
Subject: [PATCH] Make sure that special remove and teardown events get called
 when .die() is used. Additionally made sure that default
 actions are triggered when namespaced events are used.
 Fixes #6202 and #6250.

---
 src/event.js        |   48 +++++++++++++------------------
 src/manipulation.js |    2 +-
 test/unit/event.js  |   80 ++++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 91 insertions(+), 39 deletions(-)

diff --git a/src/event.js b/src/event.js
index b549cd5..173bab1 100644
--- a/src/event.js
+++ b/src/event.js
@@ -90,7 +90,9 @@ jQuery.event = {
 			}
 
 			handleObj.type = type;
-			handleObj.guid = handler.guid;
+			if ( !handleObj.guid ) {
+				handleObj.guid = handler.guid;
+			}
 
 			// Get the current list of functions bound to this event
 			var handlers = events[ type ],
@@ -335,31 +337,31 @@ jQuery.event = {
 			jQuery.event.trigger( event, data, parent, true );
 
 		} else if ( !event.isDefaultPrevented() ) {
-			var target = event.target, old,
-				isClick = jQuery.nodeName(target, "a") && type === "click",
-				special = jQuery.event.special[ type ] || {};
+			var target = event.target, old, targetType = type.replace(/\..*$/, ""),
+				isClick = jQuery.nodeName(target, "a") && targetType === "click",
+				special = jQuery.event.special[ targetType ] || {};
 
 			if ( (!special._default || special._default.call( elem, event ) === false) && 
 				!isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
 
 				try {
-					if ( target[ type ] ) {
+					if ( target[ targetType ] ) {
 						// Make sure that we don't accidentally re-trigger the onFOO events
-						old = target[ "on" + type ];
+						old = target[ "on" + targetType ];
 
 						if ( old ) {
-							target[ "on" + type ] = null;
+							target[ "on" + targetType ] = null;
 						}
 
 						jQuery.event.triggered = true;
-						target[ type ]();
+						target[ targetType ]();
 					}
 
 				// prevent IE from throwing an error for some elements with some event types, see #3533
 				} catch (triggerError) {}
 
 				if ( old ) {
-					target[ "on" + type ] = old;
+					target[ "on" + targetType ] = old;
 				}
 
 				jQuery.event.triggered = false;
@@ -381,9 +383,10 @@ jQuery.event = {
 			event.type = namespaces.shift();
 			namespace_sort = namespaces.slice(0).sort();
 			namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
-			event.namespace = namespace_sort.join(".");
 		}
 
+		event.namespace = event.namespace || namespace_sort.join(".");
+
 		events = jQuery.data(this, "events");
 		handlers = (events || {})[ event.type ];
 
@@ -495,25 +498,14 @@ jQuery.event = {
 
 		live: {
 			add: function( handleObj ) {
-				jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); 
+				jQuery.event.add( this,
+					liveConvert( handleObj.origType, handleObj.selector ),
+					jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); 
 			},
 
 			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 );
-				}
+				jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
 			}
-
 		},
 
 		beforeunload: {
@@ -983,13 +975,13 @@ jQuery.each(["live", "die"], function( i, name ) {
 			if ( name === "live" ) {
 				// bind live handler
 				for ( var j = 0, l = context.length; j < l; j++ ) {
-					jQuery.event.add( context[j], liveConvert( type, selector ),
+					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( liveConvert( type, selector ), fn );
+				context.unbind( "live." + liveConvert( type, selector ), fn );
 			}
 		}
 		
@@ -1077,7 +1069,7 @@ function liveHandler( event ) {
 }
 
 function liveConvert( type, selector ) {
-	return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&");
+	return (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&");
 }
 
 jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
diff --git a/src/manipulation.js b/src/manipulation.js
index 270c7bc..be12aa1 100644
--- a/src/manipulation.js
+++ b/src/manipulation.js
@@ -561,7 +561,7 @@ jQuery.extend({
 			if ( id ) {
 				data = cache[ id ];
 				
-				if ( data.events ) {
+				if ( data && data.events ) {
 					for ( var type in data.events ) {
 						if ( special[ type ] ) {
 							jQuery.event.remove( elem, type );
diff --git a/test/unit/event.js b/test/unit/event.js
index 786a46e..f800fd5 100644
--- a/test/unit/event.js
+++ b/test/unit/event.js
@@ -978,17 +978,20 @@ test(".live()/.die()", function() {
 	jQuery("#nothiddendiv").trigger("click");
 	equals( called, 1, "Verify that only one click occurred." );
 
+	called = 0;
 	jQuery("#anchor2").trigger("click");
-	equals( called, 2, "Verify that only one click occurred." );
+	equals( called, 1, "Verify that only one click occurred." );
 
 	// Make sure that only one callback is removed
 	jQuery("#anchor2").die("click", callback);
 
+	called = 0;
 	jQuery("#nothiddendiv").trigger("click");
-	equals( called, 3, "Verify that only one click occurred." );
+	equals( called, 1, "Verify that only one click occurred." );
 
+	called = 0;
 	jQuery("#anchor2").trigger("click");
-	equals( called, 3, "Verify that no click occurred." );
+	equals( called, 0, "Verify that no click occurred." );
 
 	// Make sure that it still works if the selector is the same,
 	// but the event type is different
@@ -997,11 +1000,13 @@ test(".live()/.die()", function() {
 	// Cleanup
 	jQuery("#nothiddendiv").die("click", callback);
 
+	called = 0;
 	jQuery("#nothiddendiv").trigger("click");
-	equals( called, 3, "Verify that no click occurred." );
+	equals( called, 0, "Verify that no click occurred." );
 
+	called = 0;
 	jQuery("#nothiddendiv").trigger("foo");
-	equals( called, 4, "Verify that one foo occurred." );
+	equals( called, 1, "Verify that one foo occurred." );
 
 	// Cleanup
 	jQuery("#nothiddendiv").die("foo", callback);
@@ -1307,6 +1312,56 @@ test("live with submit", function() {
 	jQuery("body").die("submit");
 });
 
+test("live with special events", function() {
+	expect(13);
+
+	jQuery.event.special.foo = {
+		setup: function( data, namespaces, handler ) {
+			ok( true, "Setup run." );
+		},
+		teardown: function( namespaces ) {
+			ok( true, "Teardown run." );
+		},
+		add: function( handleObj ) {
+			ok( true, "Add run." );
+		},
+		remove: function( handleObj ) {
+			ok( true, "Remove run." );
+		},
+		_default: function( event ) {
+			ok( true, "Default run." );
+		}
+	};
+
+	// Run: setup, add
+	jQuery("#liveSpan1").live("foo.a", function(e){
+		ok( true, "Handler 1 run." );
+	});
+
+	// Run: add
+	jQuery("#liveSpan1").live("foo.b", function(e){
+		ok( true, "Handler 2 run." );
+	});
+
+	// Run: Handler 1, Handler 2, Default
+	jQuery("#liveSpan1").trigger("foo");
+
+	// Run: Handler 1, Default
+	// TODO: Namespace doesn't trigger default (?)
+	jQuery("#liveSpan1").trigger("foo.a");
+
+	// Run: remove
+	jQuery("#liveSpan1").die("foo.a");
+
+	// Run: Handler 2, Default
+	jQuery("#liveSpan1").trigger("foo");
+
+	// Run: remove, teardown
+	jQuery("#liveSpan1").die("foo");
+
+	delete jQuery.event.special.foo;
+});
+
 test(".delegate()/.undelegate()", function() {
 	expect(65);
 
@@ -1460,17 +1515,20 @@ test(".delegate()/.undelegate()", function() {
 	jQuery("#nothiddendiv").trigger("click");
 	equals( called, 1, "Verify that only one click occurred." );
 
+	called = 0;
 	jQuery("#anchor2").trigger("click");
-	equals( called, 2, "Verify that only one click occurred." );
+	equals( called, 1, "Verify that only one click occurred." );
 
 	// Make sure that only one callback is removed
 	jQuery("#body").undelegate("#anchor2", "click", callback);
 
+	called = 0;
 	jQuery("#nothiddendiv").trigger("click");
-	equals( called, 3, "Verify that only one click occurred." );
+	equals( called, 1, "Verify that only one click occurred." );
 
+	called = 0;
 	jQuery("#anchor2").trigger("click");
-	equals( called, 3, "Verify that no click occurred." );
+	equals( called, 0, "Verify that no click occurred." );
 
 	// Make sure that it still works if the selector is the same,
 	// but the event type is different
@@ -1479,11 +1537,13 @@ test(".delegate()/.undelegate()", function() {
 	// Cleanup
 	jQuery("#body").undelegate("#nothiddendiv", "click", callback);
 
+	called = 0;
 	jQuery("#nothiddendiv").trigger("click");
-	equals( called, 3, "Verify that no click occurred." );
+	equals( called, 0, "Verify that no click occurred." );
 
+	called = 0;
 	jQuery("#nothiddendiv").trigger("foo");
-	equals( called, 4, "Verify that one foo occurred." );
+	equals( called, 1, "Verify that one foo occurred." );
 
 	// Cleanup
 	jQuery("#body").undelegate("#nothiddendiv", "foo", callback);
-- 
1.7.10.4