Commit be2407e2 authored by jeresig's avatar jeresig

Rewrote the live event handling to use the new event logic. Also added in...

Rewrote the live event handling to use the new event logic. Also added in support for live namespaced events. Fixes #5945.
parent 10475770
var fcleanup = function( nm ) { var rnamespaces = /\.(.*)$/,
return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { fcleanup = function( nm ) {
return "\\" + ch; return nm.replace(/[^\w\s\.\|`]/g, function( ch ) {
}); return "\\" + ch;
}; });
};
/* /*
* A number of helper functions used for managing events. * A number of helper functions used for managing events.
...@@ -24,6 +25,13 @@ jQuery.event = { ...@@ -24,6 +25,13 @@ jQuery.event = {
elem = window; elem = window;
} }
var handleObjIn, handleObj;
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
}
// Make sure that the function being executed has a unique ID // Make sure that the function being executed has a unique ID
if ( !handler.guid ) { if ( !handler.guid ) {
handler.guid = jQuery.guid++; handler.guid = jQuery.guid++;
...@@ -62,12 +70,9 @@ jQuery.event = { ...@@ -62,12 +70,9 @@ jQuery.event = {
var type, i = 0, namespaces; var type, i = 0, namespaces;
while ( (type = types[ i++ ]) ) { while ( (type = types[ i++ ]) ) {
var handleObj = { handleObj = handleObjIn ?
handler: handler, jQuery.extend({}, handleObjIn) :
data: data, { handler: handler, data: data };
namespace: "",
guid: handler.guid
};
// Namespaced event handlers // Namespaced event handlers
if ( type.indexOf(".") > -1 ) { if ( type.indexOf(".") > -1 ) {
...@@ -77,9 +82,11 @@ jQuery.event = { ...@@ -77,9 +82,11 @@ jQuery.event = {
} else { } else {
namespaces = []; namespaces = [];
handleObj.namespace = "";
} }
handleObj.type = type; handleObj.type = type;
handleObj.guid = handler.guid;
// Get the current list of functions bound to this event // Get the current list of functions bound to this event
var handlers = events[ type ], var handlers = events[ type ],
...@@ -92,7 +99,7 @@ jQuery.event = { ...@@ -92,7 +99,7 @@ jQuery.event = {
// Check for a special event handler // Check for a special event handler
// Only use addEventListener/attachEvent if the special // Only use addEventListener/attachEvent if the special
// events handler returns false // 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 // Bind the global event handler to the element
if ( elem.addEventListener ) { if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false ); elem.addEventListener( type, eventHandle, false );
...@@ -106,7 +113,7 @@ jQuery.event = { ...@@ -106,7 +113,7 @@ jQuery.event = {
if ( special.add ) { if ( special.add ) {
special.add.call( elem, handleObj ); special.add.call( elem, handleObj );
} }
// Add the function to the element's handler list // Add the function to the element's handler list
handlers.push( handleObj ); handlers.push( handleObj );
...@@ -198,14 +205,12 @@ jQuery.event = { ...@@ -198,14 +205,12 @@ jQuery.event = {
if ( handler.guid === handleObj.guid ) { if ( handler.guid === handleObj.guid ) {
// remove the given handler for the given type // remove the given handler for the given type
if ( all || namespace.test( handleObj.namespace ) ) { if ( all || namespace.test( handleObj.namespace ) ) {
fn = handleObj.handler;
if ( pos == null ) { if ( pos == null ) {
eventType.splice( j--, 1 ); eventType.splice( j--, 1 );
} }
if ( special.remove ) { if ( special.remove ) {
special.remove.call( elem, namespaces, handleObj ); special.remove.call( elem, handleObj );
} }
} }
...@@ -380,6 +385,7 @@ jQuery.event = { ...@@ -380,6 +385,7 @@ jQuery.event = {
// So that we can later remove it // So that we can later remove it
event.handler = handleObj.handler; event.handler = handleObj.handler;
event.data = handleObj.data; event.data = handleObj.data;
event.handleObj = handleObj;
var ret = handleObj.handler.apply( this, arguments ); var ret = handleObj.handler.apply( this, arguments );
...@@ -473,33 +479,28 @@ jQuery.event = { ...@@ -473,33 +479,28 @@ jQuery.event = {
}, },
live: { live: {
add: function( proxy, data, namespaces, live ) { add: function( handleObj ) {
jQuery.extend( proxy, data || {} ); jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) );
proxy.guid += data.selector + data.live;
data.liveProxy = proxy;
jQuery.event.add( this, data.live, liveHandler, data );
}, },
remove: function( namespaces ) { remove: function( handleObj ) {
if ( namespaces.length ) { var remove = true,
var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)"); type = handleObj.origType.replace(rnamespaces, "");
jQuery.each( (jQuery.data(this, "events").live || {}), function() { jQuery.each( jQuery.data(this, "events").live || [], function() {
if ( name.test(this.type) ) { if ( type === this.origType.replace(rnamespaces, "") ) {
remove++; remove = false;
} return false;
});
if ( remove < 1 ) {
jQuery.event.remove( this, namespaces[0], liveHandler );
} }
});
if ( remove ) {
jQuery.event.remove( this, handleObj.origType, liveHandler );
} }
}, }
special: {}
}, },
beforeunload: { beforeunload: {
setup: function( data, namespaces, fn ) { setup: function( data, namespaces, fn ) {
// We only want to do this special case on windows // We only want to do this special case on windows
...@@ -918,7 +919,7 @@ jQuery.fn.extend({ ...@@ -918,7 +919,7 @@ jQuery.fn.extend({
jQuery.each(["live", "die"], function( i, name ) { jQuery.each(["live", "die"], function( i, name ) {
jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { 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, selector = origSelector || this.selector,
context = origSelector ? this : jQuery( this.context ); context = origSelector ? this : jQuery( this.context );
...@@ -927,23 +928,34 @@ jQuery.each(["live", "die"], function( i, name ) { ...@@ -927,23 +928,34 @@ jQuery.each(["live", "die"], function( i, name ) {
data = undefined; data = undefined;
} }
types = (types || "").split( /\s+/ ); types = (types || "").split(" ");
while ( (type = types[ i++ ]) != null ) { 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 = type === "focus" ? "focusin" : // focus --> focusin
type === "blur" ? "focusout" : // blur --> focusout type === "blur" ? "focusout" : // blur --> focusout
type === "hover" ? types.push("mouseleave") && "mouseenter" : // hover support type === "hover" ? types.push("mouseleave" + namespaces) && "mouseenter" : // hover support
type; type;
type += namespaces;
if ( name === "live" ) { if ( name === "live" ) {
// bind live handler // bind live handler
context.bind( liveConvert( type, selector ), { context.each(function(){
data: data, selector: selector, live: type jQuery.event.add( this, liveConvert( type, selector ),
}, fn ); { data: data, selector: selector, handler: fn, origType: type, origHandler: fn } );
});
} else { } else {
// unbind live handler // 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 ) { ...@@ -953,45 +965,46 @@ jQuery.each(["live", "die"], function( i, name ) {
function liveHandler( event ) { function liveHandler( event ) {
var stop, elems = [], selectors = [], args = arguments, var stop, elems = [], selectors = [], args = arguments,
related, match, fn, elem, j, i, l, data, related, match, handleObj, elem, j, i, l, data,
live = jQuery.extend({}, jQuery.data( this, "events" ).live); events = jQuery.data( this, "events" );
// Make sure we avoid non-left-click bubbling in Firefox (#3861) // 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; return;
} }
for ( j in live ) { event.liveFired = this;
fn = live[j];
if ( fn.live === event.type || var live = events.live.slice(0);
fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
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 { } else {
delete live[j]; live.splice( j--, 1 );
} }
} }
match = jQuery( event.target ).closest( selectors, event.currentTarget ); match = jQuery( event.target ).closest( selectors, event.currentTarget );
for ( i = 0, l = match.length; i < l; i++ ) { for ( i = 0, l = match.length; i < l; i++ ) {
for ( j in live ) { for ( j = 0; j < live.length; j++ ) {
fn = live[j]; handleObj = live[j];
elem = match[i].elem;
related = null; if ( match[i].selector === handleObj.selector ) {
elem = match[i].elem;
related = null;
if ( match[i].selector === fn.selector ) {
// Those two events require additional checking // Those two events require additional checking
if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) { if ( handleObj.origType === "mouseenter" || handleObj.origType === "mouseleave" ) {
related = jQuery( event.relatedTarget ).closest( fn.selector )[0]; related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
} }
if ( !related || related !== elem ) { if ( !related || related !== elem ) {
elems.push({ elem: elem, fn: fn }); elems.push({ elem: elem, handleObj: handleObj });
} }
} }
} }
...@@ -1000,8 +1013,10 @@ function liveHandler( event ) { ...@@ -1000,8 +1013,10 @@ function liveHandler( event ) {
for ( i = 0, l = elems.length; i < l; i++ ) { for ( i = 0, l = elems.length; i < l; i++ ) {
match = elems[i]; match = elems[i];
event.currentTarget = match.elem; event.currentTarget = match.elem;
event.data = match.fn.data; event.data = match.handleObj.data;
if ( match.fn.apply( match.elem, args ) === false ) { event.handleObj = match.handleObj;
if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) {
stop = false; stop = false;
break; break;
} }
......
...@@ -772,6 +772,7 @@ test(".live()/.die()", function() { ...@@ -772,6 +772,7 @@ test(".live()/.die()", function() {
equals( liveb, 0, "Click on body" ); equals( liveb, 0, "Click on body" );
// This should trigger two events // This should trigger two events
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendiv").trigger("click"); jQuery("div#nothiddendiv").trigger("click");
equals( submit, 0, "Click on div" ); equals( submit, 0, "Click on div" );
equals( div, 1, "Click on div" ); equals( div, 1, "Click on div" );
...@@ -779,55 +780,62 @@ test(".live()/.die()", function() { ...@@ -779,55 +780,62 @@ test(".live()/.die()", function() {
equals( liveb, 0, "Click on div" ); equals( liveb, 0, "Click on div" );
// This should trigger three events (w/ bubbling) // This should trigger three events (w/ bubbling)
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("click"); jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 0, "Click on inner div" ); equals( submit, 0, "Click on inner div" );
equals( div, 2, "Click on inner div" ); equals( div, 1, "Click on inner div" );
equals( livea, 2, "Click on inner div" ); equals( livea, 1, "Click on inner div" );
equals( liveb, 1, "Click on inner div" ); equals( liveb, 1, "Click on inner div" );
// This should trigger one submit // This should trigger one submit
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("submit"); jQuery("div#nothiddendivchild").trigger("submit");
equals( submit, 1, "Submit on div" ); equals( submit, 1, "Submit on div" );
equals( div, 2, "Submit on div" ); equals( div, 0, "Submit on div" );
equals( livea, 2, "Submit on div" ); equals( livea, 0, "Submit on div" );
equals( liveb, 1, "Submit on div" ); equals( liveb, 0, "Submit on div" );
// Make sure no other events were removed in the process // Make sure no other events were removed in the process
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("click"); jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 1, "die Click on inner div" ); equals( submit, 0, "die Click on inner div" );
equals( div, 3, "die Click on inner div" ); equals( div, 1, "die Click on inner div" );
equals( livea, 3, "die Click on inner div" ); equals( livea, 1, "die Click on inner div" );
equals( liveb, 2, "die Click on inner div" ); equals( liveb, 1, "die Click on inner div" );
// Now make sure that the removal works // Now make sure that the removal works
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").die("click"); jQuery("div#nothiddendivchild").die("click");
jQuery("div#nothiddendivchild").trigger("click"); jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 1, "die Click on inner div" ); equals( submit, 0, "die Click on inner div" );
equals( div, 4, "die Click on inner div" ); equals( div, 1, "die Click on inner div" );
equals( livea, 4, "die Click on inner div" ); equals( livea, 1, "die Click on inner div" );
equals( liveb, 2, "die Click on inner div" ); equals( liveb, 0, "die Click on inner div" );
// Make sure that the click wasn't removed too early // Make sure that the click wasn't removed too early
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendiv").trigger("click"); jQuery("div#nothiddendiv").trigger("click");
equals( submit, 1, "die Click on inner div" ); equals( submit, 0, "die Click on inner div" );
equals( div, 5, "die Click on inner div" ); equals( div, 1, "die Click on inner div" );
equals( livea, 5, "die Click on inner div" ); equals( livea, 1, "die Click on inner div" );
equals( liveb, 2, "die Click on inner div" ); equals( liveb, 0, "die Click on inner div" );
// Make sure that stopPropgation doesn't stop live events // 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").live("click", function(e){ liveb++; e.stopPropagation(); });
jQuery("div#nothiddendivchild").trigger("click"); jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 1, "stopPropagation Click on inner div" ); equals( submit, 0, "stopPropagation Click on inner div" );
equals( div, 6, "stopPropagation Click on inner div" ); equals( div, 1, "stopPropagation Click on inner div" );
equals( livea, 6, "stopPropagation Click on inner div" ); equals( livea, 1, "stopPropagation Click on inner div" );
equals( liveb, 3, "stopPropagation Click on inner div" ); equals( liveb, 1, "stopPropagation Click on inner div" );
// Make sure click events only fire with primary click // Make sure click events only fire with primary click
submit = 0, div = 0, livea = 0, liveb = 0;
var event = jQuery.Event("click"); var event = jQuery.Event("click");
event.button = 1; event.button = 1;
jQuery("div#nothiddendiv").trigger(event); jQuery("div#nothiddendiv").trigger(event);
equals( livea, 6, "live secondary click" ); equals( livea, 0, "live secondary click" );
jQuery("div#nothiddendivchild").die("click"); jQuery("div#nothiddendivchild").die("click");
jQuery("div#nothiddendiv").die("click"); jQuery("div#nothiddendiv").die("click");
...@@ -1043,6 +1051,44 @@ test("live with multiple events", function(){ ...@@ -1043,6 +1051,44 @@ test("live with multiple events", function(){
equals( count, 2, "Make sure both the click and submit were triggered." ); 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(){ test("live with change", function(){
var selectChange = 0, checkboxChange = 0; var selectChange = 0, checkboxChange = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment