Used the patch from Alexander as the basis for a rewrite of the IE change event logic...
authorAlexander Farkas <a.farkas.pm@googlemail.com>
Mon, 21 Dec 2009 20:32:32 +0000 (15:32 -0500)
committerjeresig <jeresig@gmail.com>
Mon, 21 Dec 2009 20:32:32 +0000 (15:32 -0500)
src/event.js
test/delegatetest.html
test/unit/event.js

index cf00e18..983e9e5 100644 (file)
@@ -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 );
        }
 };
 
index cf30d87..e3ebbfc 100644 (file)
     <head>\r
         <script src='../dist/jquery.js' type='text/javascript'></script>\r
         <style>\r
-            .red {\r
-                background-color: red;\r
-                border: solid 3px red;\r
-            }\r
+       .red {\r
+           background-color: red;\r
+           border: solid 3px red;\r
+       }\r
         </style>\r
     </head>\r
     <body>\r
         <h2>Change Tests</h2>\r
         <table>\r
-                <tr>\r
-                    <td>\r
-                        Change each:\r
-                    </td>\r
-                    <td>\r
-                        <select class='select_test'>\r
-                           <option value='one'>change me 1</option>\r
-                            <option value='two'>change me 2</option>\r
-                            <option value='three'>change me 3</option>\r
-                        </select>\r
-                    </td>\r
-                    <td>\r
-                        <select class='mselect_test' multiple="multiple">\r
-                           <option value='one'>change me 1</option>\r
-                            <option value='two'>change me 2</option>\r
-                            <option value='three'>change me 3</option>\r
-                        </select>\r
-                    </td>\r
-                    <td>\r
-                        <input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox1"/>\r
-                        <label for="checkbox1">Checkbox 1 label</label><br/>\r
-                        <input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox2"/>\r
-                        <label for="checkbox2">Checkbox 2 label</label>\r
-                    </td>\r
-                    <td>\r
-                        <input type="radio" class="radio_test" name="myradio" id="radio1"/>\r
-                        <label for="radio1">Radio 1 label</label><br/>\r
-                        <input type="radio" class="radio_test" name="myradio" id="radio2"/>\r
-                        <label for="radio2">Radio 2 label</label>\r
-                    </td>\r
-                    <td>\r
-                        <input class='test' value='' id='input' size='10' />\r
-                    </td>\r
-                    <td>\r
-                        <textarea rows='2'></textarea>\r
-                    </td>\r
-                    <td>$().bind('change')</td>\r
-                </tr>\r
-                <tr>\r
-                    <td>Results:</td>\r
-                    <td id='select' class="red">SELECT</td>\r
-                    <td id='mselect' class="red">MULTI</td>\r
-                    <td id='checkbox' class="red">CHECKBOX</td>\r
-                    <td id='radio' class="red">RADIO</td>\r
-                    <td id='text' class="red">TEXT</td>\r
-                    <td id='textarea' class="red">TEXTAREA</td>\r
-                    <td id='boundChange' class="red">DOCUMENT</td>\r
-                </tr>\r
+           <tr>\r
+               <td>\r
+               Change each:\r
+               </td>\r
+               <td>\r
+               <select class='select_test'>\r
+                   <option value='one'>change me 1</option>\r
+                   <option value='two'>change me 2</option>\r
+                   <option value='three'>change me 3</option>\r
+               </select>\r
+               </td>\r
+               <td>\r
+               <select class='mselect_test' multiple="multiple">\r
+                   <option value='one'>change me 1</option>\r
+                   <option value='two'>change me 2</option>\r
+                   <option value='three'>change me 3</option>\r
+               </select>\r
+               </td>\r
+               <td>\r
+               <input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox1"/>\r
+               <label for="checkbox1">Checkbox 1</label><br/>\r
+               <input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox2"/>\r
+               <label for="checkbox2">Checkbox 2</label>\r
+               <input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox3" disabled="disabled"/>\r
+               <label for="checkbox3">Checkbox 3</label>\r
+               </td>\r
+               </td>\r
+               </td>\r
+               <td>\r
+               <input type="radio" class="radio_test" name="myradio" id="radio1"/>\r
+               <label for="radio1">Radio1</label><br/>\r
+               <input type="radio" class="radio_test" name="myradio" id="radio2"/>\r
+               <label for="radio2">Radio2</label>\r
+               <input type="radio" class="radio_test" name="myradio" id="radio3" disabled="disabled"/>\r
+               <label for="radio3">Radio3</label>\r
+               </td>\r
+               <td>\r
+               <input class='test' value='' id='input' size='10' />\r
+               <input class='test' value='test' id='input2' size='10' readonly="readonly" />\r
+               </td>\r
+               <td>\r
+               <textarea rows='2'></textarea>\r
+               </td>\r
+               <td>$(document).bind('change')</td>\r
+           </tr>\r
+           <tr>\r
+               <td>Live:</td>\r
+               <td id='select' class="red">SELECT</td>\r
+               <td id='mselect' class="red">MULTI</td>\r
+               <td id='checkbox' class="red">CHECKBOX</td>\r
+               <td id='radio' class="red">RADIO</td>\r
+               <td id='text' class="red">TEXT</td>\r
+               <td id='textarea' class="red">TEXTAREA</td>\r
+               <td id='boundChange' class="red">DOCUMENT</td>\r
+           </tr>\r
+           <tr>\r
+               <td>Bind:</td>\r
+               <td id='selectbind' class="red">SELECT</td>\r
+               <td id='mselectbind' class="red">MULTI</td>\r
+               <td id='checkboxbind' class="red">CHECKBOX</td>\r
+               <td id='radiobind' class="red">RADIO</td>\r
+               <td id='textbind' class="red">TEXT</td>\r
+               <td id='textareabind' class="red">TEXTAREA</td>\r
+           </tr>\r
         </table>\r
         <h2>Submit Tests</h2>\r
         <table>\r
