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
211
src/event.js
211
src/event.js
|
@ -29,18 +29,6 @@ jQuery.event = {
|
|||
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
|
||||
var elemData = jQuery.data( elem );
|
||||
|
||||
|
@ -50,25 +38,22 @@ jQuery.event = {
|
|||
return;
|
||||
}
|
||||
|
||||
var events = elemData.events || (elemData.events = {}),
|
||||
handle = elemData.handle, eventHandle;
|
||||
var events = elemData.events = elemData.events || {},
|
||||
eventHandle = elemData.handle, eventHandle;
|
||||
|
||||
if ( !handle ) {
|
||||
eventHandle = function() {
|
||||
if ( !eventHandle ) {
|
||||
elemData.handle = eventHandle = function() {
|
||||
// Handle the second event of a trigger and when
|
||||
// an event is called after a page has unloaded
|
||||
return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
|
||||
jQuery.event.handle.apply( eventHandle.elem, arguments ) :
|
||||
undefined;
|
||||
};
|
||||
|
||||
handle = elemData.handle = eventHandle;
|
||||
}
|
||||
|
||||
// Add elem as a property of the handle function
|
||||
// This is to prevent a memory leak with non-native
|
||||
// event in IE.
|
||||
handle.elem = elem;
|
||||
// This is to prevent a memory leak with non-native events in IE.
|
||||
eventHandle.elem = elem;
|
||||
|
||||
// Handle multiple events separated by a space
|
||||
// jQuery(...).bind("mouseover mouseout", fn);
|
||||
|
@ -77,62 +62,56 @@ jQuery.event = {
|
|||
var type, i = 0, namespaces;
|
||||
|
||||
while ( (type = types[ i++ ]) ) {
|
||||
if ( i > 1 ) {
|
||||
handler = jQuery.proxy( handler );
|
||||
|
||||
if ( data !== undefined ) {
|
||||
handler.data = data;
|
||||
}
|
||||
}
|
||||
var handleObj = {
|
||||
handler: handler,
|
||||
data: data,
|
||||
namespace: "",
|
||||
guid: handler.guid
|
||||
};
|
||||
|
||||
// Namespaced event handlers
|
||||
if ( type.indexOf(".") > -1 ) {
|
||||
namespaces = type.split(".");
|
||||
type = namespaces.shift();
|
||||
handler.type = namespaces.slice(0).sort().join(".");
|
||||
handleObj.namespace = namespaces.slice(0).sort().join(".");
|
||||
|
||||
} else {
|
||||
namespaces = [];
|
||||
handler.type = "";
|
||||
}
|
||||
|
||||
handleObj.type = type;
|
||||
|
||||
// Get the current list of functions bound to this event
|
||||
var handlers = events[ type ],
|
||||
special = this.special[ type ] || {};
|
||||
special = jQuery.event.special[ type ] || {};
|
||||
|
||||
// Init the event handler queue
|
||||
if ( !handlers ) {
|
||||
handlers = events[ type ] = {};
|
||||
handlers = events[ type ] = [];
|
||||
|
||||
// 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, handler ) === false ) {
|
||||
// Bind the global event handler to the element
|
||||
if ( elem.addEventListener ) {
|
||||
elem.addEventListener( type, handle, false );
|
||||
elem.addEventListener( type, eventHandle, false );
|
||||
|
||||
} else if ( elem.attachEvent ) {
|
||||
elem.attachEvent( "on" + type, handle );
|
||||
elem.attachEvent( "on" + type, eventHandle );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( special.add ) {
|
||||
var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers );
|
||||
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;
|
||||
}
|
||||
special.add.call( elem, handleObj );
|
||||
}
|
||||
|
||||
// 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
|
||||
this.global[ type ] = true;
|
||||
jQuery.event.global[ type ] = true;
|
||||
}
|
||||
|
||||
// Nullify elem to prevent memory leaks in IE
|
||||
|
@ -142,43 +121,46 @@ jQuery.event = {
|
|||
global: {},
|
||||
|
||||
// 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
|
||||
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
if ( types.type ) {
|
||||
if ( types && types.type ) {
|
||||
handler = types.handler;
|
||||
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
|
||||
// jQuery(...).unbind("mouseover mouseout", fn);
|
||||
types = types.split(" ");
|
||||
|
||||
var i = 0, all, namespaces, namespace;
|
||||
|
||||
while ( (type = types[ i++ ]) ) {
|
||||
origType = type;
|
||||
handleObj = null;
|
||||
all = type.indexOf(".") < 0;
|
||||
namespaces = null;
|
||||
namespaces = [];
|
||||
|
||||
if ( !all ) {
|
||||
// Namespaced event handlers
|
||||
|
@ -187,39 +169,54 @@ jQuery.event = {
|
|||
|
||||
namespace = new RegExp("(^|\\.)" +
|
||||
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
|
||||
if ( handler ) {
|
||||
fn = events[ type ][ handler.guid ];
|
||||
delete events[ type ][ handler.guid ];
|
||||
if ( all || namespace.test( handleObj.namespace ) ) {
|
||||
fn = handleObj.handler;
|
||||
|
||||
// remove all handlers for the given type
|
||||
} else {
|
||||
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 ( pos == null ) {
|
||||
eventType.splice( j--, 1 );
|
||||
}
|
||||
|
||||
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
|
||||
for ( ret in events[ type ] ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !ret ) {
|
||||
if ( jQuery.isEmptyObject( events[ type ] ) ) {
|
||||
if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
|
||||
removeEvent( elem, type, elemData.handle );
|
||||
}
|
||||
|
@ -228,15 +225,9 @@ jQuery.event = {
|
|||
delete events[ type ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the expando if it's no longer used
|
||||
for ( ret in events ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !ret ) {
|
||||
if ( jQuery.isEmptyObject( events ) ) {
|
||||
var handle = elemData.handle;
|
||||
if ( handle ) {
|
||||
handle.elem = null;
|
||||
|
@ -249,7 +240,6 @@ jQuery.event = {
|
|||
jQuery.removeData( elem );
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// bubbling is internal
|
||||
|
@ -278,7 +268,7 @@ jQuery.event = {
|
|||
event.stopPropagation();
|
||||
|
||||
// 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() {
|
||||
if ( this.events && this.events[type] ) {
|
||||
jQuery.event.trigger( event, data, this.handle.elem );
|
||||
|
@ -344,7 +334,7 @@ jQuery.event = {
|
|||
target[ "on" + type ] = null;
|
||||
}
|
||||
|
||||
this.triggered = true;
|
||||
jQuery.event.triggered = true;
|
||||
target[ type ]();
|
||||
}
|
||||
|
||||
|
@ -355,40 +345,43 @@ jQuery.event = {
|
|||
target[ "on" + type ] = old;
|
||||
}
|
||||
|
||||
this.triggered = false;
|
||||
jQuery.event.triggered = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handle: function( event ) {
|
||||
// returned undefined or false
|
||||
var all, handlers;
|
||||
var all, handlers, namespaces, namespace, events;
|
||||
|
||||
event = arguments[0] = jQuery.event.fix( event || window.event );
|
||||
event.currentTarget = this;
|
||||
|
||||
// Namespaced event handlers
|
||||
var namespaces = event.type.split(".");
|
||||
all = event.type.indexOf(".") < 0;
|
||||
|
||||
if ( !all ) {
|
||||
namespaces = event.type.split(".");
|
||||
event.type = namespaces.shift();
|
||||
namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
|
||||
}
|
||||
|
||||
// Cache this now, all = true means, any handler
|
||||
all = !namespaces.length && !event.exclusive;
|
||||
var events = jQuery.data(this, "events"), handlers = events[ event.type ];
|
||||
|
||||
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 in handlers ) {
|
||||
var handler = handlers[ j ];
|
||||
for ( var j = 0, l = handlers.length; j < l; j++ ) {
|
||||
var handleObj = handlers[ j ];
|
||||
|
||||
// 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
|
||||
// So that we can later remove it
|
||||
event.handler = handler;
|
||||
event.data = handler.data;
|
||||
event.handler = handleObj.handler;
|
||||
event.data = handleObj.data;
|
||||
|
||||
var ret = handler.apply( this, arguments );
|
||||
var ret = handleObj.handler.apply( this, arguments );
|
||||
|
||||
if ( ret !== undefined ) {
|
||||
event.result = ret;
|
||||
|
@ -401,7 +394,7 @@ jQuery.event = {
|
|||
if ( event.isImmediatePropagationStopped() ) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,8 +88,9 @@ test("bind(), namespace with special add", function() {
|
|||
},
|
||||
setup: function(){},
|
||||
teardown: function(){},
|
||||
add: function( handler, data, namespaces ) {
|
||||
return function(e) {
|
||||
add: function( handleObj ) {
|
||||
var handler = handleObj.handler;
|
||||
handleObj.handler = function(e) {
|
||||
e.xyz = ++i;
|
||||
handler.apply( this, arguments );
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue