Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery option appendTo select moves to next option instead of select one

http://jsfiddle.net/j3oh6s3a/

For some reason appending options to a select tag doesn't select the selected='selected' attribute option, instead selects the next option in the list.

Please see the above jfiddle.

<select id="category">
    <option value='1'>Categroy 1</option>
    <option value='2'>Categroy 2</option>
    <option value='3'>Categroy 3</option>
</select>
<select id="sub-category">
    <option value='1' data-parentid='1'>Car1</option>
    <option value='2' data-parentid='1'>Car2</option>
    <option selected='selected' value='3' data-parentid='1'>Car3</option>
    <option value='4' data-parentid='1'>Car4</option>
    <option value='5' data-parentid='1'>Car5</option>
    <option value='6' data-parentid='2'>Car6</option>
    <option value='7' data-parentid='2'>Car7</option>
    <option value='8' data-parentid='2'>Car8</option>
    <option value='9' data-parentid='3'>Car9</option>
    <option value='10' data-parentid='3'>Car10</option>
    <option value='11' data-parentid='3'>Car11</option>
    <option value='12' data-parentid='3'>Car12</option>
</select>

$(document).ready(function(){
    var allsuboptions = $('#sub-category option').remove();
    var selectedOptions = allsuboptions.filter(function () {
        return $(this).data('parentid').toString() === $('#category').val().toString();
    });
    selectedOptions.appendTo('#sub-category');
});

In the above example Car3 should be selected, but Car4 is selected after appending options to the select.

like image 275
Srikan Avatar asked Nov 05 '14 04:11

Srikan


3 Answers

This is a tricky (and interesting) question.

If you test the fiddle on different browsers you'll see that the selected value changes: Chrome (Car4), IE (Car3), Firefox (Car5). So I have made a slight change to your fiddle to "prove a theory". You can see the changes on this link: http://jsfiddle.net/j3oh6s3a/1/. I only added a log to the filter loop so I can see the selected element in each iteration:

if ($(this).is(":selected")) { console.log("Selected value = " + $(this).val()) };

Now this is what happens (or at least my theory): Once the selected element is removed from the list each browser will proceed however thinks adequate to determine the selected option. And in this case each browser will proceed in a different way:

  • As the selected option has been removed, Chrome will select automatically (by default) the first option of the remaining in the list (Car4). When this option is sent to the new list, it is automatically selected as it is newer than the previous selected option. The log is: 3, 4.

  • Internet Explorer does nothing, and copies each element the same way they are without caring about if they are selected or not. The original selected value will be the final selected value (Car3). The log is: 3.

  • Firefox will proceed like Chrome, but every time that the selected element is removed from the list, the first option of the remaining ones will be selected. The log is: 3, 4, 5, 6, 7, 8, 9, 10, 11, 12; but as the last option inserted in the list is 5, it will be the selected one.

I will check later to see if I can find any information to source this, but it will have to be tomorrow as it's a bit late here.

like image 112
Alvaro Montoro Avatar answered Nov 04 '22 22:11

Alvaro Montoro


jQuery .remove and .append internally uses .removeChild and .appendChild methods to remove/insert the child elements.

Theory: removeChild/appendChild maintains the attributes but doesn't maintain the element's property (maintaining the selection state)

When you use removeChild and then appendChild to add the options back, the attributes are maintained, but the property of the element are not maintained. You can read more about .prop() vs .attr() here.

In summary, attributes are initial values defined in the HTML that are parsed to set as properties to the Element by the browser, however setting the attributes doesn't guarantee setting the property.

$(function() {
  var categoryDD = document.getElementById('category');
  var removedOptions = remove.call(categoryDD.options);
  add.call(categoryDD, removedOptions);
});

function add(options) { //add all options
  for (var i = 0; i < options.length; i++) {
    this.appendChild(options[i]);
  }
}

