From f96bf1041553775a94c1034c97e253e350217173 Mon Sep 17 00:00:00 2001 From: John Resig Date: Wed, 5 Sep 2007 17:06:05 +0000 Subject: [PATCH] Integration of Mike Alsup's excellent form serialization code. The benefits are as follows: - New method: .serializeArray() This returns an array of name/value pairs representing the contents of a form, or individual input elements. - Enhancement: .serialize() The results are correct now (as opposed to the mess from before), and allows you to serializes forms directly (rather than just the input elements). - Enhancement: .val() This now returns the correct value when dealing wih selects. Additionally, when dealing with multiple selects, it returns an array of values. Based upon Mike's code: http://malsup.com/jquery/form/comp/form.js and test suite: http://malsup.com/jquery/form/comp/test.html --- build/test/index.html | 33 +++++++++++++++++++++++++++++++ src/ajax/ajax.js | 24 +++++++++++++++++++---- src/ajax/ajaxTest.js | 32 ++++++++++++++++++++++++------ src/event/eventTest.js | 2 +- src/jquery/coreTest.js | 4 ++-- src/jquery/jquery.js | 29 +++++++++++++++++++++++++--- src/selector/selectorTest.js | 44 +++++++++++++++++++++--------------------- 7 files changed, 130 insertions(+), 38 deletions(-) diff --git a/build/test/index.html b/build/test/index.html index 3b59b0e..b2876b7 100644 --- a/build/test/index.html +++ b/build/test/index.html @@ -116,6 +116,39 @@
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/ajax/ajax.js b/src/ajax/ajax.js index dd54d0c..ffe3048 100644 --- a/src/ajax/ajax.js +++ b/src/ajax/ajax.js @@ -62,9 +62,25 @@ jQuery.fn.extend({ }, serialize: function() { - return jQuery.param( this ); - } - + return jQuery.param(this.serializeArray()); + }, + serializeArray: function() { + return this.map(function(){ + return jQuery.nodeName(this, "form") ? + jQuery.makeArray(this.elements) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + (this.checked || /select|textarea/i.test(this.nodeName) || + /text|hidden|password/i.test(this.type)); + }) + .map(function(i, elem){ var val = jQuery(this).val(); + return val == null ? null : + val.constructor == Array ? + jQuery.map( val, function(i, val){ return {name: elem.name, value: val}; + }) : + {name: elem.name, value: val}; + }).get(); } }); // Attach a bunch of functions for handling common AJAX events @@ -440,7 +456,7 @@ jQuery.extend({ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) ); // Return the resulting serialization - return s.join("&"); + return s.join("&").replace(/%20/g, "+"); } }); diff --git a/src/ajax/ajaxTest.js b/src/ajax/ajaxTest.js index c694cc1..f158bae 100644 --- a/src/ajax/ajaxTest.js +++ b/src/ajax/ajaxTest.js @@ -3,7 +3,7 @@ module("ajax"); // Safari 3 randomly crashes when running these tests, // but only in the full suite - you can run just the Ajax // tests and they'll pass -if ( !jQuery.browser.safari ) { +//if ( !jQuery.browser.safari ) { test("$.ajax() - success callbacks", function() { expect( 8 ); @@ -163,11 +163,31 @@ test("$.ajax - dataType html", function() { }); test("serialize()", function() { - expect(1); - // ignore button, IE takes text content as value, not relevant for this test - equals( $(':input').not('button').serialize(), - 'action=Test&text2=Test&radio1=on&radio2=on&check=on&=on&hidden=&foo%5Bbar%5D=&name=name&=foobar&select1=&select2=3&select3=1&test=&id=', + expect(6); + + equals( $('#form').serialize(), + "action=Test&radio2=on&check=on&hidden=&foo%5Bbar%5D=&name=name&select1=&select2=3&select3=0&select3=1", + 'Check form serialization as query string'); + + equals( $('#form :input').serialize(), + "action=Test&radio2=on&check=on&hidden=&foo%5Bbar%5D=&name=name&select1=&select2=3&select3=0&select3=1", + 'Check input serialization as query string'); + + equals( $('#testForm').serialize(), + 'T3=%3F%0AZ&H1=x&H2=&PWD=&T1=&T2=YES&My+Name=me&S1=abc&S3=YES&S4=', 'Check form serialization as query string'); + + equals( $('#testForm :input').serialize(), + 'T3=%3F%0AZ&H1=x&H2=&PWD=&T1=&T2=YES&My+Name=me&S1=abc&S3=YES&S4=', + 'Check input serialization as query string'); + + equals( $('#form, #testForm').serialize(), + "action=Test&radio2=on&check=on&hidden=&foo%5Bbar%5D=&name=name&select1=&select2=3&select3=0&select3=1&T3=%3F%0AZ&H1=x&H2=&PWD=&T1=&T2=YES&My+Name=me&S1=abc&S3=YES&S4=", + 'Multiple form serialization as query string'); + + equals( $('#form, #testForm :input').serialize(), + "action=Test&radio2=on&check=on&hidden=&foo%5Bbar%5D=&name=name&select1=&select2=3&select3=0&select3=1&T3=%3F%0AZ&H1=x&H2=&PWD=&T1=&T2=YES&My+Name=me&S1=abc&S3=YES&S4=", + 'Mixed form/input serialization as query string'); }); test("$.param()", function() { @@ -618,4 +638,4 @@ test("custom timeout does not set error message when timeout occurs, see #970", } -} +//} diff --git a/src/event/eventTest.js b/src/event/eventTest.js index cacfdeb..334f286 100644 --- a/src/event/eventTest.js +++ b/src/event/eventTest.js @@ -42,7 +42,7 @@ test("bind()", function() { function selectOnChange(event) { equals( event.data, counter++, "Event.data is not a global event object" ); }; - $("select").each(function(i){ + $("#form select").each(function(i){ $(this).bind('change', i, selectOnChange); }).trigger('change'); diff --git a/src/jquery/coreTest.js b/src/jquery/coreTest.js index d0fc9d2..db47856 100644 --- a/src/jquery/coreTest.js +++ b/src/jquery/coreTest.js @@ -836,7 +836,7 @@ test("html(String)", function() { test("filter()", function() { expect(4); - isSet( $("input").filter(":checked").get(), q("radio2", "check1"), "filter(String)" ); + isSet( $("#form input").filter(":checked").get(), q("radio2", "check1"), "filter(String)" ); isSet( $("p").filter("#ap, #sndp").get(), q("ap", "sndp"), "filter('String, String')" ); isSet( $("p").filter("#ap,#sndp").get(), q("ap", "sndp"), "filter('String,String')" ); isSet( $("p").filter(function() { return !$("a", this).length }).get(), q("sndp", "first"), "filter(Function)" ); @@ -862,7 +862,7 @@ test("siblings([String])", function() { isSet( $("#en").siblings().get(), q("sndp", "sap"), "Check for siblings" ); isSet( $("#sndp").siblings(":has(code)").get(), q("sap"), "Check for filtered siblings (has code child element)" ); isSet( $("#sndp").siblings(":has(a)").get(), q("en", "sap"), "Check for filtered siblings (has anchor child element)" ); - isSet( $("#foo").siblings("form, b").get(), q("form", "lengthtest", "floatTest"), "Check for multiple filters" ); + isSet( $("#foo").siblings("form, b").get(), q("form", "lengthtest", "testForm", "floatTest"), "Check for multiple filters" ); isSet( $("#en, #sndp").siblings().get(), q("sndp", "sap", "en"), "Check for unique results from siblings" ); }); diff --git a/src/jquery/jquery.js b/src/jquery/jquery.js index 9e59f9d..d5a32e2 100644 --- a/src/jquery/jquery.js +++ b/src/jquery/jquery.js @@ -306,9 +306,32 @@ jQuery.fn = jQuery.prototype = { }, val: function( val ) { - return val == undefined ? - ( this.length ? this[0].value : null ) : - this.attr( "value", val ); + if ( val == undefined ) { + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special if ( jQuery.nodeName(elem, "select") ) { var index = elem.selectedIndex, + a = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected if ( index < 0 ) + return null; + + // Loop through all the selected options for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[i]; if ( option.selected ) { // Get the specifc value for the option var val = jQuery.browser.msie && !option.attributes["value"].specified ? option.text : option.value; + + // We don't need an array for one selects if ( one ) + return val; + + // Multi-Selects return an array a.push(val); } } + return a; + + // Everything else, we just grab the value } else + return this[0].value.replace(/\r/g, ""); + } + } else + return this.attr( "value", val ); }, html: function( val ) { diff --git a/src/selector/selectorTest.js b/src/selector/selectorTest.js index 626119e..d6121f2 100644 --- a/src/selector/selectorTest.js +++ b/src/selector/selectorTest.js @@ -128,8 +128,8 @@ test("child and adjacent", function() { t( "Last Child", "p:last-child", ["sap"] ); t( "Last Child", "a:last-child", ["simon1","anchor1","mark","yahoo","anchor2","simon"] ); - t( "Nth-child", "#main form > *:nth-child(2)", ["text2","idTest"] ); - t( "Nth-child", "#main form > :nth-child(2)", ["text2","idTest"] ); + t( "Nth-child", "#main form#form > *:nth-child(2)", ["text2"] ); + t( "Nth-child", "#main form#form > :nth-child(2)", ["text2"] ); }); test("attributes", function() { @@ -141,9 +141,9 @@ test("attributes", function() { t( "Attribute Equals", "a[rel='bookmark']", ["simon1"] ); t( "Attribute Equals", 'a[rel="bookmark"]', ["simon1"] ); t( "Attribute Equals", "a[rel=bookmark]", ["simon1"] ); - t( "Multiple Attribute Equals", "input[type='hidden'],input[type='radio']", ["hidden1","radio1","radio2"] ); - t( "Multiple Attribute Equals", "input[type=\"hidden\"],input[type='radio']", ["hidden1","radio1","radio2"] ); - t( "Multiple Attribute Equals", "input[type=hidden],input[type=radio]", ["hidden1","radio1","radio2"] ); + t( "Multiple Attribute Equals", "#form input[type='hidden'],#form input[type='radio']", ["hidden1","radio1","radio2"] ); + t( "Multiple Attribute Equals", "#form input[type=\"hidden\"],#form input[type='radio']", ["hidden1","radio1","radio2"] ); + t( "Multiple Attribute Equals", "#form input[type=hidden],#form input[type=radio]", ["hidden1","radio1","radio2"] ); t( "Attribute selector using UTF8", "span[lang=中文]", ["台北"] ); @@ -157,9 +157,9 @@ test("attributes", function() { t( "Grouped Form Elements", "input[name='foo[bar]']", ["hidden2"] ); - t( ":not() Existing attribute", "select:not([multiple])", ["select1", "select2"]); - t( ":not() Equals attribute", "select:not([name=select1])", ["select2", "select3"]); - t( ":not() Equals quoted attribute", "select:not([name='select1'])", ["select2", "select3"]); + t( ":not() Existing attribute", "#form select:not([multiple])", ["select1", "select2"]); + t( ":not() Equals attribute", "#form select:not([name=select1])", ["select2", "select3"]); + t( ":not() Equals quoted attribute", "#form select:not([name='select1'])", ["select2", "select3"]); }); test("pseudo (:) selectors", function() { @@ -168,10 +168,10 @@ test("pseudo (:) selectors", function() { t( "Last Child", "p:last-child", ["sap"] ); t( "Only Child", "a:only-child", ["simon1","anchor1","yahoo","anchor2"] ); t( "Empty", "ul:empty", ["firstUL"] ); - t( "Enabled UI Element", "input:enabled", ["text1","radio1","radio2","check1","check2","hidden1","hidden2","name","length","idTest"] ); - t( "Disabled UI Element", "input:disabled", ["text2"] ); - t( "Checked UI Element", "input:checked", ["radio2","check1"] ); - t( "Selected Option Element", "option:selected", ["option1a","option2d","option3b","option3c"] ); + t( "Enabled UI Element", "#form input:enabled", ["text1","radio1","radio2","check1","check2","hidden1","hidden2","name"] ); + t( "Disabled UI Element", "#form input:disabled", ["text2"] ); + t( "Checked UI Element", "#form input:checked", ["radio2","check1"] ); + t( "Selected Option Element", "#form option:selected", ["option1a","option2d","option3b","option3c"] ); t( "Text Contains", "a:contains('Google')", ["google","groups"] ); t( "Text Contains", "a:contains('Google Groups')", ["groups"] ); t( "Element Preceded By", "p ~ div", ["foo","fx-queue","fx-tests"] ); @@ -186,16 +186,16 @@ test("pseudo (:) selectors", function() { t( "Position Greater Than", "p:gt(0)", ["ap","sndp","en","sap","first"] ); t( "Position Less Than", "p:lt(3)", ["firstp","ap","sndp"] ); t( "Is A Parent", "p:parent", ["firstp","ap","sndp","en","sap","first"] ); - t( "Is Visible", "input:visible", ["text1","text2","radio1","radio2","check1","check2","name","length","idTest"] ); - t( "Is Hidden", "input:hidden", ["hidden1","hidden2"] ); - - t( "Form element :input", ":input", ["text1", "text2", "radio1", "radio2", "check1", "check2", "hidden1", "hidden2", "name", "button", "area1", "select1", "select2", "select3", "length", "idTest"] ); - t( "Form element :radio", ":radio", ["radio1", "radio2"] ); - t( "Form element :checkbox", ":checkbox", ["check1", "check2"] ); - t( "Form element :text", ":text", ["text1", "text2", "hidden2", "name", "length", "idTest"] ); - t( "Form element :radio:checked", ":radio:checked", ["radio2"] ); - t( "Form element :checkbox:checked", ":checkbox:checked", ["check1"] ); - t( "Form element :checkbox:checked, :radio:checked", ":checkbox:checked, :radio:checked", ["check1", "radio2"] ); + t( "Is Visible", "#form input:visible", ["text1","text2","radio1","radio2","check1","check2","name"] ); + t( "Is Hidden", "#form input:hidden", ["hidden1","hidden2"] ); + + t( "Form element :input", "#form :input", ["text1", "text2", "radio1", "radio2", "check1", "check2", "hidden1", "hidden2", "name", "button", "area1", "select1", "select2", "select3"] ); + t( "Form element :radio", "#form :radio", ["radio1", "radio2"] ); + t( "Form element :checkbox", "#form :checkbox", ["check1", "check2"] ); + t( "Form element :text", "#form :text", ["text1", "text2", "hidden2", "name"] ); + t( "Form element :radio:checked", "#form :radio:checked", ["radio2"] ); + t( "Form element :checkbox:checked", "#form :checkbox:checked", ["check1"] ); + t( "Form element :checkbox:checked, :radio:checked", "#form :checkbox:checked, #form :radio:checked", ["check1", "radio2"] ); t( "Headers", ":header", ["header", "banner", "userAgent"] ); t( "Has Children - :has()", "p:has(a)", ["firstp","ap","en","sap"] ); -- 1.7.10.4