X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=src%2Fevent.js;h=2bb8a0edc8f608ca2440722fa7fdcb9cd8b13919;hb=ddb86f8d5bd1bd21b2beeeea55baf505b47dfed5;hp=7a28080aaf06cd03422f754fb3d173842a5cd60a;hpb=287ecdbf6759a3a89c168a5c0aff14833947f430;p=jquery.git diff --git a/src/event.js b/src/event.js index 7a28080..2bb8a0e 100644 --- a/src/event.js +++ b/src/event.js @@ -57,13 +57,18 @@ jQuery.event = { // Namespaced event handlers var namespaces = type.split("."); type = namespaces.shift(); - handler.type = namespaces.slice().sort().join("."); + handler.type = namespaces.slice(0).sort().join("."); // Get the current list of functions bound to this event - var handlers = events[ type ]; - - if ( this.specialAll[ type ] ) { - this.specialAll[ type ].setup.call( elem, data, namespaces ); + var handlers = events[ type ], + special = this.special[ type ] || {}; + + if ( special.add ) { + var modifiedHandler = special.add.call( elem, handler, data, namespaces ); + if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) { + modifiedHandler.guid = modifiedHandler.guid || handler.guid; + handler = modifiedHandler; + } } // Init the event handler queue @@ -73,7 +78,7 @@ jQuery.event = { // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false - if ( !this.special[ type ] || this.special[ type ].setup.call( elem, data, namespaces ) === false ) { + if ( !special.setup || special.setup.call( elem, data, namespaces ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, handle, false ); @@ -128,9 +133,10 @@ jQuery.event = { var namespaces = type.split("."); type = namespaces.shift(); var all = !namespaces.length, - namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"), + special = this.special[ type ] || {}; - if ( events[type] ) { + if ( events[ type ] ) { // remove the given handler for the given type if ( handler ) { delete events[ type ][ handler.guid ]; @@ -145,8 +151,8 @@ jQuery.event = { } } - if ( this.specialAll[ type ] ) { - this.specialAll[ type ].teardown.call( elem, namespaces ); + if ( special.remove ) { + special.remove.call( elem, namespaces ); } // remove generic event handler if no more handlers exist @@ -154,7 +160,7 @@ jQuery.event = { break; } if ( !ret ) { - if ( !this.special[ type ] || this.special[ type ].teardown.call( elem, namespaces ) === false ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, jQuery.data( elem, "handle" ), false ); } else if ( elem.detachEvent ) { @@ -184,9 +190,10 @@ jQuery.event = { }, // bubbling is internal - trigger: function( event, data, elem, bubbling ) { + trigger: function( event, data, elem /*, bubbling */ ) { // Event object or event type - var type = event.type || event; + var type = event.type || event, + bubbling = arguments[3]; if ( !bubbling ) { event = typeof event === "object" ? @@ -208,11 +215,11 @@ jQuery.event = { event.stopPropagation(); // Only trigger if we've ever bound an event for it if ( this.global[ type ] ) { - for ( var cached in jQuery.cache ) { - if ( cached.events && cached.events[ type ] ) { - this.trigger( event, data, cached.handle.elem ); + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); } - } + }); } } @@ -240,16 +247,22 @@ jQuery.event = { handle.apply( elem, data ); } + var nativeFn, nativeHandler; + try { + nativeFn = elem[ type ]; + nativeHandler = elem[ "on" + type ]; + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links) - if ( (!elem[ type ] || (jQuery.nodeName(elem, 'a') && type === "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) { + if ( (!nativeFn || (jQuery.nodeName(elem, 'a') && type === "click")) && nativeHandler && nativeHandler.apply( elem, data ) === false ) { event.result = false; } // Trigger the native events (except for clicks on links) - if ( !bubbling && elem[ type ] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type === "click") ) { + if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type === "click") ) { this.triggered = true; try { - elem[ type ](); + nativeFn(); // prevent IE from throwing an error for some hidden elements } catch (e) {} } @@ -278,7 +291,7 @@ jQuery.event = { // Cache this now, all = true means, any handler all = !namespaces.length && !event.exclusive; - var namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); + var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); handlers = ( jQuery.data(this, "events") || {} )[ event.type ]; @@ -345,8 +358,8 @@ jQuery.event = { // Calculate pageX/Y if missing and clientX/Y available 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.clientTop || 0); + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } // Add which for key events @@ -361,15 +374,20 @@ jQuery.event = { // Add which for click: 1 == left; 2 == middle; 3 == right // Note: button is not normalized, so don't use it - if ( !event.which && event.button ) { + if ( !event.which && event.button !== undefined ) { event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); } return event; }, - proxy: function( fn, proxy ) { - proxy = proxy || function() { return fn.apply( this, arguments ); }; + proxy: function( fn, proxy, thisObject ) { + if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + // FIXME: Should proxy be redefined to be applied with thisObject if defined? + proxy = proxy || function() { return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++; // So proxy can be declared as an argument @@ -381,15 +399,16 @@ jQuery.event = { // Make sure the ready event is setup setup: bindReady, teardown: function() {} - } - }, + }, - specialAll: { live: { - setup: function( selector, namespaces ) { - jQuery.event.add( this, namespaces[0], liveHandler ); + add: function( proxy, data, namespaces ) { + jQuery.extend( proxy, data || {} ); + proxy.guid += data.selector + data.live; + jQuery.event.add( this, data.live, liveHandler, data ); }, - teardown: function( namespaces ) { + + remove: function( namespaces ) { if ( namespaces.length ) { var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)"); @@ -448,6 +467,7 @@ jQuery.Event.prototype = { if ( !e ) { return; } + // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); @@ -484,8 +504,11 @@ var withinElement = function( event ) { var parent = event.relatedTarget; // Traverse up the tree while ( parent && parent != this ) { + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of try { parent = parent.parentNode; } - catch(e) { parent = this; } + // assuming we've left the element since we most likely mousedover a xul element + catch(e) { break; } } if ( parent != this ) { @@ -494,40 +517,152 @@ var withinElement = function( event ) { // handle event if we actually just moused on to a non sub-element jQuery.event.handle.apply( this, arguments ); } + +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); }; +// Create mouseenter and mouseleave events jQuery.each({ - mouseover: 'mouseenter', - mouseout: 'mouseleave' + mouseover: "mouseenter", + mouseout: "mouseleave" }, function( orig, fix ) { jQuery.event.special[ fix ] = { - setup: function(){ - jQuery.event.add( this, orig, withinElement, fix ); + setup: function(data){ + jQuery.event.add( this, orig, data && data.selector ? delegate : withinElement, fix ); + }, + teardown: function(data){ + jQuery.event.remove( this, orig, data && data.selector ? delegate : withinElement ); + } + }; +}); + +(function() { + + var event = jQuery.event, + special = event.special, + handle = event.handle; + + special.submit = { + setup: function(data, namespaces) { + if(data.selector) { + event.add(this, 'click.specialSubmit', function(e, eventData) { + if(jQuery(e.target).filter(":submit, :image").closest(data.selector).length) { + e.type = "submit"; + return handle.call( this, e, eventData ); + } + }); + + event.add(this, 'keypress.specialSubmit', function( e, eventData ) { + if(jQuery(e.target).filter(":text, :password").closest(data.selector).length) { + e.type = "submit"; + return handle.call( this, e, eventData ); + } + }); + } else { + return false; + } }, - teardown: function(){ - jQuery.event.remove( this, orig, withinElement ); + + remove: function(namespaces) { + event.remove(this, 'click.specialSubmit'); + event.remove(this, 'keypress.specialSubmit'); + } + }; + +})(); + +// Create "bubbling" focus and blur events +jQuery.each({ + focus: "focusin", + blur: "focusout" +}, function( orig, fix ){ + var event = jQuery.event, + handle = event.handle; + + function ieHandler() { + arguments[0].type = orig; + return handle.apply(this, arguments); + } + + event.special[orig] = { + setup:function() { + if ( this.addEventListener ) { + this.addEventListener( orig, handle, true ); + } else { + event.add( this, fix, ieHandler ); + } + }, + teardown:function() { + if ( this.removeEventListener ) { + this.removeEventListener( orig, handle, true ); + } else { + event.remove( this, fix, ieHandler ); + } } }; }); jQuery.fn.extend({ - bind: function( type, data, fn ) { - return type === "unload" ? this.one(type, data, fn) : this.each(function() { - jQuery.event.add( this, type, fn || data, fn && data ); + // TODO: make bind(), unbind() and one() DRY! + bind: function( type, data, fn, thisObject ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this.bind(key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + thisObject = fn; + fn = data; + data = undefined; + } + fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject ); + return type === "unload" ? this.one(type, data, fn, thisObject) : this.each(function() { + jQuery.event.add( this, type, fn, data ); }); }, - one: function( type, data, fn ) { - var one = jQuery.event.proxy( fn || data, function( event ) { + one: function( type, data, fn, thisObject ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this.one(key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + thisObject = fn; + fn = data; + data = undefined; + } + fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject ); + var one = jQuery.event.proxy( fn, function( event ) { jQuery( this ).unbind( event, one ); - return (fn || data).apply( this, arguments ); + return fn.apply( this, arguments ); }); return this.each(function() { - jQuery.event.add( this, type, one, fn && data ); + jQuery.event.add( this, type, one, data ); }); }, unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + return this; + } + return this.each(function() { jQuery.event.remove( this, type, fn ); }); @@ -560,18 +695,19 @@ jQuery.fn.extend({ return this.click( jQuery.event.proxy( fn, function( event ) { // Figure out which function to execute - this.lastToggle = ( this.lastToggle || 0 ) % i; + var lastToggle = ( jQuery.data( this, 'lastToggle' + fn.guid ) || 0 ) % i; + jQuery.data( this, 'lastToggle' + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); // and execute the function - return args[ this.lastToggle++ ].apply( this, arguments ) || false; + return args[ lastToggle ].apply( this, arguments ) || false; })); }, hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut ); + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); }, ready: function( fn ) { @@ -592,41 +728,54 @@ jQuery.fn.extend({ return this; }, - live: function( type, fn ) { - var proxy = jQuery.event.proxy( fn ); - proxy.guid += this.selector + type; - - jQuery( this.context ).bind( liveConvert( type, this.selector ), this.selector, proxy ); - + live: function( type, data, fn, thisObject ) { + if ( jQuery.isFunction( data ) ) { + if ( fn !== undefined ) { + thisObject = fn; + } + fn = data; + data = undefined; + } + jQuery( this.context ).bind( liveConvert( type, this.selector ), { + data: data, selector: this.selector, live: type + }, fn, thisObject ); return this; }, die: function( type, fn ) { - jQuery( this.context ).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null ); + jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null ); return this; } }); function liveHandler( event ) { - var check = new RegExp("(^|\\.)" + event.type + "(\\.|$)"), - stop = true, elems = []; + var stop = true, elems = [], args = arguments; jQuery.each( jQuery.data( this, "events" ).live || [], function( i, fn ) { - if ( check.test( fn.type ) ) { - var elem = jQuery( event.target ).closest( fn.data )[0]; + if ( fn.live === event.type ) { + var elem = jQuery( event.target ).closest( fn.selector, event.currentTarget )[0], + related; if ( elem ) { - elems.push({ elem: elem, fn: fn }); + // Those two events require additional checking + if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( fn.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, fn: fn }); + } } } }); elems.sort(function( a, b ) { - return jQuery.data( a.elem, "closest" ) - jQuery.data( b.elem, "closest" ); + return a.closer - b.closer; }); jQuery.each(elems, function() { event.currentTarget = this.elem; - if ( this.fn.call( this.elem, event, this.fn.data ) === false ) { + event.data = this.fn.data; + if ( this.fn.apply( this.elem, args ) === false ) { return (stop = false); } }); @@ -645,6 +794,10 @@ jQuery.extend({ ready: function() { // Make sure that the DOM is not already loaded if ( !jQuery.isReady ) { + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + // Remember that the DOM is ready jQuery.isReady = true; @@ -669,9 +822,15 @@ jQuery.extend({ var readyBound = false; function bindReady() { - if ( readyBound ) return; + if ( readyBound ) { return; } readyBound = true; + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback @@ -685,53 +844,61 @@ function bindReady() { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent("onreadystatechange", function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", arguments.callee ); jQuery.ready(); } }); - // If IE and not an iframe + // If IE and not a frame // continually check to see if the document is ready - if ( document.documentElement.doScroll && window === window.top ) (function() { - if ( jQuery.isReady ) { - return; - } + var toplevel = false; - 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; - } + try { + toplevel = window.frameElement == null; + } catch(e){} - // and execute any waiting functions - jQuery.ready(); - })(); + if ( document.documentElement.doScroll && toplevel ) { + (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(); + })(); + } } // 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,mouseenter,mouseleave," + - "change,select,submit,keydown,keypress,keyup,error").split(","), function( i, name ) { +jQuery.each( ("blur focus load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "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 ); + return fn ? this.bind( name, fn ) : this.trigger( name ); }; }); // Prevent memory leaks in IE -// And prevent errors on refresh with events like mouseover in other browsers // Window isn't included so as not to unbind existing unload events // More info: // - http://isaacschlueter.com/2006/10/msie-memory-leaks/ -// - https://bugzilla.mozilla.org/show_bug.cgi?id=252542 +/*@cc_on jQuery( window ).bind( 'unload', function() { for ( var id in jQuery.cache ) { // Skip the window @@ -740,3 +907,4 @@ jQuery( window ).bind( 'unload', function() { } } }); +@*/