6

I have hit a weird case where, to the best of my knowledge, two things that should act the same behave quite differently.

If I have two select menus on a page, one static menu hardcoded in the HTML and one appended to the body at runtime with JQuery. I then disable the first option in both select menus. When displayed both menus have their first options disabled as expected, however the dynamically appended menu has automatically set the value to the second option while the static menu still has the first selected.

http://jsfiddle.net/hm3xgkLg/1/

HTML:

<select class="dropMenu">
  <option value="1">First</option>
  <option value="2">Second</option>
  <option value="3">Third</option>
  <option value="4">fourth</option>
</select>

Javascript:

var arr = [
  {val : 1, text: 'One'},
  {val : 2, text: 'Two'},
  {val : 3, text: 'Three'},
  {val : 4, text: 'Four'}
];

var sel = $('<select class="dropMenu">').appendTo('body');
$(arr).each(function() {
   sel.append($("<option>").attr('value',this.val).text(this.text));
 });
$('.dropMenu option:nth-child(1)').attr('disabled', 'disabled');

Why are these two seemingly identical select menus behaving differently? I would like both to act like the static menu (keep 1st value selected) is that possible?

It should also be noted I tried wrapping the disable function in $(document).ready to rule out an issue of the menu not being rendered yet but there was no change. I also tried having two distinct classes with two separate calls to disable the options to make sure its wasn't clashing somehow with no change.

DasBeasto
  • 2,082
  • 5
  • 25
  • 65
  • The approach in the accepted answer here seems to work also http://stackoverflow.com/questions/14227611/jquery-force-display-of-modified-dom but I don't fully understand why. http://jsfiddle.net/hm3xgkLg/4/ – heisenberg Jul 22 '15 at 14:41
  • try to set your `$('.dropMenu option:nth-child(1)').attr('disabled', 'disabled');` in setInterval – Bhavin Solanki Jul 22 '15 at 14:49

5 Answers5

6

Why are these two seemingly identical select menus behaving differently

This is because the first select was already part of the document and was parsed. As per the spec here:

The initial state has the first option selected..

and hence the first option was pre-selected. Later on when you disable one option, it doesn't change the selection.

Example 1: You can see that the first select has disabled attribute already applied to the first option in markup. The second option gets pre-selected, as compared to the second select.

<select class="dropMenu">
    <option value="1" disabled>First</option>
    <option value="2">Second</option>
    <option value="3">Third</option>
    <option value="4">fourth</option>
</select>
<select class="dropMenu">
    <option value="1">First</option>
    <option value="2">Second</option>
    <option value="3">Third</option>
    <option value="4">fourth</option>
</select>

In your case of dynamically adding a select using Javascript, the second select was created and added to the body immediately. When this became the part of DOM, there are no options and hence no pre-selection possible. Later when you append options to it, and then disable one of the options, it seems that it gets executed too fast and by the time it is loaded the first option is already disabled and the second one gets pre-selected.

Example 2: Do NOT append the select immediately to the body. Append it after you have the options populated. This time the first option will be pre-selected despite you changing the disabled attribute later on.

var arr = [
  {val : 1, text: 'One'},
  {val : 2, text: 'Two'},
  {val : 3, text: 'Three'},
  {val : 4, text: 'Four'}
];

var sel = $('<select class="dropMenu">');
$(arr).each(function () {
    sel.append($("<option>").attr('value', this.val).text(this.text));
});

sel.appendTo('body'); /* <--- append here */

$('.dropMenu option:nth-child(1)').prop('disabled', true);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="dropMenu">
    <option value="1">First</option>
    <option value="2">Second</option>
    <option value="3">Third</option>
    <option value="4">fourth</option>
</select>

Example 3: Add a delay before you disable an option. This will give you the intended behaviour of getting the first option pre-selected because it will have enough time.

var arr = [
  {val : 1, text: 'One'},
  {val : 2, text: 'Two'},
  {val : 3, text: 'Three'},
  {val : 4, text: 'Four'}
];

var sel = $('<select class="dropMenu">').appendTo('body');
$(arr).each(function () {
    sel.append($("<option>").attr('value', this.val).text(this.text));
});

setTimeout(disable, 1000); /* <--- give some delay here */

