From: Alexander Farkas Date: Mon, 21 Dec 2009 20:32:32 +0000 (-0500) Subject: Used the patch from Alexander as the basis for a rewrite of the IE change event logic... X-Git-Url: http://git.asbjorn.biz/?p=jquery.git;a=commitdiff_plain;h=5dc6b7ce3469eaadb37a151d449e8d36571d1894 Used the patch from Alexander as the basis for a rewrite of the IE change event logic. Now has full parity with the regular change event in other browsers: Works with regular bind, works better with multiple selects, works as a regular change event (note test suite changes), works with readonly/disabled inputs, and much more. The original patch had a number of problems, including firing the change event too many times, not bubblinb properly, and not handling clicks on multi-selects properly - that should all be fixed now. Thanks Alexander for the patch pushing in the right direction. --- diff --git a/src/event.js b/src/event.js index cf00e18..983e9e5 100644 --- a/src/event.js +++ b/src/event.js @@ -612,50 +612,85 @@ jQuery.event.special.submit = { // change delegation, happens here so we have bind. if ( !jQuery.support.changeBubbles ) { -jQuery.event.special.change = { - filters: { - click: function( e ) { - var elem = e.target; +var formElems = /textarea|input|select/i; - if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) { - return trigger( "change", this, arguments ); - } +function getVal( elem ) { + var type = elem.type, val = elem.value; - return changeFilters.keyup.call( this, e ); - }, - keyup: function( e ) { - var elem = e.target, data, index = elem.selectedIndex + ""; + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; - if ( elem.nodeName.toLowerCase() === "select" ) { - data = jQuery.data( elem, "_change_data" ); - jQuery.data( elem, "_change_data", index ); + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; - if ( (elem.type === "select-multiple" || data != null) && data !== index ) { - return trigger( "change", this, arguments ); - } - } - }, - beforeactivate: function( e ) { - var elem = e.target; + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; +} + +function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); - if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) { - return trigger( "change", this, arguments ); + if ( val === data ) { + return; + } + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( elem.type !== "select" && (data != null || val) ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], this ); + } +} + +jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); } }, - 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 ) { + // Change has to be called before submit + // Keydown will be called before keypress, wich is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; - return trigger( "change", this, arguments ); + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); } }, - 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 ); + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + + if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" ) { + return jQuery.data( elem, "_change_data", getVal(elem) ); } } }, @@ -663,14 +698,15 @@ jQuery.event.special.change = { 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; + + return formElems.test( this.nodeName ); }, remove: function( namespaces, fn ) { for ( var type in changeFilters ) { jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] ); } + + return formElems.test( this.nodeName ); } }; diff --git a/test/delegatetest.html b/test/delegatetest.html index cf30d87..e3ebbfc 100644 --- a/test/delegatetest.html +++ b/test/delegatetest.html @@ -2,122 +2,161 @@

Change Tests

- - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- Change each: - - - - - - -
- - -
- -
- - -
- - - - $().bind('change')
Results:SELECTMULTICHECKBOXRADIOTEXTTEXTAREADOCUMENT
+ Change each: + + + + + + +
+ + + + +
+ +
+ + + + +
+ + + + + $(document).bind('change')
Live:SELECTMULTICHECKBOXRADIOTEXTTEXTAREADOCUMENT
Bind:SELECTMULTICHECKBOXRADIOTEXTTEXTAREA

Submit Tests

- - - - - - - - - - - - - - + + + + + + + + + + + + + +
- Submit each: - -
- -
-
-
- -
-
-
- -
-
$().bind('submit')
Results:TEXTPASSWORDBUTTONDOCUMENT
+ Submit each: + +
+ +
+
+
+ +
+
+
+ +
+
$(document).bind('submit')
Results:TEXTPASSWORDBUTTONDOCUMENT
- + + diff --git a/test/unit/event.js b/test/unit/event.js index 80a2e6a..70c721f 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -873,26 +873,20 @@ test("live with change", function(){ // test click on select - // first click sets data - if ( !jQuery.support.changeBubbles ) { - select[0].selectedIndex = 1; - select.trigger("keyup"); - } - // second click that changed it selectChange = 0; select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; - select.trigger(jQuery.support.changeBubbles ? "change" : "click"); + select.trigger("change"); equals( selectChange, 1, "Change on click." ); // test keys on select selectChange = 0; select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; - select.trigger(jQuery.support.changeBubbles ? "change" : "keyup"); + select.trigger("change"); equals( selectChange, 1, "Change on keyup." ); // test click on checkbox - checkbox.trigger(jQuery.support.changeBubbles ? "change" : "click"); + checkbox.trigger("change"); equals( checkboxChange, 1, "Change on checkbox." ); // test before activate on radio @@ -903,12 +897,8 @@ test("live with change", function(){ textareaChange++; }); - if ( !jQuery.support.changeBubbles ) { - textarea.trigger("focus"); - } - textarea.val(oldVal + "foo"); - textarea.trigger(jQuery.support.changeBubbles ? "change" : "blur"); + textarea.trigger("change"); equals( textareaChange, 1, "Change on textarea." ); textarea.val(oldVal); @@ -920,12 +910,8 @@ test("live with change", function(){ textChange++; }); - if ( !jQuery.support.changeBubbles ) { - text.trigger("focus"); - } - text.val(oldVal+"foo"); - text.trigger(jQuery.support.changeBubbles ? "change" : "blur"); + text.trigger("change"); equals( textChange, 1, "Change on text input." ); text.val(oldTextVal); @@ -937,12 +923,8 @@ test("live with change", function(){ passwordChange++; }); - if ( !jQuery.support.changeBubbles ) { - password.trigger("focus"); - } - password.val(oldPasswordVal + "foo"); - password.trigger(jQuery.support.changeBubbles ? "change" : "blur"); + password.trigger("change"); equals( passwordChange, 1, "Change on password input." ); password.val(oldPasswordVal); @@ -954,17 +936,17 @@ test("live with change", function(){ selectChange = 0; select.die("change"); select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; - select.trigger(jQuery.support.changeBubbles ? "change" : "click"); + select.trigger("change"); equals( selectChange, 0, "Die on click works." ); selectChange = 0; select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; - select.trigger(jQuery.support.changeBubbles ? "change" : "keyup"); + select.trigger("change"); equals( selectChange, 0, "Die on keyup works." ); // die specific checkbox checkbox.die("change", checkboxFunction); - checkbox.trigger(jQuery.support.changeBubbles ? "change" : "click"); + checkbox.trigger("change"); equals( checkboxChange, 1, "Die on checkbox." ); });