chief7
chief7

Reputation: 14383

Clone isn't cloning select values

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?

Upvotes: 84

Views: 46487

Answers (12)

David Antelo
David Antelo

Reputation: 533

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!

Upvotes: 0

Hilary Okoro
Hilary Okoro

Reputation: 341

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

});

Upvotes: 1

chief7
chief7

Reputation: 14383

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());
});

Upvotes: 83

Galley
Galley

Reputation: 633

@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);
});

Upvotes: -1

Adam Pietrasiak
Adam Pietrasiak

Reputation: 13184

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(){

Upvotes: 8

Alex
Alex

Reputation: 1073

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');

Upvotes: 0

SkarXa
SkarXa

Reputation: 1194

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());

Upvotes: 0

Collin Anderson
Collin Anderson

Reputation: 15434

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

Upvotes: 5

Novalis
Novalis

Reputation: 521

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);

Upvotes: 42

jamesvl
jamesvl

Reputation: 1659

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.

Upvotes: 2

mpen
mpen

Reputation: 282825

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.

Upvotes: 10

user123444555621
user123444555621

Reputation: 152956

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)

Upvotes: 2

Related Questions