5

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.

Srikan
  • 2,161
  • 5
  • 21
  • 36
  • I just checked the [fiddle](http://jsfiddle.net/j3oh6s3a/) you've shared and it selects `Car3` only. I didn't understand your problem may be. – Vivek Pradhan Nov 05 '14 at 04:43
  • The funny thing is that it shows Car4, but if you inspect element and look at the code, Car3 is selected: `` – Alvaro Montoro Nov 05 '14 at 04:47
  • Did you run the code. It is selecting Car4 ? I just verified it – Srikan Nov 05 '14 at 04:47
  • I run the code in the jsfiddle that you posted; then, without making any change, right clicked on the dropdown and click on "inspect element". Car4 is the option shown, but according to the code Car3 is the option selected – Alvaro Montoro Nov 05 '14 at 04:52
  • @AlvaroMontoro I noticed that as well, strange. – EternalHour Nov 05 '14 at 04:53
  • 2
    Who ever are looking at this answer. There is some pretty good explanation given below by @salman. Please check it out – Srikan Nov 10 '14 at 01:29

5 Answers5

3

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.

Alvaro Montoro
  • 28,081
  • 7
  • 57
  • 86
  • That's pretty good theory. If all experiments conclude these results, then we don't have any other option, but to accept this theory. For now before removing the options from the select, I am taking a back up of the selected value and setting it again. This is how I am solving the issue in my project – Srikan Nov 06 '14 at 03:48
  • I cannot find anything to prove this though. I'll try to contact them to see if they can throw some light. – Alvaro Montoro Nov 06 '14 at 12:24
  • @Srikan that seems to be the best cross-browser solution at this point. – Alvaro Montoro Nov 06 '14 at 12:25
  • 1
    Who ever are looking at this answer. There is some pretty good explanation given below by @salman. Please check it out – Srikan Nov 10 '14 at 01:28
3

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>
Community
  • 1
  • 1
Selvakumar Arumugam
  • 79,297
  • 15
  • 120
  • 134
2

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);
Romain
  • 573
  • 3
  • 8
  • Adding this `$("#sub-category").find("[selected]").prop("selected", true);` line of code worked in all browsers for me. – GoreDefex Nov 08 '14 at 18:14
  • This is interesting. The attribute is copied but the property is "lost" when removing/appending? I need to read more about the difference between attribute and properties and how they work – Alvaro Montoro Nov 12 '14 at 12:52
2

Firefox selects the last element. This behavior is probably correct and explainable. In order to understand, please keep the following in mind:

  1. jQuery append and remove methods process the elements one by one behind the scene.
  2. The current state of an input element should be retrieved or set using the corresponding property, not attribute.

Expected Behavior (Firefox)

Removing all options from a select element as demonstrated in your example works as follows:

  1. Car1 is removed (Car3 remains selected)
  2. Car2 is removed (Car3 remains selected)
  3. Car3 is removed. Since this is the selected element, removing it will cause the next element to become selected
  4. Car4 is removed. Since this is the selected element, removing it will cause the next element to become selected

This will repeat until all options are moved from DOM to the memory. At this point the options will have the following properties:

// allsuboptions.each(function() { console.log(this.value, this.selected); });

value:  1, selected: false
value:  2, selected: false
value:  3, selected: true
value:  4, selected: true
...
value: 12, selected: true

There are 3 options with selected = true. When you add the options back to the select element, the browser sets the last selected element as the selected one.

Internet Explorer and Chrome

While the options are removed one by one, these two browsers do not update the selected element immediately (they possibly wait for JavaScript execution to finish). This causes the following discrepancies:

  • Internet Explorer does not immediately make the next option selected when currently selected option is removed. Therefore the removed options contain only one selected element.

  • Chrome seems to immediately make the next option selected when currently selected option is removed, but it does that only once. Therefore the removed options contain two selected elements.

In order to prove the point about immediate and deferred updates, here is a Fiddle containing:

  1. The original code
  2. A variation that forces the browser to update the select element each time an option is removed

http://jsfiddle.net/salman/j3oh6s3a/9/

Solution

As suggested in other answers, the correct workaround is to use a variable that references the currently selected option before removing the options. Something like:

var $selectedOption = $("#sub-category option").filter(function() {
    return this.selected;
});

Once the options are re-inserted, you can check if that element was added back and select it again:

if ($selectedOption.parent().length) {
    $selectedOption.prop("selected", true);
}
// or
$("#sub-category option").filter($selectedOption).prop("selected", true);
Salman A
  • 262,204
  • 82
  • 430
  • 521
0

Your script is doing what you created it to do.

The reason car4 is selected, is because

<option selected='selected' value='3' data-parentid='1'>Car3</option>

is initially selected, then you are appending parentid='1' to the value which causes car4 to be the new selection.

What is the purpose of this script?

EternalHour
  • 8,308
  • 6
  • 38
  • 57
  • It's late and I may be missing something. What parentid='1' is OP appending to which value? – Alvaro Montoro Nov 05 '14 at 05:06
  • It's this line here `return $(this).data('parentid').toString() === $('#category').val().toString();`. But I am still not quite sure what it does :) – EternalHour Nov 05 '14 at 05:12
  • The above line validates if the data-parentid of the options is matching the category value. Those options will be filtered out. – Srikan Nov 06 '14 at 03:29