new special events api, ready is now a first class event that you can use bind, unbind or the ready helper, two new events: mouseenter and mouseleave, the hover helper method now uses mouseenter and mouseleave, bind and unbind can now take a space sperated list of event types
This commit is contained in:
parent
01e8f33e44
commit
b264f789b9
2 changed files with 219 additions and 171 deletions
359
src/event.js
359
src/event.js
|
@ -7,7 +7,7 @@ jQuery.event = {
|
|||
|
||||
// Bind an event to an element
|
||||
// Original by Dean Edwards
|
||||
add: function(element, type, handler, data) {
|
||||
add: function(element, types, handler, data) {
|
||||
if ( element.nodeType == 3 || element.nodeType == 8 )
|
||||
return;
|
||||
|
||||
|
@ -38,99 +38,113 @@ jQuery.event = {
|
|||
handler.guid = fn.guid;
|
||||
}
|
||||
|
||||
// Namespaced event handlers
|
||||
var parts = type.split(".");
|
||||
type = parts[0];
|
||||
handler.type = parts[1];
|
||||
|
||||
// Init the element's event structure
|
||||
var events = jQuery.data(element, "events") || jQuery.data(element, "events", {});
|
||||
var events = jQuery.data(element, "events") || jQuery.data(element, "events", {}),
|
||||
handle = jQuery.data(element, "handle") || jQuery.data(element, "handle", function(){
|
||||
// returned undefined or false
|
||||
var val;
|
||||
|
||||
// Handle the second event of a trigger and when
|
||||
// an event is called after a page has unloaded
|
||||
if ( typeof jQuery == "undefined" || jQuery.event.triggered )
|
||||
return val;
|
||||
|
||||
val = jQuery.event.handle.apply(element, arguments);
|
||||
|
||||
var handle = jQuery.data(element, "handle") || jQuery.data(element, "handle", function(){
|
||||
// returned undefined or false
|
||||
var val;
|
||||
|
||||
// Handle the second event of a trigger and when
|
||||
// an event is called after a page has unloaded
|
||||
if ( typeof jQuery == "undefined" || jQuery.event.triggered )
|
||||
return val;
|
||||
});
|
||||
|
||||
val = jQuery.event.handle.apply(element, arguments);
|
||||
|
||||
return val;
|
||||
});
|
||||
// Handle multiple events seperated by a space
|
||||
// jQuery(...).bind("mouseover mouseout", fn);
|
||||
jQuery.each(types.split(/\s+/), function(index, type) {
|
||||
// Namespaced event handlers
|
||||
var parts = type.split(".");
|
||||
type = parts[0];
|
||||
handler.type = parts[1];
|
||||
|
||||
// Get the current list of functions bound to this event
|
||||
var handlers = events[type];
|
||||
// Get the current list of functions bound to this event
|
||||
var handlers = events[type];
|
||||
|
||||
// Init the event handler queue
|
||||
if (!handlers) {
|
||||
handlers = events[type] = {};
|
||||
|
||||
// And bind the global event handler to the element
|
||||
if (element.addEventListener)
|
||||
element.addEventListener(type, handle, false);
|
||||
else
|
||||
element.attachEvent("on" + type, handle);
|
||||
}
|
||||
// Init the event handler queue
|
||||
if (!handlers) {
|
||||
handlers = events[type] = {};
|
||||
|
||||
// Check for a special event handler
|
||||
// Only use addEventListener/attachEvent if the special
|
||||
// events handler returns false
|
||||
if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(element) === false ) {
|
||||
// Bind the global event handler to the element
|
||||
if (element.addEventListener)
|
||||
element.addEventListener(type, handle, false);
|
||||
else if (element.attachEvent)
|
||||
element.attachEvent("on" + type, handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the function to the element's handler list
|
||||
handlers[handler.guid] = handler;
|
||||
// Add the function to the element's handler list
|
||||
handlers[handler.guid] = handler;
|
||||
|
||||
// Keep track of which events have been used, for global triggering
|
||||
this.global[type] = true;
|
||||
// Keep track of which events have been used, for global triggering
|
||||
jQuery.event.global[type] = true;
|
||||
});
|
||||
},
|
||||
|
||||
guid: 1,
|
||||
global: {},
|
||||
|
||||
// Detach an event or set of events from an element
|
||||
remove: function(element, type, handler) {
|
||||
remove: function(element, types, handler) {
|
||||
// don't do events on text and comment nodes
|
||||
if ( element.nodeType == 3 || element.nodeType == 8 )
|
||||
return;
|
||||
|
||||
var events = jQuery.data(element, "events"), ret, index;
|
||||
|
||||
// Namespaced event handlers
|
||||
if ( typeof type == "string" ) {
|
||||
var parts = type.split(".");
|
||||
type = parts[0];
|
||||
}
|
||||
|
||||
if ( events ) {
|
||||
// type is actually an event object here
|
||||
if ( type && type.type ) {
|
||||
handler = type.handler;
|
||||
type = type.type;
|
||||
}
|
||||
|
||||
if ( !type ) {
|
||||
for ( type in events )
|
||||
// Unbind all events for the element
|
||||
if ( !types )
|
||||
for ( var type in events )
|
||||
this.remove( element, type );
|
||||
|
||||
} else if ( events[type] ) {
|
||||
// remove the given handler for the given type
|
||||
if ( handler )
|
||||
delete events[type][handler.guid];
|
||||
|
||||
// remove all handlers for the given type
|
||||
else
|
||||
for ( handler in events[type] )
|
||||
// Handle the removal of namespaced events
|
||||
if ( !parts[1] || events[type][handler].type == parts[1] )
|
||||
delete events[type][handler];
|
||||
|
||||
// remove generic event handler if no more handlers exist
|
||||
for ( ret in events[type] ) break;
|
||||
if ( !ret ) {
|
||||
if (element.removeEventListener)
|
||||
element.removeEventListener(type, jQuery.data(element, "handle"), false);
|
||||
else
|
||||
element.detachEvent("on" + type, jQuery.data(element, "handle"));
|
||||
ret = null;
|
||||
delete events[type];
|
||||
else {
|
||||
// types is actually an event object here
|
||||
if ( types.type ) {
|
||||
handler = types.handler;
|
||||
types = types.type;
|
||||
}
|
||||
|
||||
// Handle multiple events seperated by a space
|
||||
// jQuery(...).unbind("mouseover mouseout", fn);
|
||||
jQuery.each(types.split(/\s+/), function(index, type){
|
||||
// Namespaced event handlers
|
||||
var parts = type.split(".");
|
||||
type = parts[0];
|
||||
|
||||
if ( events[type] ) {
|
||||
// remove the given handler for the given type
|
||||
if ( handler )
|
||||
delete events[type][handler.guid];
|
||||
|
||||
// remove all handlers for the given type
|
||||
else
|
||||
for ( handler in events[type] )
|
||||
// Handle the removal of namespaced events
|
||||
if ( !parts[1] || events[type][handler].type == parts[1] )
|
||||
delete events[type][handler];
|
||||
|
||||
// remove generic event handler if no more handlers exist
|
||||
for ( ret in events[type] ) break;
|
||||
if ( !ret ) {
|
||||
if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(this, element) === false ) {
|
||||
if (element.removeEventListener)
|
||||
element.removeEventListener(type, jQuery.data(element, "handle"), false);
|
||||
else if (element.detachEvent)
|
||||
element.detachEvent("on" + type, jQuery.data(element, "handle"));
|
||||
}
|
||||
ret = null;
|
||||
delete events[type];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the expando if it's no longer used
|
||||
|
@ -304,6 +318,98 @@ jQuery.event = {
|
|||
event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
|
||||
|
||||
return event;
|
||||
},
|
||||
|
||||
special: {
|
||||
ready: {
|
||||
setup: function() {
|
||||
var handler = jQuery.event.special.ready.handler;
|
||||
|
||||
// Mozilla, Opera and webkit nightlies currently support this event
|
||||
if ( document.addEventListener )
|
||||
// Use the handy event callback
|
||||
document.addEventListener( "DOMContentLoaded", handler, false );
|
||||
|
||||
// If Safari or IE is used
|
||||
// Continually check to see if the document is ready
|
||||
if (jQuery.browser.msie || jQuery.browser.safari ) (function(){
|
||||
try {
|
||||
// If IE is used, use the trick by Diego Perini
|
||||
// http://javascript.nwbox.com/IEContentLoaded/
|
||||
if ( jQuery.browser.msie || document.readyState != "loaded" && document.readyState != "complete" )
|
||||
document.documentElement.doScroll("left");
|
||||
} catch( error ) {
|
||||
return setTimeout( arguments.callee, 0 );
|
||||
}
|
||||
|
||||
// and execute any waiting functions
|
||||
handler();
|
||||
})();
|
||||
|
||||
// A fallback to window.onload, that will always work
|
||||
jQuery.event.add( window, "load", handler );
|
||||
},
|
||||
|
||||
teardown: function() {return;},
|
||||
|
||||
handler: function() {
|
||||
// Make sure that the DOM is not already loaded
|
||||
if ( !jQuery.isReady ) {
|
||||
// Remember that the DOM is ready
|
||||
jQuery.isReady = true;
|
||||
jQuery(document).triggerHandler("ready");
|
||||
jQuery(document).unbind("ready");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mouseenter: {
|
||||
setup: function() {
|
||||
if (jQuery.browser.msie) return false;
|
||||
jQuery(this).bind('mouseover', jQuery.event.special.mouseenter.handler);
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
if (jQuery.browser.msie) return false;
|
||||
jQuery(this).unbind('mouseover', jQuery.event.special.mouseenter.handler);
|
||||
},
|
||||
|
||||
handler: function(event) {
|
||||
var args = Array.prototype.slice.call( arguments, 1 );
|
||||
// If we actually just moused on to a sub-element, ignore it
|
||||
if ( withinElement(event, this) ) return;
|
||||
// Execute the right handlers by setting the event type to mouseenter
|
||||
event.type = 'mouseenter';
|
||||
// Include the event object as the first argument
|
||||
args.unshift(event);
|
||||
var val = jQuery.event.handle.apply(this, args);
|
||||
return val;
|
||||
}
|
||||
},
|
||||
|
||||
mouseleave: {
|
||||
setup: function() {
|
||||
if (jQuery.browser.msie) return false;
|
||||
jQuery(this).bind('mouseout', jQuery.event.special.mouseleave.handler);
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
if (jQuery.browser.msie) return false;
|
||||
jQuery(this).unbind('mouseout', jQuery.event.special.mouseleave.handler);
|
||||
},
|
||||
|
||||
handler: function(event) {
|
||||
var args = Array.prototype.slice.call( arguments, 1 );
|
||||
// If we actually just moused on to a sub-element, ignore it
|
||||
if ( withinElement(event, this) ) return false;
|
||||
// Execute the right handlers by setting the event type to mouseleave
|
||||
event.type = 'mouseleave';
|
||||
// Include the event object as the first argument
|
||||
args.unshift(event);
|
||||
var val = jQuery.event.handle.apply(this, args);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -357,78 +463,15 @@ jQuery.fn.extend({
|
|||
},
|
||||
|
||||
hover: function(fnOver, fnOut) {
|
||||
|
||||
// A private function for handling mouse 'hovering'
|
||||
function handleHover(event) {
|
||||
// Check if mouse(over|out) are still within the same parent element
|
||||
var parent = event.relatedTarget;
|
||||
|
||||
// Traverse up the tree
|
||||
while ( parent && parent != this ) try { parent = parent.parentNode; } catch(error) { parent = this; };
|
||||
|
||||
// If we actually just moused on to a sub-element, ignore it
|
||||
if ( parent == this )
|
||||
return true;
|
||||
|
||||
// Execute the right function
|
||||
return (event.type == "mouseover" ? fnOver : fnOut).apply(this, [event]);
|
||||
}
|
||||
|
||||
// Bind the function to the two event listeners
|
||||
return this.mouseover(handleHover).mouseout(handleHover);
|
||||
},
|
||||
|
||||
ready: function(fn) {
|
||||
// Attach the listeners
|
||||
bindReady();
|
||||
|
||||
// If the DOM is already ready
|
||||
if ( jQuery.isReady )
|
||||
// Execute the function immediately
|
||||
fn.apply( document, [jQuery] );
|
||||
|
||||
// Otherwise, remember the function for later
|
||||
else
|
||||
// Add the function to the wait list
|
||||
jQuery.readyList.push( function() { return fn.apply(this, [jQuery]); } );
|
||||
|
||||
return this;
|
||||
return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
|
||||
}
|
||||
});
|
||||
|
||||
jQuery.extend({
|
||||
/*
|
||||
* All the code that makes DOM Ready work nicely.
|
||||
*/
|
||||
isReady: false,
|
||||
readyList: [],
|
||||
|
||||
// Handle when the DOM is ready
|
||||
ready: function() {
|
||||
// Make sure that the DOM is not already loaded
|
||||
if ( !jQuery.isReady ) {
|
||||
// Remember that the DOM is ready
|
||||
jQuery.isReady = true;
|
||||
|
||||
// If there are functions bound, to execute
|
||||
if ( jQuery.readyList ) {
|
||||
// Execute all of them
|
||||
jQuery.each( jQuery.readyList, function(){
|
||||
this.apply( document );
|
||||
});
|
||||
|
||||
// Reset the list of functions
|
||||
jQuery.readyList = null;
|
||||
}
|
||||
// Remove event listener to avoid memory leak
|
||||
if ( document.removeEventListener )
|
||||
document.removeEventListener( "DOMContentLoaded", jQuery.ready, false );
|
||||
}
|
||||
}
|
||||
isReady: false
|
||||
});
|
||||
|
||||
|
||||
jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
|
||||
jQuery.each( ("blur,focus,load,ready,resize,scroll,unload,click,dblclick," +
|
||||
"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
|
||||
"submit,keydown,keypress,keyup,error").split(","), function(i, name){
|
||||
|
||||
|
@ -438,39 +481,19 @@ jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
|
|||
};
|
||||
});
|
||||
|
||||
var readyBound = false;
|
||||
|
||||
function bindReady(){
|
||||
if ( readyBound ) return;
|
||||
readyBound = true;
|
||||
|
||||
// Mozilla, Opera and webkit nightlies currently support this event
|
||||
if ( document.addEventListener )
|
||||
// Use the handy event callback
|
||||
document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
|
||||
|
||||
// If Safari or IE is used
|
||||
// Continually check to see if the document is ready
|
||||
if (jQuery.browser.msie || jQuery.browser.safari ) (function(){
|
||||
try {
|
||||
// If IE is used, use the trick by Diego Perini
|
||||
// http://javascript.nwbox.com/IEContentLoaded/
|
||||
if ( jQuery.browser.msie || document.readyState != "loaded" && document.readyState != "complete" )
|
||||
document.documentElement.doScroll("left");
|
||||
} catch( error ) {
|
||||
return setTimeout( arguments.callee, 0 );
|
||||
}
|
||||
|
||||
// and execute any waiting functions
|
||||
jQuery.ready();
|
||||
})();
|
||||
|
||||
// A fallback to window.onload, that will always work
|
||||
jQuery.event.add( window, "load", jQuery.ready );
|
||||
}
|
||||
// Checks if an event happened on an element within another element
|
||||
// Used in jQuery.event.special.mouseenter and mouseleave handlers
|
||||
var withinElement = function(event, element) {
|
||||
// Check if mouse(over|out) are still within the same parent element
|
||||
var parent = event.relatedTarget;
|
||||
// Traverse up the tree
|
||||
while ( parent && parent != element ) try { parent = parent.parentNode } catch(error) { parent = element; };
|
||||
// Return true if we actually just moused on to a sub-element
|
||||
return parent == element;
|
||||
};
|
||||
|
||||
// Prevent memory leaks in IE
|
||||
// And prevent errors on refresh with events like mouseover
|
||||
// And prevent errors on refresh with events like mouseover in other browsers
|
||||
// Window isn't included so as not to unbind existing unload events
|
||||
jQuery(window).bind("unload", function() {
|
||||
jQuery("*").add(document).unbind();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module("event");
|
||||
|
||||
test("bind()", function() {
|
||||
expect(16);
|
||||
expect(18);
|
||||
|
||||
var handler = function(event) {
|
||||
ok( event.data, "bind() with data, check passed data exists" );
|
||||
|
@ -18,7 +18,20 @@ test("bind()", function() {
|
|||
ok( data, "Check trigger data" );
|
||||
ok( data.bar == "foo", "Check value of trigger data" );
|
||||
};
|
||||
$("#firstp").bind("click", {foo: "bar"}, handler).trigger("click", [{bar: "foo"}]).unbind(handler);
|
||||
$("#firstp").bind("click", {foo: "bar"}, handler).trigger("click", [{bar: "foo"}]).unbind("click", handler);
|
||||
|
||||
reset();
|
||||
var clickCounter = mouseoverCounter = 0;
|
||||
var handler = function(event) {
|
||||
if (event.type == "click")
|
||||
clickCounter += 1;
|
||||
else if (event.type == "mouseover")
|
||||
mouseoverCounter += 1;
|
||||
};
|
||||
$("#firstp").bind("click mouseover", handler).trigger("click").trigger("mouseover");
|
||||
ok( clickCounter == 1, "bind() with multiple events at once" );
|
||||
ok( mouseoverCounter == 1, "bind() with multiple events at once" );
|
||||
|
||||
|
||||
reset();
|
||||
var handler = function(event) {
|
||||
|
@ -96,7 +109,7 @@ test("click()", function() {
|
|||
});
|
||||
|
||||
test("unbind(event)", function() {
|
||||
expect(6);
|
||||
expect(8);
|
||||
var el = $("#firstp");
|
||||
el.click(function() {
|
||||
ok( true, "Fake normal bind" );
|
||||
|
@ -118,6 +131,18 @@ test("unbind(event)", function() {
|
|||
|
||||
el.unbind('click');
|
||||
ok( !jQuery.data(el[0], "events"), "Removed the events expando after all handlers are unbound." );
|
||||
|
||||
reset();
|
||||
var clickCounter = mouseoverCounter = 0;
|
||||
var handler = function(event) {
|
||||
if (event.type == "click")
|
||||
clickCounter += 1;
|
||||
else if (event.type == "mouseover")
|
||||
mouseoverCounter += 1;
|
||||
};
|
||||
$("#firstp").bind("click mouseover", handler).unbind("click mouseover", handler).trigger("click").trigger("mouseover");
|
||||
ok( clickCounter == 0, "unbind() with multiple events at once" );
|
||||
ok( mouseoverCounter == 0, "unbind() with multiple events at once" );
|
||||
});
|
||||
|
||||
test("trigger(event, [data], [fn])", function() {
|
||||
|
|
Loading…
Reference in a new issue