X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=src%2Fevent.js;h=d0cacf6b582b174cab4540573e5566b14232aade;hb=5d033dba02fc1ffe1398cdaf0e9d402fc75e13f0;hp=9922e021914db3a7361a547fc33ad0c80ca94084;hpb=e6a481e533dc25ecf4d25123d6515c61a97a7f19;p=jquery.git diff --git a/src/event.js b/src/event.js index 9922e02..d0cacf6 100644 --- a/src/event.js +++ b/src/event.js @@ -7,11 +7,14 @@ jQuery.event = { // Bind an event to an element // Original by Dean Edwards - add: function(element, type, handler, data) { + add: function(elem, types, handler, data) { + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return; + // For whatever reason, IE has trouble passing the window object // around, causing it to be cloned in the process - if ( jQuery.browser.msie && element.setInterval != undefined ) - element = window; + if ( jQuery.browser.msie && elem.setInterval != undefined ) + elem = window; // Make sure that the function being executed has a unique ID if ( !handler.guid ) @@ -19,7 +22,7 @@ jQuery.event = { // if data is passed, bind to handler if( data != undefined ) { - // Create temporary function pointer to original handler + // Create temporary function pointer to original handler var fn = handler; // Create unique handler function, wrapped around original handler @@ -35,135 +38,173 @@ 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(elem, "events") || jQuery.data(elem, "events", {}), + handle = jQuery.data(elem, "handle") || jQuery.data(elem, "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(arguments.callee.elem, 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; + }); + // 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; - val = jQuery.event.handle.apply(element, arguments); - - return val; - }); - - // 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); - } + // 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]; + + // 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(elem) === false ) { + // Bind the global event handler to the element + if (elem.addEventListener) + elem.addEventListener(type, handle, false); + else if (elem.attachEvent) + elem.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; + }); + + // Nullify elem to prevent memory leaks in IE + elem = null; }, guid: 1, global: {}, // Detach an event or set of events from an element - remove: function(element, type, handler) { - var events = jQuery.data(element, "events"), ret, index; + remove: function(elem, types, handler) { + // don't do events on text and comment nodes + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return; - // Namespaced event handlers - if ( typeof type == "string" ) { - var parts = type.split("."); - type = parts[0]; - } + var events = jQuery.data(elem, "events"), ret, index; 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 ) - 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]; + // Unbind all events for the element + if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") ) + for ( var type in events ) + this.remove( elem, type + (types || "") ); + 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(elem) === false ) { + if (elem.removeEventListener) + elem.removeEventListener(type, jQuery.data(elem, "handle"), false); + else if (elem.detachEvent) + elem.detachEvent("on" + type, jQuery.data(elem, "handle")); + } + ret = null; + delete events[type]; + } + } + }); } // Remove the expando if it's no longer used for ( ret in events ) break; if ( !ret ) { - jQuery.removeData( element, "events" ); - jQuery.removeData( element, "handle" ); + var handle = jQuery.data( elem, "handle" ); + if ( handle ) handle.elem = null; + jQuery.removeData( elem, "events" ); + jQuery.removeData( elem, "handle" ); } } }, - trigger: function(type, data, element, donative, extra) { + trigger: function(type, data, elem, donative, extra) { // Clone the incoming data, if any data = jQuery.makeArray(data || []); + if ( type.indexOf("!") >= 0 ) { + type = type.slice(0, -1); + var exclusive = true; + } + // Handle a global trigger - if ( !element ) { + if ( !elem ) { // Only trigger if we've ever bound an event for it if ( this.global[type] ) jQuery("*").add([window, document]).trigger(type, data); // Handle triggering a single element } else { - var val, ret, fn = jQuery.isFunction( element[ type ] || null ), + // don't do events on text and comment nodes + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return undefined; + + var val, ret, fn = jQuery.isFunction( elem[ type ] || null ), // Check to see if we need to provide a fake event, or not event = !data[0] || !data[0].preventDefault; // Pass along a fake event if ( event ) - data.unshift( this.fix({ type: type, target: element }) ); + data.unshift( this.fix({ type: type, target: elem }) ); // Enforce the right trigger type data[0].type = type; + if ( exclusive ) + data[0].exclusive = true; // Trigger the event - if ( jQuery.isFunction( jQuery.data(element, "handle") ) ) - val = jQuery.data(element, "handle").apply( element, data ); + if ( jQuery.isFunction( jQuery.data(elem, "handle") ) ) + val = jQuery.data(elem, "handle").apply( elem, data ); - // Handle triggering native .onfoo handlers - if ( !fn && element["on"+type] && element["on"+type].apply( element, data ) === false ) + // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links) + if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) val = false; // Extra functions don't get the custom event object @@ -171,13 +212,21 @@ jQuery.event = { data.shift(); // Handle triggering of extra function - if ( extra && extra.apply( element, data ) === false ) - val = false; + if ( extra && jQuery.isFunction( extra ) ) { + // call the extra function and tack the current return value on the end for possible inspection + ret = extra.apply( elem, val == null ? data : data.concat( val ) ); + // if anything is returned, give it precedence and have it overwrite the previous value + if (ret !== undefined) + val = ret; + } // Trigger the native events (except for clicks on links) - if ( fn && donative !== false && val !== false && !(jQuery.nodeName(element, 'a') && type == "click") ) { + if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) { this.triggered = true; - element[ type ](); + try { + elem[ type ](); + // prevent IE from throwing an error for some hidden elements + } catch (e) {} } this.triggered = false; @@ -208,7 +257,7 @@ jQuery.event = { args[0].data = handler.data; // Filter the functions by class - if ( !parts[1] || handler.type == parts[1] ) { + if ( !parts[1] && !event.exclusive || handler.type == parts[1] ) { var ret = handler.apply( this, args ); if ( val !== false ) @@ -268,11 +317,11 @@ jQuery.event = { if ( event.pageX == null && event.clientX != null ) { var doc = document.documentElement, body = document.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0); } // Add which for key events - if ( !event.which && (event.charCode || event.keyCode) ) + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) event.which = event.charCode || event.keyCode; // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) @@ -285,6 +334,62 @@ jQuery.event = { event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); return event; + }, + + special: { + ready: { + setup: function() { + // Make sure the ready event is setup + bindReady(); + return; + }, + + teardown: function() { return; } + }, + + mouseenter: { + setup: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler); + return true; + }, + + teardown: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler); + return true; + }, + + handler: function(event) { + // If we actually just moused on to a sub-element, ignore it + if ( withinElement(event, this) ) return true; + // Execute the right handlers by setting the event type to mouseenter + arguments[0].type = "mouseenter"; + return jQuery.event.handle.apply(this, arguments); + } + }, + + mouseleave: { + setup: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler); + return true; + }, + + teardown: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler); + return true; + }, + + handler: function(event) { + // If we actually just moused on to a sub-element, ignore it + if ( withinElement(event, this) ) return true; + // Execute the right handlers by setting the event type to mouseleave + arguments[0].type = "mouseleave"; + return jQuery.event.handle.apply(this, arguments); + } + } } }; @@ -319,6 +424,7 @@ jQuery.fn.extend({ triggerHandler: function( type, data, fn ) { if ( this[0] ) return jQuery.event.trigger( type, data, this[0], false, fn ); + return undefined; }, toggle: function() { @@ -333,29 +439,12 @@ jQuery.fn.extend({ event.preventDefault(); // and execute the function - return args[this.lastToggle].apply( this, [event] ) || false; + return args[this.lastToggle].apply( this, arguments ) || false; }); }, 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 false; - - // 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); + return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut); }, ready: function(fn) { @@ -365,24 +454,20 @@ jQuery.fn.extend({ // If the DOM is already ready if ( jQuery.isReady ) // Execute the function immediately - fn.apply( document, [jQuery] ); + fn.call( 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]); } ); + jQuery.readyList.push( function() { return fn.call(this, jQuery); } ); return this; } }); 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 @@ -400,57 +485,98 @@ jQuery.extend({ // Reset the list of functions jQuery.readyList = null; } - // Remove event listener to avoid memory leak - if ( document.removeEventListener ) - document.removeEventListener( "DOMContentLoaded", jQuery.ready, false ); + + // Trigger any bound ready events + jQuery(document).triggerHandler("ready"); } } }); - -jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + - "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + - "submit,keydown,keypress,keyup,error").split(","), function(i, name){ - - // Handle event binding - jQuery.fn[name] = function(fn){ - return fn ? this.bind(name, fn) : this.trigger(name); - }; -}); - var readyBound = false; function bindReady(){ if ( readyBound ) return; readyBound = true; - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) + // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event + if ( document.addEventListener && !jQuery.browser.opera) // Use the handy event callback document.addEventListener( "DOMContentLoaded", jQuery.ready, false ); - // If Safari or IE is used + // If IE is used and is not in a frame // Continually check to see if the document is ready - if (jQuery.browser.msie || jQuery.browser.safari ) (function(){ + if ( jQuery.browser.msie && window == top ) (function(){ + if (jQuery.isReady) return; 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"); + document.documentElement.doScroll("left"); } catch( error ) { - return setTimeout( arguments.callee, 0 ); + setTimeout( arguments.callee, 0 ); + return; } - // and execute any waiting functions jQuery.ready(); })(); + if ( jQuery.browser.opera ) + document.addEventListener( "DOMContentLoaded", function () { + if (jQuery.isReady) return; + for (var i = 0; i < document.styleSheets.length; i++) + if (document.styleSheets[i].disabled) { + setTimeout( arguments.callee, 0 ); + return; + } + // and execute any waiting functions + jQuery.ready(); + }, false); + + if ( jQuery.browser.safari ) { + var numStyles; + (function(){ + if (jQuery.isReady) return; + if ( document.readyState != "loaded" && document.readyState != "complete" ) { + setTimeout( arguments.callee, 0 ); + return; + } + if ( numStyles === undefined ) + numStyles = jQuery("style, link[rel=stylesheet]").length; + if ( document.styleSheets.length != numStyles ) { + setTimeout( arguments.callee, 0 ); + return; + } + // and execute any waiting functions + jQuery.ready(); + })(); + } + // A fallback to window.onload, that will always work jQuery.event.add( window, "load", jQuery.ready ); } +jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + + "submit,keydown,keypress,keyup,error").split(","), function(i, name){ + + // Handle event binding + jQuery.fn[name] = function(fn){ + return fn ? this.bind(name, fn) : this.trigger(name); + }; +}); + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function(event, elem) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + // Traverse up the tree + while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; } + // Return true if we actually just moused on to a sub-element + return parent == elem; +}; + // 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();