X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=src%2Fevent.js;h=3e0a885d86125cdf905e2ce50295aba2a295a2c7;hb=709df933049ccce53d0b6bb510ad9c39fd6304fe;hp=904c671d0d746e1de772c159874f22491af1d96a;hpb=feb9051c0e29937c2494c3db0862a671efc21747;p=jquery.git diff --git a/src/event.js b/src/event.js index 904c671..3e0a885 100644 --- a/src/event.js +++ b/src/event.js @@ -7,14 +7,14 @@ jQuery.event = { // Bind an event to an element // Original by Dean Edwards - add: function(element, types, handler, data) { - if ( element.nodeType == 3 || element.nodeType == 8 ) + 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 ) @@ -39,8 +39,8 @@ jQuery.event = { } // Init the element's event structure - var events = jQuery.data(element, "events") || jQuery.data(element, "events", {}), - handle = jQuery.data(element, "handle") || jQuery.data(element, "handle", function(){ + 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; @@ -49,10 +49,14 @@ jQuery.event = { if ( typeof jQuery == "undefined" || jQuery.event.triggered ) return val; - val = jQuery.event.handle.apply(element, arguments); + val = jQuery.event.handle.apply(arguments.callee.elem, arguments); 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; // Handle multiple events seperated by a space // jQuery(...).bind("mouseover mouseout", fn); @@ -72,12 +76,12 @@ jQuery.event = { // 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 ) { + if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === 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); + if (elem.addEventListener) + elem.addEventListener(type, handle, false); + else if (elem.attachEvent) + elem.attachEvent("on" + type, handle); } } @@ -87,24 +91,27 @@ jQuery.event = { // 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, types, handler) { + remove: function(elem, types, handler) { // don't do events on text and comment nodes - if ( element.nodeType == 3 || element.nodeType == 8 ) + if ( elem.nodeType == 3 || elem.nodeType == 8 ) return; - var events = jQuery.data(element, "events"), ret, index; + var events = jQuery.data(elem, "events"), ret, index; if ( events ) { // Unbind all events for the element - if ( !types ) + if ( types == undefined ) for ( var type in events ) - this.remove( element, type ); + this.remove( elem, type ); else { // types is actually an event object here if ( types.type ) { @@ -134,11 +141,11 @@ jQuery.event = { // 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")); + 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]; @@ -150,18 +157,20 @@ jQuery.event = { // 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 || []); // 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); @@ -169,26 +178,26 @@ jQuery.event = { // Handle triggering a single element } else { // don't do events on text and comment nodes - if ( element.nodeType == 3 || element.nodeType == 8 ) + if ( elem.nodeType == 3 || elem.nodeType == 8 ) return undefined; - var val, ret, fn = jQuery.isFunction( element[ type ] || null ), + 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; // 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 ) + if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) val = false; // Extra functions don't get the custom event object @@ -196,19 +205,19 @@ jQuery.event = { data.shift(); // Handle triggering of extra function - if ( extra ) { + if ( extra && jQuery.isFunction( extra ) ) { // call the extra function and tack the current return value on the end for possible inspection - var ret = extra.apply( element, data.concat( val ) ); + 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; try { - element[ type ](); + elem[ type ](); // prevent IE from throwing an error for some hidden elements } catch (e) {} } @@ -305,7 +314,7 @@ jQuery.event = { } // 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) @@ -323,96 +332,55 @@ jQuery.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 ) { - setTimeout( arguments.callee, 0 ); - return; - } - - // and execute any waiting functions - handler(); - })(); - - // A fallback to window.onload, that will always work - jQuery.event.add( window, "load", handler ); + // Make sure the ready event is setup + bindReady(); + return; }, - 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"); - } - } + teardown: function() { return; } }, mouseenter: { setup: function() { - if (jQuery.browser.msie) return false; - jQuery(this).bind('mouseover', jQuery.event.special.mouseenter.handler); + 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); + if ( jQuery.browser.msie ) return false; + jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler); return true; }, 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 true; // 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; + 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); + 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); + if ( jQuery.browser.msie ) return false; + jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler); return true; }, 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 true; // 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; + arguments[0].type = "mouseleave"; + return jQuery.event.handle.apply(this, arguments); } } } @@ -470,14 +438,116 @@ jQuery.fn.extend({ hover: function(fnOver, fnOut) { return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut); + }, + + ready: function(fn) { + // Attach the listeners + bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + else + // Add the function to the wait list + jQuery.readyList.push( function() { return fn.call(this, jQuery); } ); + + return this; } }); jQuery.extend({ - isReady: false + 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; + } + + // Trigger any bound ready events + jQuery(document).triggerHandler("ready"); + } + } }); -jQuery.each( ("blur,focus,load,ready,resize,scroll,unload,click,dblclick," + +var readyBound = false; + +function bindReady(){ + if ( readyBound ) return; + readyBound = true; + + // 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 IE is used and is not in a frame + // Continually check to see if the document is ready + 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/ + document.documentElement.doScroll("left"); + } catch( error ) { + 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){ @@ -489,13 +559,13 @@ jQuery.each( ("blur,focus,load,ready,resize,scroll,unload,click,dblclick," + // 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) { +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 != element ) try { parent = parent.parentNode } catch(error) { parent = element; }; + 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 == element; + return parent == elem; }; // Prevent memory leaks in IE