function remove() { //removes all options
  var el, returnOpt = [];
  while (this.length) {
    el = this[0];
    returnOpt.push(el.parentNode.removeChild(el));
  }
  return returnOpt;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<select id="category">
  <option value='1'>Categroy 1</option>
  <option value='2' selected="selected">Categroy 2</option>
  <option value='3'>Categroy 3</option>
  <option value='4'>Categroy 4</option>
  <option value='5'>Categroy 5</option>
  <option value='6'>Categroy 6</option>
</select>

Testing Results:

On testing the above snippet, IE 10 and FF yielded me the same result which is selecting the last option from the drop down, however chrome seems to be bugged as it always selected the next option from the original selection. The results from IE 10 and FF made a little sense as to "Not maintaining the state", however Chrome behavior seems like a bug.

Above is my theory based on my test case, however I couldn't find a legit reference that states the same.

Anyways, tryout below solutions for a consistent output.

Solution 1: Remove only options that are NOT equal to parentId.

$('#sub-category option').filter(function () {
    return $(this).data('parentid').toString() !== $('#category').val().toString();
}).remove();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<select id="category">
    <option value='1'>Categroy 1</option>
    <option value='2'>Categroy 2</option>
    <option value='3'>Categroy 3</option>
</select>
<select id="sub-category">
    <option value='1' data-parentid='1'>Car1</option>
    <option value='2' data-parentid='1'>Car2</option>
    <option selected='selected' value='3' data-parentid='1'>Car3</option>
    <option value='4' data-parentid='1'>Car4</option>
    <option value='5' data-parentid='1'>Car5</option>
    <option value='6' data-parentid='2'>Car6</option>
    <option value='7' data-parentid='2'>Car7</option>
    <option value='8' data-parentid='2'>Car8</option>
    <option value='9' data-parentid='3'>Car9</option>
    <option value='10' data-parentid='3'>Car10</option>
    <option value='11' data-parentid='3'>Car11</option>
    <option value='12' data-parentid='3'>Car12</option>
</select>

Solution 2: [based on your original answer] The solution is simple, just get the select value before removing the options and set the selection after using .append.

var $subcategory = $('#sub-category');
var selectedOption = $subcategory.val();
var allsuboptions = $subcategory.find('option').remove();
var selectedOptions = allsuboptions.filter(function() {
  return $(this).data('parentid').toString() === $('#category').val().toString();
});
selectedOptions.appendTo('#sub-category');
$subcategory.val(selectedOption);
<select id="category">
  <option value='1'>Categroy 1</option>
  <option value='2'>Categroy 2</option>
  <option value='3'>Categroy 3</option>
</select>
<select id="sub-category">
  <option value='1' data-parentid='1'>Car1</option>
  <option value='2' data-parentid='1'>Car2</option>
  <option selected='selected' value='3' data-parentid='1'>Car3</option>
  <option value='4' data-parentid='1'>Car4</option>
  <option value='5' data-parentid='1'>Car5</option>
  <option value='6' data-parentid='2'>Car6</option>
  <option value='7' data-parentid='2'>Car7</option>
  <option value='8' data-parentid='2'>Car8</option>
  <option value='9' data-parentid='3'>Car9</option>
  <option value='10' data-parentid='3'>Car10</option>
  <option value='11' data-parentid='3'>Car11</option>
  <option value='12' data-parentid='3'>Car12</option>
</select>
like image 3
Selvakumar Arumugam Avatar answered Nov 04 '22 20:11

Selvakumar Arumugam


What you're not seeing is the difference between the "selected" property and the "selected" attribute. If you put this at the end of your code you can see it:

// Attribute
console.log( $("#sub-category").find("[selected]").val() );
// Property
console.log( $("#sub-category").find(":selected").val() );

Your option with value "3" has the selected attribute, but not the property, it's the opposite for the option with value "4".

Whenever you add options inside a select, you must re-select the desired one:

$("#sub-category").find("[selected]").prop("selected", true);
like image 2
Romain Avatar answered Nov 04 '22 20:11

Romain