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:
Brandon Aaron 2007-12-15 05:55:33 +00:00
parent 01e8f33e44
commit b264f789b9
2 changed files with 219 additions and 171 deletions

View file

@ -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();

View file

@ -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() {