X-Git-Url: http://git.asbjorn.it/?a=blobdiff_plain;f=src%2Fevent.js;h=26f23409c65dede33f4867263ff961fc0deedb43;hb=813886a4dff0cf4be62fea51c3ae8be9ef88e12a;hp=cac56bd6f75bc432d1f2a5441c5ecff05892269f;hpb=b763cc6602fdf2bede16be5bf106ceaa1d1d0525;p=jquery.git diff --git a/src/event.js b/src/event.js index cac56bd..26f2340 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 @@ -124,7 +126,7 @@ jQuery.event = { types = types.type; } - // Handle multiple events seperated by a space + // Handle multiple events separated by a space // jQuery(...).unbind("mouseover mouseout", fn); types = types.split(/\s+/); var i = 0; @@ -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,18 +251,26 @@ jQuery.event = { handle.apply( elem, data ); } - // 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 ) { - event.result = false; - } + 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) {} + + var isClick = jQuery.nodeName(elem, "a") && type === "click"; // 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() && !isClick ) { this.triggered = true; try { - elem[ type ](); + nativeFn(); // prevent IE from throwing an error for some hidden elements } catch (e) {} + + // Handle triggering native .onfoo handlers + } else if ( nativeHandler && nativeHandler.apply( elem, data ) === false ) { + event.result = false; } this.triggered = false; @@ -285,7 +297,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 ]; @@ -315,6 +327,8 @@ jQuery.event = { } } + + return event.result; }, props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), @@ -368,7 +382,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 +410,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,6 +432,22 @@ jQuery.event = { jQuery.event.remove( this, namespaces[0], liveHandler ); } } + }, + special: {} + }, + beforeunload: { + setup: function( data, namespaces, fn ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = fn; + } + + return false; + }, + teardown: function( namespaces, fn ) { + if ( this.onbeforeunload === fn ) { + this.onbeforeunload = null; + } } } } @@ -461,6 +493,7 @@ jQuery.Event.prototype = { if ( !e ) { return; } + // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); @@ -510,28 +543,171 @@ 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' + mouseenter: "mouseover", + mouseleave: "mouseout" }, function( orig, fix ) { - jQuery.event.special[ fix ] = { - setup: function(){ - jQuery.event.add( this, orig, withinElement, fix ); + jQuery.event.special[ orig ] = { + setup: function(data){ + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); }, - teardown: function(){ - jQuery.event.remove( this, orig, withinElement ); + teardown: function(data){ + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); } }; }); +// submit delegation +if ( !jQuery.support.submitBubbles ) { + +jQuery.event.special.submit = { + setup: function( data, namespaces, fn ) { + if ( 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 ); + } + }); + } + }, + + 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. +if ( !jQuery.support.changeBubbles ) { + +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 ); + } + }, + 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 ) { + 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 ) { + 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 +if ( !jQuery.support.focusBubbles ) { + +jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){ + event.special[ orig ] = { + setup: function() { + jQuery.event.add( this, fix, ieHandler ); + }, + teardown: function() { + jQuery.event.remove( this, fix, ieHandler ); + } + }; + + function ieHandler() { + arguments[0].type = orig; + return jQuery.event.handle.apply(this, arguments); + } +}); + +} + 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 +718,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 +742,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 +782,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 +836,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]; } - }); + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + 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]; + } - elems.sort(function( a, b ) { - return jQuery.data( a.elem, "closest" ) - jQuery.data( b.elem, "closest" ); - }); + if ( !related || related !== elem ) { + elems.push({ elem: elem, fn: fn }); + } + } + } + } - 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 = 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 +902,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 +930,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 +952,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 +1003,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 +1015,4 @@ jQuery( window ).bind( 'unload', function() { } } }); +@*/