Skip to content

Commit

Permalink
Merge pull request #27 from GUI/mixed-types-arrays
Browse files Browse the repository at this point in the history
Fix bugs when an input name is used across elements of different types. Fixes #15
  • Loading branch information
kflorence committed Oct 2, 2015
2 parents d8435e7 + 7ec5074 commit 807a2ac
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 30 deletions.
78 changes: 53 additions & 25 deletions src/jquery.deserialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ function getElementsByName( elements ) {

jQuery.each( elements, function( i, element ) {
current = elementsByName[ element.name ];
elementsByName[ element.name ] = current === undefined ? element :
( jQuery.isArray( current ) ? current.concat( element ) : [ current, element ] );
if ( current === undefined ) {
elementsByName[ element.name ] = [];
}
elementsByName[ element.name ].push( element );
});

return elementsByName;
Expand Down Expand Up @@ -73,6 +75,7 @@ jQuery.fn.deserialize = function( data, options ) {
}

var current, element, j, len, name, property, type, value,
elementsForName, k, nameIndex, optionsAndInputs,
change = jQuery.noop,
complete = jQuery.noop,
names = {};
Expand All @@ -95,40 +98,65 @@ jQuery.fn.deserialize = function( data, options ) {
name = current.name;
value = current.value;

if ( !( element = elements[ name ] ) ) {
elementsForName = elements[ name ];
if ( !elementsForName || elementsForName.length === 0 ) {
continue;
}

type = ( len = element.length ) ? element[ 0 ] : element;
type = ( type.type || type.nodeName ).toLowerCase();
property = null;

if ( rvalue.test( type ) ) {
if ( len ) {
j = names[ name ];
element = element[ names[ name ] = ( j == undefined ) ? 0 : ++j ];
// Keep track of parameters that are named the same for array handling.
if ( names[ name ] === undefined ) {
names[ name ] = 0;
}
nameIndex = names[ name ]++;

// Handle the simple case of inputs that take a simple value.
//
// Possible arrays are handled by fetching the element that corresponds
// to the index of the current name.
if ( elementsForName[ nameIndex ] ) {
element = elementsForName[ nameIndex ];
type = ( element.type || element.nodeName ).toLowerCase();
if ( rvalue.test( type ) ) {
change.call( element, ( element.value = value ) );

// Skip further processing for this simple case.
continue;
}
}

change.call( element, ( element.value = value ) );
// Handle more complex cases involving select menus, checkboxes, or radios.
for ( j = 0, len = elementsForName.length; j < len; j++) {
element = elementsForName[ j ];
type = ( element.type || element.nodeName ).toLowerCase();
property = null;

} else if ( rcheck.test( type ) ) {
property = "checked";
if ( rcheck.test( type ) ) {
property = "checked";

} else if ( rselect.test( type ) ) {
property = "selected";
}
} else if ( rselect.test( type ) ) {
property = "selected";

if ( property ) {
if ( !len ) {
element = [ element ];
len = 1;
}

for ( j = 0; j < len; j++ ) {
current = element[ j ];
if ( property ) {
// Flatten all of the inputs (radios & checkboxes) and options
// (under select menus), so all of them can be treated in a
// standard way.
optionsAndInputs = [];
if ( element.options ) {
for ( k = 0; k < element.options.length; k++ ) {
optionsAndInputs.push( element.options[ k ] );
}

} else {
optionsAndInputs.push(element);
}

if ( current.value == value ) {
change.call( current, ( current[ property ] = true ) && value );
for ( k = 0; k < optionsAndInputs.length; k++ ) {
current = optionsAndInputs[ k ];
if ( current.value == value ) {
change.call( current, ( current[ property ] = true ) && value );
}
}
}
}
Expand Down
89 changes: 88 additions & 1 deletion tests/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,96 @@ <h4>Multiple Select List</h4>
<option value="3">3</option>
</select>
</li>
<li>
<h4>Multiple Select Lists With Same Name</h4>
<select name="selectMultipleDuplicate" multiple="multiple">
<option value="1" selected="selected">1</option>
<option value="2" selected="selected">2</option>
<option value="3">3</option>
</select>
<select name="selectMultipleDuplicate" multiple="multiple">
<option value="4">4</option>
<option value="5" selected="selected">5</option>
</select>
</li>
<li>
<h4>Lone Checkbox With Hidden Default (checked)</h4>
<input name="loneCheckboxWithHiddenChecked" type="hidden" value="false" />
<label class="checkbox"><input name="loneCheckboxWithHiddenChecked" type="checkbox" value="true" checked="checked" />true</label>
</li>
<li>
<h4>Lone Checkbox With Hidden Default (unchecked)</h4>
<input name="loneCheckboxWithHiddenUnchecked" type="hidden" value="false" />
<label class="checkbox"><input name="loneCheckboxWithHiddenUnchecked" type="checkbox" value="true" />true</label>
</li>
<li>
<h4>Checkbox Group With Hidden Default (checked)</h4>
<span class="group">
<input name="checkboxGroupWithHiddenChecked" type="hidden" value="false" />
<label class="checkbox"><input name="checkboxGroupWithHiddenChecked" type="checkbox" value="1" checked="checked" />1</label>
<label class="checkbox"><input name="checkboxGroupWithHiddenChecked" type="checkbox" value="2" />2</label>
<label class="checkbox"><input name="checkboxGroupWithHiddenChecked" type="checkbox" value="3" checked="checked" />3</label>
</span>
</li>
<li>
<h4>Checkbox Group With Hidden Default (unchecked)</h4>
<span class="group">
<input name="checkboxGroupWithHiddenUnchecked" type="hidden" value="false" />
<label class="checkbox"><input name="checkboxGroupWithHiddenUnchecked" type="checkbox" value="1" />1</label>
<label class="checkbox"><input name="checkboxGroupWithHiddenUnchecked" type="checkbox" value="2" />2</label>
<label class="checkbox"><input name="checkboxGroupWithHiddenUnchecked" type="checkbox" value="3" />3</label>
</span>
</li>
<li>
<h4>Multiple Select List With Hidden Default (with selections)</h4>
<input name="selectMultipleWithHiddenWithSelections" type="hidden" value="false" />
<select name="selectMultipleWithHiddenWithSelections" multiple="multiple">
<option value="1" selected="selected">1</option>
<option value="2" selected="selected">2</option>
<option value="3">3</option>
</select>
</li>
<li>
<h4>Multiple Select List With Hidden Default (no selections)</h4>
<input name="selectMultipleWithHiddenNoSelections" type="hidden" value="false" />
<select name="selectMultipleWithHiddenNoSelections" multiple="multiple">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</li>
<li>
<h4>Multiple Select List With Checkbox After</h4>
<select name="selectMultipleWithCheckboxAfter" multiple="multiple">
<option value="1" selected="selected">1</option>
<option value="2" selected="selected">2</option>
<option value="3">3</option>
</select>
<input name="selectMultipleWithCheckboxAfter" type="checkbox" value="4" checked="checked" />
</li>
<li>
<h4>Multiple Select List With Checkboxes Before and After</h4>
<input name="selectMultipleWithCheckboxesSurrounding" type="checkbox" value="0" checked="checked" />
<select name="selectMultipleWithCheckboxesSurrounding" multiple="multiple">
<option value="1" selected="selected">1</option>
<option value="2" selected="selected">2</option>
<option value="3">3</option>
</select>
<input name="selectMultipleWithCheckboxesSurrounding" type="checkbox" value="4" checked="checked" />
</li>
<li>
<h4>Multiple Select List With Checkboxes Before and After</h4>
<input name="selectMultipleWithHiddenAndCheckbox" type="hidden" value="false" />
<select name="selectMultipleWithHiddenAndCheckbox" multiple="multiple">
<option value="1">1</option>
<option value="2" selected="selected">2</option>
<option value="3">3</option>
</select>
<input name="selectMultipleWithHiddenAndCheckbox" type="checkbox" value="4" />
</li>
</ol>
<input name="multtext" type="hidden" value="howdy" />
</form>
</div>
</body>
</html>
</html>
15 changes: 11 additions & 4 deletions tests/unit/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,24 @@ $.each( [ "serialize", "serializeArray", "serializeObject" ], function( i, seria
var changeCalledCount = 0,
data = $form[ serializeMethod ]();

$form.get( 0 ).reset();
// Completely clear the form of any values to ensure deserialize
// repopulates the form (this differs from a form.reset(), since
// reset only resets back to the default state from initial page
// load).
$form.not(':button, :submit, :reset, :hidden, :checkbox, :radio, select, option').val('');
$(':checkbox, :radio').removeAttr('checked');
$('select').attr('selectedIndex', -1);
$('option:selected').removeAttr('selected');

$form.deserialize( data, {
change: function() {
changeCalledCount++;
},
complete: function() {
deepEqual( data, $form[ serializeMethod ](), "Serialized data matches deserialized data (element #" + elementIndex + ")" );
equal( changeCount, changeCalledCount, "Change called for each changed input (element #" + elementIndex + ")" );
deepEqual( $form[ serializeMethod ](), data, "Serialized data matches deserialized data (element #" + elementIndex + ")" );
equal( changeCalledCount, changeCount, "Change called for each changed input (element #" + elementIndex + ")" );
}
});
});
});
});
});

0 comments on commit 807a2ac

Please sign in to comment.