function disable() {
    $('.dropMenu option:nth-child(1)').prop('disabled', true);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="dropMenu">
    <option value="1">First</option>
    <option value="2">Second</option>
    <option value="3">Third</option>
    <option value="4">fourth</option>
</select>

Example 4: You can also select one option explicitly before disabling them.

var arr = [
  {val : 1, text: 'One'},
  {val : 2, text: 'Two'},
  {val : 3, text: 'Three'},
  {val : 4, text: 'Four'}
];

var sel = $('<select class="dropMenu">').appendTo('body');
$(arr).each(function () {
    sel.append($("<option>").attr('value', this.val).text(this.text));
});

$('.dropMenu option:nth-child(1)').prop('selected', true); /* <-- select explicitly */
$('.dropMenu option:nth-child(1)').prop('disabled', true);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="dropMenu">
    <option value="1">First</option>
    <option value="2">Second</option>
    <option value="3">Third</option>
    <option value="4">fourth</option>
</select>
Abhitalks
  • 27,721
  • 5
  • 58
  • 81
1

try to set your $('.dropMenu option:nth-child(1)').attr('disabled', 'disabled'); in setInterval, because you are creating dynamic dropdown so it will take some time to execute and create meanwhile you disable dropdown element jquery is going to execute. so that issue comes.

Checkout Code Snippet

var arr = [
  {val : 1, text: 'One'},
  {val : 2, text: 'Two'},
  {val : 3, text: 'Three'},
  {val : 4, text: 'Four'}
];

var sel = $('<select class="dropMenu">').appendTo('body');
$(arr).each(function() {
   sel.append($("<option>").attr('value',this.val).text(this.text));
 });


setTimeout(function() {
    $('.dropMenu option:nth-child(1)').attr('disabled', 'disabled');
}, 1);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="dropMenu">
  <option value="1">First</option>
  <option value="2">Second</option>
  <option value="3">Third</option>
  <option value="4">fourth</option>
</select>
Bhavin Solanki
  • 4,740
  • 3
  • 26
  • 46
0

Well by default if you have given option selected to the first option of your dynamic dropdown then it would have been done it for you:

Just see this DEMO:

$(arr).each(function(key,value) {
    if(key==0)
         sel.append($("<option>").attr('value',this.val).text(this.text).attr('selected',true));
    else
         sel.append($("<option>").attr('value',this.val).text(this.text));
});

Now why this happens?

For static select, the element is already present in DOM and then you are adding disabled for first option using jquery and by default first option will be kept as selected already.

Now for dynamic select since you are adding it dynamically may be it is not considering to select the first option by default since it is disabled!!

Even I feel this as strange behavior but ya sometimes we need to take care of these things by trail and error.

Guruprasad J Rao
  • 29,410
  • 14
  • 101
  • 200
  • 1
    That's the solution for sure, but still doesn't explain the curious behaviour the OP has found. – lucasnadalutti Jul 22 '15 at 14:35
  • @lucasnadalutti Well I tried to explain to the best of my knowledge and that's in my point of view or I can say that's what I can extend my knowledge to! Always welcome if anyone has better knowledge on this.. :) – Guruprasad J Rao Jul 22 '15 at 14:40
  • 1
    This does work for the given scenario but in reality I'm not using a simple array to populate the dropdown, it is JSON data pulled from the server and added to a Knockout.js template as an array so I can't use this solution in my case. I just simplified it for the sake of making the point as it had the same result. If I can't find a usable solution though I'll accept yours as it does satisfy the given issue. – DasBeasto Jul 22 '15 at 14:44
0

Just manually select the first option in the dinamically added menu:

$('.dropMenu option:nth-child(1)')
      .attr('disabled', 'disabled')
      .attr('selected', 'selected');

jsfiddle here (I borrowed yours :P): https://jsfiddle.net/z0dzk5nL/1/

EDIT A quick update. Here you can find the specification for the "select" element. I've quoted the part that answers your question:

If nodes are inserted or nodes are removed causing the list of options to gain or lose one or more option elements, or if an option element in the list of options asks for a reset, then, if the select element's multiple attribute is absent, the user agent must run the first applicable set of steps from the following list:

If the select element's display size is 1, and no option elements in the select element's list of options have their selectedness set to true Set the selectedness of the first option element in the list of options in tree order that is not disabled, if any, to true.

If two or more option elements in the select element's list of options have their selectedness set to true Set the selectedness of all but the last option element with its selectedness set to true in the list of options in tree order to false.

And here's the fiddle to show that if you pre-select the option when appending it, you can than disable without losing the selection (I used a for loop instead of the "each" func exactly to demonstrate that):

jsfiddle.net/xnvbevhm/

(sorry I can't post more than two links, as I don't have enough reputation :P)

ANVerona
  • 439
  • 3
  • 10
0

As an alternative to setting the first attribute as selected, using setTimeout with a minimal time (i.e. 1) seems to make things behave as expected. I'm still researching the reason for this, though.

setTimeout(function() {
    $('.dropMenu option:nth-child(1)').attr('disabled', 'disabled');
}, 1);

Updated fiddle.

lucasnadalutti
  • 5,818
  • 1
  • 28
  • 48