-            <tr>\r
-                <td>\r
-                    Submit each:\r
-                </td>\r
-                <td>\r
-                    <form action="" id="text_submit">\r
-                        <input class='test' type='text' value='Key Return To Submit'/>\r
-                    </form>\r
-                </td>\r
-                <td>\r
-                    <form action="" id="password_submit">\r
-                        <input class='test' type='password' value=''/>\r
-                    </form>\r
-                </td>\r
-                <td>\r
-                    <form action="" id="submit_submit">\r
-                        <input type='submit' value="Click Me To Submit" />\r
-                    </form>\r
-                </td>\r
-                <td>$().bind('submit')</td>\r
-            </tr>\r
-            <tr>\r
-                    <td>Results:</td>\r
-                    <td id='textSubmit' class="red">TEXT</td>\r
-                    <td id='passwordSubmit' class="red">PASSWORD</td>\r
-                    <td id='submitSubmit' class="red">BUTTON</td>\r
-                    <td id='boundSubmit' class="red">DOCUMENT</td>\r
-            </tr>\r
+       <tr>\r
+           <td>\r
+               Submit each:\r
+           </td>\r
+           <td>\r
+               <form action="" id="text_submit">\r
+               <input class='test' type='text' value='Key Return To Submit'/>\r
+               </form>\r
+           </td>\r
+           <td>\r
+               <form action="" id="password_submit">\r
+               <input class='test' type='password' value=''/>\r
+               </form>\r
+           </td>\r
+           <td>\r
+               <form action="" id="submit_submit">\r
+               <input type='submit' value="Click Me To Submit" />\r
+               </form>\r
+           </td>\r
+           <td>$(document).bind('submit')</td>\r
+       </tr>\r
+       <tr>\r
+               <td>Results:</td>\r
+               <td id='textSubmit' class="red">TEXT</td>\r
+               <td id='passwordSubmit' class="red">PASSWORD</td>\r
+               <td id='submitSubmit' class="red">BUTTON</td>\r
+               <td id='boundSubmit' class="red">DOCUMENT</td>\r
+       </tr>\r
         </table>\r
-        \r
+\r
+       <ul id="log"></ul>\r
 \r
         <script type='text/javascript'>\r
-            makeChangeFunc = function(id, prevent){\r
-                return function(e){\r
-                    if(prevent)\r
-                        e.preventDefault();\r
-                    $(id).css("backgroundColor","green").css("border","solid 3px green");\r
-                    setTimeout(function(){\r
-                        $(id).css("backgroundColor","");\r
-                    }, 700)\r
-                }\r
-            }\r
-            \r
-            $(".select_test").live("change",makeChangeFunc("#select"))\r
-            $(".mselect_test").live("change",makeChangeFunc("#mselect"))\r
-            $(".checkbox_test").live("change",makeChangeFunc("#checkbox"))\r
-            $(".radio_test").live("change",makeChangeFunc("#radio"))\r
-            $('textarea').live('change', makeChangeFunc("#textarea"))\r
-            $('#input').live('change', makeChangeFunc("#text"))\r
-            $(document).bind('change', makeChangeFunc("#boundChange"))\r
-            \r
-            $("#text_submit").live("submit", makeChangeFunc("#textSubmit", true) )\r
-            $("#password_submit").live("submit", makeChangeFunc("#passwordSubmit", true) )\r
-            $("#submit_submit").live("submit", makeChangeFunc("#submitSubmit", true) )\r
-            $(document).bind('submit', makeChangeFunc("#boundSubmit"))\r
-            \r
+       jQuery.fn.addChangeTest = function( id, prevent ) {\r
+               return this.bind("change", function(e){\r
+                       jQuery(id + "bind").blink();\r
+               }).live("change", function(e){\r
+                       if ( prevent ) {\r
+                               e.preventDefault();\r
+                       }\r
+\r
+                       jQuery(id).blink();\r
+               });\r
+       };\r
+\r
+       jQuery.fn.addSubmitTest = function( id, prevent ) {\r
+               return this.live("submit", function(e){\r
+                       if ( prevent ) {\r
+                               e.preventDefault();\r
+                       }\r
+\r
+                       jQuery(id).blink();\r
+               });\r
+       };\r
+\r
+       jQuery.fn.blink = function(){\r
+               return this.css("backgroundColor","green").css("border","solid 3px green").delay(700).queue(function(next){\r
+                       jQuery(this).css("backgroundColor","");\r
+                       next();\r
+               });\r
+       };\r
+       \r
+       $(".select_test").addChangeTest("#select");\r
+       $(".mselect_test").addChangeTest("#mselect");\r
+       $(".checkbox_test").addChangeTest("#checkbox");\r
+       $(".radio_test").addChangeTest("#radio");\r
+       $('textarea').addChangeTest("#textarea");\r
+       $('#input').addChangeTest("#text");\r
+       $(document).bind("change", function(){\r
+               jQuery("#boundChange").blink();\r
+       });\r
+       \r
+       $("#text_submit").addSubmitTest("#textSubmit", true);\r
+       $("#password_submit").addSubmitTest("#passwordSubmit", true);\r
+       $("#submit_submit").addSubmitTest("#submitSubmit", true);\r
+       $(document).bind("submit", function(){\r
+               jQuery("#boundSubmit").blink();\r
+       });\r
+       \r
         </script>\r
     </body>\r
 </html>\r
index 80a2e6a..70c721f 100644 (file)
@@ -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." );
 });