Clone isn't cloning select values

Jquery

Jquery Problem Overview


I didn't expect it but the following test fails on the cloned value check:

test("clone should retain values of select", function() {
    var select = $("<select>").append($("<option>")
                              .val("1"))
                              .append($("<option>")
                              .val("2"));
    $(select).val("2");
    equals($(select).find("option:selected").val(), "2", "expect 2");
    var clone = $(select).clone();
    equals($(clone).find("option:selected").val(), "2", "expect 2");
});

Is this right?

Jquery Solutions


Solution 1 - Jquery

After further research I found this ticket in the JQuery bug tracker system which explains the bug and provides a work around. Apparently, it is too expensive to clone the select values so they won't fix it.

https://bugs.jquery.com/ticket/1294

My use of the clone method was in a generic method where anything might be cloned so I'm not sure when or if there will be a select to set the value on. So I added the following:

var selects = $(cloneSourceId).find("select");
$(selects).each(function(i) {
    var select = this;
    $(clone).find("select").eq(i).val($(select).val());
});

Solution 2 - Jquery

Here's a fixed version of the clone method for jQuery:

https://github.com/spencertipping/jquery.fix.clone

// Textarea and select clone() bug workaround | Spencer Tipping
// Licensed under the terms of the MIT source code license

// Motivation.
// jQuery's clone() method works in most cases, but it fails to copy the value of textareas and select elements. This patch replaces jQuery's clone() method with a wrapper that fills in the
// values after the fact.

// An interesting error case submitted by Piotr Przybył: If two <select> options had the same value, the clone() method would select the wrong one in the cloned box. The fix, suggested by Piotr
// and implemented here, is to use the selectedIndex property on the <select> box itself rather than relying on jQuery's value-based val().

(function (original) {
  jQuery.fn.clone = function () {
    var result           = original.apply(this, arguments),
        my_textareas     = this.find('textarea').add(this.filter('textarea')),
        result_textareas = result.find('textarea').add(result.filter('textarea')),
        my_selects       = this.find('select').add(this.filter('select')),
        result_selects   = result.find('select').add(result.filter('select'));

    for (var i = 0, l = my_textareas.length; i < l; ++i) $(result_textareas[i]).val($(my_textareas[i]).val());
    for (var i = 0, l = my_selects.length;   i < l; ++i) result_selects[i].selectedIndex = my_selects[i].selectedIndex;

    return result;
  };
}) (jQuery.fn.clone);

Solution 3 - Jquery

Made a plugin out of chief7's answer:

(function($,undefined) {
	$.fn.cloneSelects = function(withDataAndEvents, deepWithDataAndEvents) {
	    var $clone = this.clone(withDataAndEvents, deepWithDataAndEvents);
	    var $origSelects = $('select', this);
	    var $clonedSelects = $('select', $clone);
	    $origSelects.each(function(i) {
	        $clonedSelects.eq(i).val($(this).val());
	    });
	    return $clone;
	}
})(jQuery);

Only tested it briefly, but it seems to work.

Solution 4 - Jquery

My approach is a little different.

Instead of modifying selects during cloning, I'm just watching every select on page for change event, and then, if value is changed I add needed selected attribute to selected <option> so it becomes <option selected="selected">. As selection is now marked in <option>'s markup, it will be passed when you'll .clone() it.

The only code you need:

//when ANY select on page changes its value
$(document).on("change", "select", function(){
	var val = $(this).val(); //get new value
    //find selected option
	$("option", this).removeAttr("selected").filter(function(){
		return $(this).attr("value") == val;
	}).first().attr("selected", "selected"); //add selected attribute to selected option
});

And now, you can copy select any way you want and it'll have it's value copied too.

$("#my-select").clone(); //will have selected value copied

I think this solution is less custom so you don't need to worry if your code will break if you'll modify something later.

If you don't want it to be applied to every select on page, you can change selector on the first line like:

$(document).on("change", "select.select-to-watch", function(){

Solution 5 - Jquery

Simplification of chief7's answer:

var cloned_form = original_form.clone()
original_form.find('select').each(function(i) {
	cloned_form.find('select').eq(i).val($(this).val())
})

Again, here's the jQuery ticket: http://bugs.jquery.com/ticket/1294

Solution 6 - Jquery

Yes. This is because the 'selected' property of a 'select' DOM node differs from the 'selected' attribute of the options. jQuery does not modify the options' attributes in any way.

Try this instead:

$('option', select).get(1).setAttribute('selected', 'selected');
//    starting from 0   ^

If you're really interested in how the val function works, you may want to examine

alert($.fn.val)

Solution 7 - Jquery

Cloning a <select> does not copy the value= property on <option>s. So Mark's plugin does not work in all cases.

To fix, do this before cloning the <select> values:

var $origOpts = $('option', this);
var $clonedOpts = $('option', $clone);
$origOpts.each(function(i) {
   $clonedOpts.eq(i).val($(this).val());
});

A different way to clone which <select> option is selected, in jQuery 1.6.1+...

// instead of:
$clonedSelects.eq(i).val($(this).val());

// use this:
$clonedSelects.eq(i).prop('selectedIndex', $(this).prop('selectedIndex'));

The latter allows you to set the <option> values after setting the selectedIndex.

Solution 8 - Jquery

$(document).on("change", "select", function(){
    original = $("#original");
	clone = $(original.clone());
    clone.find("select").val(original.find("select").val());

});

Solution 9 - Jquery

If you just need the value of the select, to serialize the form or something like it, this works for me:

$clonedForm.find('theselect').val($origForm.find('theselect').val());

Solution 10 - Jquery

After 1 hour of trying different solutions that didn't work, I did create this simple solution

$clonedItem.find('select option').removeAttr('selected');
$clonedItem.find('select option[value="' + $originaItem.find('select').val() + '"]').attr('selected', 'true');

Solution 11 - Jquery

@pie6k show an good idea.

It solved my problem. I change it a little small:

$(document).on("change", "select", function(){
    var val = $(this).val();
    $(this).find("option[value=" + val + "]").attr("selected",true);
});

Solution 12 - Jquery

just reporting back. For some godly unknown reason, and even though this was the first thing I tested, and I haven't changed my code whatsoever, now the

$("#selectTipoIntervencion1").val($("#selectTipoIntervencion0").val());

approach is working. I have no idea why or if it will stop working again as soon as I change something, but I'm gonna go with this for now. Thanks everybody for the help!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionchief7View Question on Stackoverflow
Solution 1 - Jquerychief7View Answer on Stackoverflow
Solution 2 - JqueryNovalisView Answer on Stackoverflow
Solution 3 - JquerympenView Answer on Stackoverflow
Solution 4 - JqueryAdam PietrasiakView Answer on Stackoverflow
Solution 5 - JqueryCollin AndersonView Answer on Stackoverflow
Solution 6 - Jqueryuser123444555621View Answer on Stackoverflow
Solution 7 - JqueryjamesvlView Answer on Stackoverflow
Solution 8 - JqueryHilary OkoroView Answer on Stackoverflow
Solution 9 - JquerySkarXaView Answer on Stackoverflow
Solution 10 - JqueryAlexView Answer on Stackoverflow
Solution 11 - JqueryGalleyView Answer on Stackoverflow
Solution 12 - JqueryDavid AnteloView Answer on Stackoverflow