83

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?

Alex B
  • 82,554
  • 44
  • 203
  • 280
chief7
  • 14,263
  • 14
  • 47
  • 80

12 Answers12

82

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());
});
Jon
  • 2,932
  • 2
  • 23
  • 30
chief7
  • 14,263
  • 14
  • 47
  • 80
  • 18
    They don't explain why it's "too expensive". I'm surprised there's no better solution 8 years later. – Tyler Collier Nov 23 '15 at 17:25
  • 2
    Why not just watch selects for changes and add `html` `selected` attribute on any change so it would be easly cloned? – Adam Pietrasiak Jul 26 '16 at 09:12
  • Bug ticket complaints (fixed URL: https://bugs.jquery.com/ticket/1294) against IE, but it does work nowhere. Test your browesr by this fiddle: https://jsfiddle.net/ygmL0k8m/4/ – hejdav Oct 27 '16 at 11:52
  • 1
    This fix did not work for me. The below answer (Novalis') worked. – rigdonmr Jul 27 '17 at 22:13
41

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);
Antony
  • 14,900
  • 10
  • 46
  • 74
Novalis
  • 511
  • 4
  • 7
  • 1
    +1 Thats an awesome plugin. Please continue maintaining this project. – Houman Dec 20 '12 at 12:26
  • 3
    This works great! Why cant jQuery just do it, dev's using Clone understand its expensive... but we NEED to do it any way.. jeeez... so much time wasted trying to debug this. Thanks, this fix works great! +beer – Piotr Kula Jul 10 '15 at 11:02
  • can somebody explain how to apply this? for example in case of `$('.inputPrefixListIx:first').clone().insertAfter('.inputPrefixListIx:last');` how it should look like considering `jQuery.fn.clone`? – nskalis Feb 19 '20 at 13:58
  • 3
    not working with select multiple. It only selects first option ((( – Сергей Сергеев Nov 20 '20 at 13:10
  • For anyone curious, Spencer Tipping has an update on his Jquery Clone repo that handles Selects with Multiple attr, find it here https://github.com/spencertipping/jquery.fix.clone/blob/master/jquery.fix.clone.js – David Arias Dec 19 '22 at 21:55
10

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.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Many thanks this looks like a universal solution. But how to implement this together with JQuery please? – seedme May 06 '21 at 02:12
8

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(){
Adam Pietrasiak
  • 12,773
  • 9
  • 78
  • 91
  • I thought this approach was pretty clever so I almost used it -- however, eventually I realized that it goes against the spirit of the "selected" attribute, which is not really supposed to change as it represents the initial (default) value for the – etipton Feb 28 '17 at 07:09
  • What is the downside of changing it? ie. changing `initial selection`? – Adam Pietrasiak Feb 28 '17 at 09:38
  • _Practically_, I can't think of any real downside TBH. Just pointing out that _technically_ it goes against the W3C spec. But not in a major way (so still a nice solution). – etipton Mar 07 '17 at 10:43
5

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

Collin Anderson
  • 14,787
  • 6
  • 68
  • 57
2

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.

jamesvl
  • 1,649
  • 12
  • 8
2

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)
user123444555621
  • 148,182
  • 27
  • 114
  • 126
  • If I use the val() on the select object the test fails as well: test("clone should retain values of select", function() { var select = $(" – chief7 Apr 13 '09 at 12:46
  • That's pretty weird, cause this works for me in IE 6/7, Firefox 3 and Opera 9. Maybe something wrong with your 'equals' function? alert(eval(' select = $(" – user123444555621 Apr 13 '09 at 14:27
1
$(document).on("change", "select", function(){
    original = $("#original");
    clone = $(original.clone());
    clone.find("select").val(original.find("select").val());

});
Hilary Okoro
  • 281
  • 1
  • 3
  • 7
0

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());
SkarXa
  • 1,184
  • 1
  • 12
  • 24
0

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');
Alex
  • 1,033
  • 4
  • 23
  • 43
0

@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);
});
Galley
  • 493
  • 6
  • 9
0

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!

David Antelo
  • 503
  • 6
  • 19