diff --git a/src/event.js b/src/event.js index 699b9381e..00b332c5b 100644 --- a/src/event.js +++ b/src/event.js @@ -7,6 +7,8 @@ var rnamespaces = /\.(.*)$/, rescape = /[^\w\s.|`]/g, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])?$/, quickParse = function( selector ) { var quick = rquickIs.exec( selector ); @@ -407,12 +409,6 @@ jQuery.event = { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); - var propHook = jQuery.event.propHooks[ event.type ]; - - if ( propHook ) { - event = propHook( event ); - } - var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []), delegateCount = handlers.delegateCount, args = Array.prototype.slice.call( arguments, 0 ), @@ -469,55 +465,89 @@ jQuery.event = { return event.result; }, - props: "altKey attrName bubbles button cancelable charCode ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view which".split(" "), + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp type view which".split(" "), propHooks: {}, + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement layerX layerY offsetX offsetY pageX pageY screenX screenY toElement wheelDelta".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = original.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && original.fromElement ) { + event.relatedTarget = original.fromElement === event.target ? original.toElement : original.fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = (button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) )); + } + + return event; + } + }, + fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } - // store a copy of the original event object - // and "clone" to set read-only properties + // Create a writable copy of the event object and normalize some properties var originalEvent = event, - propHook = jQuery.event.propHooks[ event.type ], - copy = this.props; + propHook = jQuery.event.propHooks[ event.type ] || {}, + copy = propHook.props? this.props.concat( propHook.props ) : this.props; event = jQuery.Event( originalEvent ); - if ( propHook ) { - copy.push.apply( copy, propHook() || [] ); - } - for ( var i = copy.length, prop; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } - // Fix target property, if necessary - // Removal will crash IE6,7,8 + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) if ( !event.target ) { - // Fixes #1925 where srcElement might not be defined either - event.target = event.srcElement || document; + event.target = originalEvent.srcElement || document; } - // check if target is a textnode (safari) - // Removal will crash IE6,7,8 + // Target should not be a text node (#504, Safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; } - if ( propHook ) { - event = propHook( event, originalEvent ); - } - - return event; + return propHook.filter? propHook.filter( event, originalEvent ) : event; }, // Deprecated, use jQuery.guid instead @@ -1026,58 +1056,14 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl jQuery.attrFn[ name ] = true; } - // Key Event property hooks if ( rkeyEvent.test( name ) ) { - jQuery.event.propHooks[ name ] = function( event, original ) { - - var charCode = event.charCode, - keyCode = event.keyCode, - ctrlKey = event.ctrlKey; - - // Add which for key events - if ( event.which == null && (charCode != null || keyCode != null) ) { - event.which = charCode != null ? charCode : keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && ctrlKey ) { - event.metaKey = ctrlKey; - } - - return event; - }; + jQuery.event.propHooks[ name ] = jQuery.event.keyHooks; } - // Mouse Event property hooks if ( rmouseEvent.test( name ) ) { - jQuery.event.propHooks[ name ] = function( event, original ) { - - if ( !event ) { - return mouseProps; - } - - var eventDoc, doc, body, - button = event.button; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - 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 click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = (button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) )); - } - - return event; - }; - }}); + jQuery.event.propHooks[ name ] = jQuery.event.mouseHooks; + } +}); })( jQuery ); diff --git a/test/unit/event.js b/test/unit/event.js index 103b7f00a..319569ab9 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2383,6 +2383,38 @@ test("delegated events quickIs", function() { markup.remove(); }); +test("propHooks extensions", function() { + expect( 3 ); + + jQuery( "" ).appendTo( "#qunit-fixture" ); + + var $fixture = jQuery( "#hook-fixture" ); + + // Ensure the property doesn't exist + $fixture.bind( "focus", function( event ) { + ok( !("blurrinessLevel" in event), "event.blurrinessLevel does not exist" ); + })[0].focus(); + + // Must blur the link so focus works below + $fixture.unbind( "focus" )[0].blur(); + + // Define a custom property for "focus" events via the filter function + ok( !jQuery.event.propHooks.focus, "We aren't clobbering an existing focus hook" ); + jQuery.event.propHooks.focus = { + filter: function( event, originalEvent ) { + event.blurrinessLevel = 42; + return event; + } + }; + + // Trigger a native focus and ensure the property is set + $fixture.bind( "focus", function( event ) { + equals( event.blurrinessLevel, 42, "event.blurrinessLevel was set" ); + })[0].focus(); + + delete jQuery.event.propHooks.focus; + $fixture.unbind( "focus" ).remove(); +}); (function(){ // This code must be run before DOM ready! @@ -2457,72 +2489,4 @@ test("delegated events quickIs", function() { })(); -test("jQuery.event.propHooks", function() { - expect( 1 ); - ok( jQuery.event.propHooks, "jQuery.event.propHooks exists" ); -}); - -test("jQuery.event.propHooks as function", function() { - - expect( 2 ); - - jQuery( "
" ).appendTo( "#qunit-fixture" ); - - var $fixture = jQuery( "#hook-fixture" ); - - // Does not exist - $fixture.bind( "click", function( event ) { - ok( !("propC" in event), "event.propC Does not exist" ); - }).trigger( "click" ); - - $fixture.unbind( "click" ); - - // Store as function - jQuery.event.propHooks[ "custom" ] = function( event ) { - // receives the event object for processing - event.propC = true; - return event; - }; - - $fixture.bind( "custom", function( event ) { - ok( event.propC, "event.propC exists" ); - }).trigger( "custom" ); -}); - -test("jQuery.event.propHooks usecase", function() { - - expect( 3 ); - - jQuery( "" ).appendTo( "#qunit-fixture" ); - - var $fixture = jQuery( "#hook-fixture" ); - - $fixture.bind( "fakedrop", function( event ) { - ok( !("dataTransfer" in event), "event.dataTransfer is not available" ); - }).trigger( "fakedrop" ); - - $fixture.unbind( "fakedrop" ); - - jQuery.event.propHooks[ "fakedrop" ] = function( event ) { - event.dataTransfer = "some val"; - return event; - }; - - $fixture.bind( "fakedrop", function( event ) { - ok( ("dataTransfer" in event), "event.dataTransfer exists, just copied" ); - equal( event.dataTransfer, "some val", "event.dataTransfer equal 'some val'" ); - }).trigger( "fakedrop" ); - - $fixture.unbind( "fakedrop" ); -}); - -/* -test("event properties", function() { - stop(); - jQuery("#simon1").click(function(event) { - ok( event.timeStamp, "assert event.timeStamp is present" ); - start(); - }).click(); -}); -*/