X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=src%2Fevent.js;h=acf363b679047c5277bc0925858cfa2eaa1891ff;hb=d42afd0f657d12d6daba6894d40226bea83fe1b6;hp=cac56bd6f75bc432d1f2a5441c5ecff05892269f;hpb=b763cc6602fdf2bede16be5bf106ceaa1d1d0525;p=jquery.git diff --git a/src/event.js b/src/event.js index cac56bd..acf363b 100644 --- a/src/event.js +++ b/src/event.js @@ -57,19 +57,13 @@ 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 ], 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 if ( !handlers ) { @@ -78,7 +72,7 @@ jQuery.event = { // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces ) === false ) { + if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, handle, false ); @@ -87,7 +81,15 @@ jQuery.event = { } } } - + + if ( special.add ) { + var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers ); + if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) { + modifiedHandler.guid = modifiedHandler.guid || handler.guid; + handler = modifiedHandler; + } + } + // Add the function to the element's handler list handlers[ handler.guid ] = handler; @@ -109,7 +111,7 @@ jQuery.event = { return; } - var events = jQuery.data( elem, "events" ), ret, type; + var events = jQuery.data( elem, "events" ), ret, type, fn; if ( events ) { // Unbind all events for the element @@ -133,12 +135,14 @@ jQuery.event = { var namespaces = type.split("."); type = namespaces.shift(); var all = !namespaces.length, - namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"), + cleaned = jQuery.map( namespaces.slice(0).sort() , function(nm){ return nm.replace(/[^\w\s\.\|`]/g, function(ch){return "\\"+ch }) }), + namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"), special = this.special[ type ] || {}; if ( events[ type ] ) { // remove the given handler for the given type if ( handler ) { + fn = events[ type ][ handler.guid ]; delete events[ type ][ handler.guid ]; // remove all handlers for the given type @@ -152,7 +156,7 @@ jQuery.event = { } if ( special.remove ) { - special.remove.call( elem, namespaces ); + special.remove.call( elem, namespaces, fn); } // remove generic event handler if no more handlers exist @@ -247,16 +251,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) {} } @@ -285,7 +295,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 ]; @@ -368,7 +378,7 @@ 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 ) )); } @@ -396,10 +406,12 @@ jQuery.event = { }, live: { - add: function( proxy, data, namespaces ) { + add: function( proxy, data, namespaces, live ) { jQuery.extend( proxy, data || {} ); - proxy.guid += data.selector + data.live; - jQuery.event.add( this, data.live, liveHandler ); + + proxy.guid += data.selector + data.live; + jQuery.event.add( this, data.live, liveHandler, data ); + }, remove: function( namespaces ) { @@ -416,7 +428,8 @@ jQuery.event = { jQuery.event.remove( this, namespaces[0], liveHandler ); } } - } + }, + special: {} } } }; @@ -461,6 +474,7 @@ jQuery.Event.prototype = { if ( !e ) { return; } + // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); @@ -510,28 +524,180 @@ 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 ); + } + }; +}); + +// submit delegation +jQuery.event.special.submit = { + setup: function( data, namespaces, fn ) { + if ( !jQuery.support.submitBubbles && this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + } + + return false; + }, + + remove: function( namespaces, fn ) { + jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") ); + jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") ); + } +}; + +// change delegation, happens here so we have bind. +jQuery.event.special.change = { + filters: { + click: function( e ) { + var elem = e.target; + + if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) { + return trigger( "change", this, arguments ); + } + + return changeFilters.keyup.call( this, e ); + }, + keyup: function( e ) { + var elem = e.target, data, index = elem.selectedIndex + ""; + + if ( elem.nodeName.toLowerCase() === "select" ) { + data = jQuery.data( elem, "_change_data" ); + jQuery.data( elem, "_change_data", index ); + + if ( (elem.type === "select-multiple" || data != null) && data !== index ) { + return trigger( "change", this, arguments ); + } + } + }, + beforeactivate: function( e ) { + var elem = e.target; + + if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) { + return trigger( "change", this, arguments ); + } }, - teardown: function(){ - jQuery.event.remove( this, orig, withinElement ); + blur: function( e ) { + var elem = e.target, nodeName = elem.nodeName.toLowerCase(); + + if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password"))) + && jQuery.data(elem, "_change_data") !== elem.value ) { + + return trigger( "change", this, arguments ); + } + }, + focus: function( e ) { + var elem = e.target, nodeName = elem.nodeName.toLowerCase(); + + if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) { + jQuery.data( elem, "_change_data", elem.value ); + } + } + }, + setup: function( data, namespaces, fn ) { + // return false if we bubble + if ( !jQuery.support.changeBubbles ) { + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] ); + } + } + + // always want to listen for change for trigger + return false; + }, + remove: function( namespaces, fn ) { + if ( !jQuery.support.changeBubbles ) { + for ( var type in changeFilters ) { + jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] ); + } + } + } +}; + +var changeFilters = jQuery.event.special.change.filters; + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// 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({ + // TODO: make bind(), unbind() and one() DRY! bind: function( type, data, fn, thisObject ) { - if ( jQuery.isFunction( data ) ) { - if ( fn !== undefined ) { - thisObject = fn; + // 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; } @@ -542,10 +708,16 @@ jQuery.fn.extend({ }, one: function( type, data, fn, thisObject ) { - if ( jQuery.isFunction( data ) ) { - if ( fn !== undefined ) { - thisObject = fn; + // 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; } @@ -560,6 +732,14 @@ jQuery.fn.extend({ }, 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 ); }); @@ -592,13 +772,14 @@ 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; })); }, @@ -645,34 +826,63 @@ jQuery.fn.extend({ }); function liveHandler( event ) { - var stop = true, elems = [], args = arguments; - - jQuery.each( jQuery.data( this, "events" ).live || [], function( i, fn ) { - if ( fn.live === event.type ) { - var elem = jQuery( event.target ).closest( fn.selector )[0]; - if ( elem ) { - elems.push({ elem: elem, fn: fn }); + var stop = true, elems = [], selectors = [], args = arguments, + related, match, fn, elem, j, i, data, + live = jQuery.extend({}, jQuery.data( this, "events" ).live); + + for ( j in live ) { + fn = live[j]; + if ( fn.live === event.type || + fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) { + + data = fn.data; + if ( !(data.beforeFilter && data.beforeFilter[event.type] && + !data.beforeFilter[event.type](event)) ) { + selectors.push( fn.selector ); } + } else { + delete live[j]; } - }); + } - elems.sort(function( a, b ) { - return jQuery.data( a.elem, "closest" ) - jQuery.data( b.elem, "closest" ); - }); + match = jQuery( event.target ).closest( selectors, event.currentTarget ); - jQuery.each(elems, function() { - event.currentTarget = this.elem; - event.data = this.fn.data; - if ( this.fn.apply( this.elem, args ) === false ) { - return (stop = false); + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j in live ) { + fn = live[j]; + elem = match[i].elem; + related = null; + + if ( match[i].selector === fn.selector ) { + // 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 }); + } + } } - }); + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.fn.data; + if ( match.fn.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } return stop; } function liveConvert( type, selector ) { - return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join("."); + return ["live", type, selector//.replace(/[^\w\s\.]/g, function(ch){ return "\\"+ch}) + .replace(/\./g, "`") + .replace(/ /g, "|")].join("."); } jQuery.extend({ @@ -682,6 +892,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; @@ -706,9 +920,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 @@ -722,40 +942,49 @@ 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 ) { @@ -764,11 +993,10 @@ jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + }); // 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 @@ -777,3 +1005,4 @@ jQuery( window ).bind( 'unload', function() { } } }); +@*/