A large refactor of the event handling logic. Data and namespace information is maintained in a separate object now, no longer on the event handler. Proxy functions are no longer needed, as a result. Additionally execution order of the handlers is maintained, fixing #4261, and the execution of handlers is maintained even while they're being removed. Live events will be refactored separately.
This commit is contained in:
parent
8a4b2102ff
commit
e7912805d6
2 changed files with 144 additions and 150 deletions
209
src/event.js
209
src/event.js
|
@ -29,18 +29,6 @@ jQuery.event = {
|
||||||
handler.guid = jQuery.guid++;
|
handler.guid = jQuery.guid++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if data is passed, bind to handler
|
|
||||||
if ( data !== undefined ) {
|
|
||||||
// Create temporary function pointer to original handler
|
|
||||||
var fn = handler;
|
|
||||||
|
|
||||||
// Create unique handler function, wrapped around original handler
|
|
||||||
handler = jQuery.proxy( fn );
|
|
||||||
|
|
||||||
// Store data in unique handler
|
|
||||||
handler.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init the element's event structure
|
// Init the element's event structure
|
||||||
var elemData = jQuery.data( elem );
|
var elemData = jQuery.data( elem );
|
||||||
|
|
||||||
|
@ -50,25 +38,22 @@ jQuery.event = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var events = elemData.events || (elemData.events = {}),
|
var events = elemData.events = elemData.events || {},
|
||||||
handle = elemData.handle, eventHandle;
|
eventHandle = elemData.handle, eventHandle;
|
||||||
|
|
||||||
if ( !handle ) {
|
if ( !eventHandle ) {
|
||||||
eventHandle = function() {
|
elemData.handle = eventHandle = function() {
|
||||||
// Handle the second event of a trigger and when
|
// Handle the second event of a trigger and when
|
||||||
// an event is called after a page has unloaded
|
// an event is called after a page has unloaded
|
||||||
return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
|
return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
|
||||||
jQuery.event.handle.apply( eventHandle.elem, arguments ) :
|
jQuery.event.handle.apply( eventHandle.elem, arguments ) :
|
||||||
undefined;
|
undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
handle = elemData.handle = eventHandle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add elem as a property of the handle function
|
// Add elem as a property of the handle function
|
||||||
// This is to prevent a memory leak with non-native
|
// This is to prevent a memory leak with non-native events in IE.
|
||||||
// event in IE.
|
eventHandle.elem = elem;
|
||||||
handle.elem = elem;
|
|
||||||
|
|
||||||
// Handle multiple events separated by a space
|
// Handle multiple events separated by a space
|
||||||
// jQuery(...).bind("mouseover mouseout", fn);
|
// jQuery(...).bind("mouseover mouseout", fn);
|
||||||
|
@ -77,32 +62,32 @@ jQuery.event = {
|
||||||
var type, i = 0, namespaces;
|
var type, i = 0, namespaces;
|
||||||
|
|
||||||
while ( (type = types[ i++ ]) ) {
|
while ( (type = types[ i++ ]) ) {
|
||||||
if ( i > 1 ) {
|
var handleObj = {
|
||||||
handler = jQuery.proxy( handler );
|
handler: handler,
|
||||||
|
data: data,
|
||||||
if ( data !== undefined ) {
|
namespace: "",
|
||||||
handler.data = data;
|
guid: handler.guid
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Namespaced event handlers
|
// Namespaced event handlers
|
||||||
if ( type.indexOf(".") > -1 ) {
|
if ( type.indexOf(".") > -1 ) {
|
||||||
namespaces = type.split(".");
|
namespaces = type.split(".");
|
||||||
type = namespaces.shift();
|
type = namespaces.shift();
|
||||||
handler.type = namespaces.slice(0).sort().join(".");
|
handleObj.namespace = namespaces.slice(0).sort().join(".");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
namespaces = [];
|
namespaces = [];
|
||||||
handler.type = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleObj.type = type;
|
||||||
|
|
||||||
// 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 ],
|
||||||
special = this.special[ type ] || {};
|
special = jQuery.event.special[ type ] || {};
|
||||||
|
|
||||||
// Init the event handler queue
|
// Init the event handler queue
|
||||||
if ( !handlers ) {
|
if ( !handlers ) {
|
||||||
handlers = events[ type ] = {};
|
handlers = events[ type ] = [];
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -110,29 +95,23 @@ jQuery.event = {
|
||||||
if ( !special.setup || special.setup.call( elem, data, namespaces, handler ) === false ) {
|
if ( !special.setup || special.setup.call( elem, data, namespaces, handler ) === 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, handle, false );
|
elem.addEventListener( type, eventHandle, false );
|
||||||
|
|
||||||
} else if ( elem.attachEvent ) {
|
} else if ( elem.attachEvent ) {
|
||||||
elem.attachEvent( "on" + type, handle );
|
elem.attachEvent( "on" + type, eventHandle );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( special.add ) {
|
if ( special.add ) {
|
||||||
var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers );
|
special.add.call( elem, handleObj );
|
||||||
if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
|
|
||||||
modifiedHandler.guid = modifiedHandler.guid || handler.guid;
|
|
||||||
modifiedHandler.data = modifiedHandler.data || handler.data;
|
|
||||||
modifiedHandler.type = modifiedHandler.type || handler.type;
|
|
||||||
handler = modifiedHandler;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the function to the element's handler list
|
// 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
|
// 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
|
// Nullify elem to prevent memory leaks in IE
|
||||||
|
@ -142,43 +121,46 @@ jQuery.event = {
|
||||||
global: {},
|
global: {},
|
||||||
|
|
||||||
// Detach an event or set of events from an element
|
// 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
|
// don't do events on text and comment nodes
|
||||||
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
|
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var elemData = jQuery.data( elem );
|
var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
|
||||||
|
elemData = jQuery.data( elem ),
|
||||||
|
events = elemData && elemData.events;
|
||||||
|
|
||||||
if ( !elemData ) {
|
if ( !elemData || !events ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var events = elemData.events, ret, type, fn;
|
|
||||||
|
|
||||||
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
|
// types is actually an event object here
|
||||||
if ( types.type ) {
|
if ( types && types.type ) {
|
||||||
handler = types.handler;
|
handler = types.handler;
|
||||||
types = types.type;
|
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
|
// Handle multiple events separated by a space
|
||||||
// jQuery(...).unbind("mouseover mouseout", fn);
|
// jQuery(...).unbind("mouseover mouseout", fn);
|
||||||
types = types.split(" ");
|
types = types.split(" ");
|
||||||
|
|
||||||
var i = 0, all, namespaces, namespace;
|
|
||||||
|
|
||||||
while ( (type = types[ i++ ]) ) {
|
while ( (type = types[ i++ ]) ) {
|
||||||
|
origType = type;
|
||||||
|
handleObj = null;
|
||||||
all = type.indexOf(".") < 0;
|
all = type.indexOf(".") < 0;
|
||||||
namespaces = null;
|
namespaces = [];
|
||||||
|
|
||||||
if ( !all ) {
|
if ( !all ) {
|
||||||
// Namespaced event handlers
|
// Namespaced event handlers
|
||||||
|
@ -187,39 +169,54 @@ jQuery.event = {
|
||||||
|
|
||||||
namespace = new RegExp("(^|\\.)" +
|
namespace = new RegExp("(^|\\.)" +
|
||||||
jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)")
|
jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)")
|
||||||
|
|
||||||
} else {
|
|
||||||
namespaces = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var special = this.special[ type ] || {};
|
eventType = events[ type ];
|
||||||
|
|
||||||
if ( events[ type ] ) {
|
if ( !eventType ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !handler ) {
|
||||||
|
for ( var 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
special = jQuery.event.special[ type ] || {};
|
||||||
|
|
||||||
|
for ( var j = pos || 0; j < eventType.length; j++ ) {
|
||||||
|
handleObj = eventType[ j ];
|
||||||
|
|
||||||
|
if ( handler.guid === handleObj.guid ) {
|
||||||
// remove the given handler for the given type
|
// remove the given handler for the given type
|
||||||
if ( handler ) {
|
if ( all || namespace.test( handleObj.namespace ) ) {
|
||||||
fn = events[ type ][ handler.guid ];
|
fn = handleObj.handler;
|
||||||
delete events[ type ][ handler.guid ];
|
|
||||||
|
|
||||||
// remove all handlers for the given type
|
if ( pos == null ) {
|
||||||
} else {
|
eventType.splice( j--, 1 );
|
||||||
for ( var handle in events[ type ] ) {
|
|
||||||
// Handle the removal of namespaced events
|
|
||||||
if ( all || namespace.test( events[ type ][ handle ].type ) ) {
|
|
||||||
delete events[ type ][ handle ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( special.remove ) {
|
if ( special.remove ) {
|
||||||
special.remove.call( elem, namespaces, fn);
|
special.remove.call( elem, namespaces, handleObj );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( pos != null ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove generic event handler if no more handlers exist
|
// remove generic event handler if no more handlers exist
|
||||||
for ( ret in events[ type ] ) {
|
if ( jQuery.isEmptyObject( events[ type ] ) ) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !ret ) {
|
|
||||||
if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
|
if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
|
||||||
removeEvent( elem, type, elemData.handle );
|
removeEvent( elem, type, elemData.handle );
|
||||||
}
|
}
|
||||||
|
@ -228,15 +225,9 @@ jQuery.event = {
|
||||||
delete events[ type ];
|
delete events[ type ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the expando if it's no longer used
|
// Remove the expando if it's no longer used
|
||||||
for ( ret in events ) {
|
if ( jQuery.isEmptyObject( events ) ) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !ret ) {
|
|
||||||
var handle = elemData.handle;
|
var handle = elemData.handle;
|
||||||
if ( handle ) {
|
if ( handle ) {
|
||||||
handle.elem = null;
|
handle.elem = null;
|
||||||
|
@ -249,7 +240,6 @@ jQuery.event = {
|
||||||
jQuery.removeData( elem );
|
jQuery.removeData( elem );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// bubbling is internal
|
// bubbling is internal
|
||||||
|
@ -278,7 +268,7 @@ jQuery.event = {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
// Only trigger if we've ever bound an event for it
|
// 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() {
|
jQuery.each( jQuery.cache, function() {
|
||||||
if ( this.events && this.events[type] ) {
|
if ( this.events && this.events[type] ) {
|
||||||
jQuery.event.trigger( event, data, this.handle.elem );
|
jQuery.event.trigger( event, data, this.handle.elem );
|
||||||
|
@ -344,7 +334,7 @@ jQuery.event = {
|
||||||
target[ "on" + type ] = null;
|
target[ "on" + type ] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.triggered = true;
|
jQuery.event.triggered = true;
|
||||||
target[ type ]();
|
target[ type ]();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,40 +345,43 @@ jQuery.event = {
|
||||||
target[ "on" + type ] = old;
|
target[ "on" + type ] = old;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.triggered = false;
|
jQuery.event.triggered = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handle: function( event ) {
|
handle: function( event ) {
|
||||||
// returned undefined or false
|
var all, handlers, namespaces, namespace, events;
|
||||||
var all, handlers;
|
|
||||||
|
|
||||||
event = arguments[0] = jQuery.event.fix( event || window.event );
|
event = arguments[0] = jQuery.event.fix( event || window.event );
|
||||||
event.currentTarget = this;
|
event.currentTarget = this;
|
||||||
|
|
||||||
// Namespaced event handlers
|
// Namespaced event handlers
|
||||||
var namespaces = event.type.split(".");
|
all = event.type.indexOf(".") < 0;
|
||||||
|
|
||||||
|
if ( !all ) {
|
||||||
|
namespaces = event.type.split(".");
|
||||||
event.type = namespaces.shift();
|
event.type = namespaces.shift();
|
||||||
|
namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
|
||||||
|
}
|
||||||
|
|
||||||
// Cache this now, all = true means, any handler
|
var events = jQuery.data(this, "events"), handlers = events[ event.type ];
|
||||||
all = !namespaces.length && !event.exclusive;
|
|
||||||
|
|
||||||
var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
|
if ( events && handlers ) {
|
||||||
|
// Clone the handlers to prevent manipulation
|
||||||
|
handlers = handlers.slice(0);
|
||||||
|
|
||||||
handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
|
for ( var j = 0, l = handlers.length; j < l; j++ ) {
|
||||||
|
var handleObj = handlers[ j ];
|
||||||
for ( var j in handlers ) {
|
|
||||||
var handler = handlers[ j ];
|
|
||||||
|
|
||||||
// Filter the functions by class
|
// Filter the functions by class
|
||||||
if ( all || namespace.test(handler.type) ) {
|
if ( (all && !event.exclusive) || namespace.test( handleObj.namespace ) ) {
|
||||||
// Pass in a reference to the handler function itself
|
// Pass in a reference to the handler function itself
|
||||||
// So that we can later remove it
|
// So that we can later remove it
|
||||||
event.handler = handler;
|
event.handler = handleObj.handler;
|
||||||
event.data = handler.data;
|
event.data = handleObj.data;
|
||||||
|
|
||||||
var ret = handler.apply( this, arguments );
|
var ret = handleObj.handler.apply( this, arguments );
|
||||||
|
|
||||||
if ( ret !== undefined ) {
|
if ( ret !== undefined ) {
|
||||||
event.result = ret;
|
event.result = ret;
|
||||||
|
@ -401,7 +394,7 @@ jQuery.event = {
|
||||||
if ( event.isImmediatePropagationStopped() ) {
|
if ( event.isImmediatePropagationStopped() ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,8 +88,9 @@ test("bind(), namespace with special add", function() {
|
||||||
},
|
},
|
||||||
setup: function(){},
|
setup: function(){},
|
||||||
teardown: function(){},
|
teardown: function(){},
|
||||||
add: function( handler, data, namespaces ) {
|
add: function( handleObj ) {
|
||||||
return function(e) {
|
var handler = handleObj.handler;
|
||||||
|
handleObj.handler = function(e) {
|
||||||
e.xyz = ++i;
|
e.xyz = ++i;
|
||||||
handler.apply( this, arguments );
|
handler.apply( this, arguments );
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue