Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fix for #13514.Set selectedIdx to 0 if a 'wrong' value is set as dropdown-list value #1191

Closed
wants to merge 12 commits into from

4 participants

@ruado1987

This commit fixes the issue described in the bug ticket when a dropdown-list is set to a value which doesnt match with any of its options' value.

src/attributes.js
@@ -280,6 +280,12 @@ jQuery.extend({
if ( !values.length ) {
elem.selectedIndex = -1;
+ } else {
+ // IE 9 doesn't select a dropdown-list's first option
@staabm
staabm added a note

needs to be Support: IE9. is IE7/8/10 also affected?

Well, I haven't had a chance to check the issue on these ie versions. Anyway, I will try to check it out today.

BTW, you also meant the comment needs to be changed to Suport: IE9 ..., right?

@staabm
staabm added a note

Yes

@staabm: this issue doesnt affect IE7/8, I havent had a chance to test it on IE10 though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ruado1987

@dmethvin @gibson042 : Please review this fix and give away your advice. Thanks.

src/attributes.js
@@ -280,6 +280,12 @@ jQuery.extend({
if ( !values.length ) {
elem.selectedIndex = -1;
+ } else {
+ // IE 9 doesn't select a dropdown-list's first option
+ // when value doesnt match with one of its options' value
+ if ( elem.selectedIndex < 0 ) {
+ elem.selectedIndex = 0;
@gibson042 Collaborator

This will not work correctly when elem is a select-multiple. Also, I'm concerned about this edge case fix requiring too much additional code, but that could be mitigated somewhat by templating off the get method. I'd try starting with

var optionSet,
    values = jQuery.makeArray( value ),
    options = elem.options,
    index = elem.selectedIndex,
    one = elem.type === "select-one" || index < 0;

and proceed from there, making sure to add thorough unit tests for both select-one and select-multiple.

This issue is not occurring to select-multiple. Do you mean that the code has to be changed so that calling val() with a 'wrong' option value(s) on select-multiple will also select the first option? Currently no option will be selected when passing a 'wrong' value to val() in case of select-multiple.

@gibson042 Collaborator

Observe the change in behavior from introducing this code: http://jsfiddle.net/wuM8f/

My latest changes: http://jsfiddle.net/aVjyH/1/ to preserve the current behavior when elem is a select-multiple. The new code changes elem.selectedIndex only if elem.type is "select-multiple" and elem.selectedIndex is less than 0. The approach used in get doesn't work well when applying to set because one = elem.type === "select-one" || index < 0 can be true even when elem is a select-multiple and none of its options is selected.

@gibson042 Collaborator

That's right of course; we slip it in there because a select-multiple with nothing selected can follow the single-get null case. Regardless, http://jsfiddle.net/aVjyH/1/ is too much code, and will compress poorly. Seriously, follow along these lines:

var optionSet,
    values = jQuery.makeArray( value ),
    options = elem.options,
    index = elem.selectedIndex,
    // Keep as a variable only if overall gzip size is smaller
    // than using elem.type !== "select-one" directly in an if condition
    one = elem.type === "select-one";

and try using the loop body instead of selectedIndex to track if post-iteration fixup is necessary.

Following your advice, I've come up with this change. However, I don't use the index variable because the variable is not useful in determining if post-iteration fixup is necessary. I also use elem.type === "select-one" directly in the if condition since it results in a bit smaller file than introducing the one variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/attributes.js
((13 lines not shown))
});
if ( !values.length ) {
elem.selectedIndex = -1;
+ } else {
+ // fix IE9 bug when non-matching value is set
+ if ( elem.type === "select-one" && !optionSet ) {
@gibson042 Collaborator

What file are you using to verify size changes? Our standard metric is jquery.min.js.gz, and you'll be able to see it directly from grunt if you rebase onto 91d5764. I'm certain, for instance, that you'll achieve better compression by declaring options third rather than first and skipping the initial assignment to optionSet. There are even greater savings to be had by abandoning jQuery.each in favor of native iteration.

Also, looking at this with fresh eyes, I'm pretty sure you can combine the two tests here and just set elem.selectedIndex = -1 whenever optionSet is falsy.

I had verified size changes using the generated minified file before you pushed the latest changes regarding to the grunt-compare-size. Just pulled the latest changes and started using this plugin for my verification. You are also right when saying native iteration will result in better performance than jQuery.each. Actually the former was what I wanted to use, but I thought it would be better to follow the old code to use jQuery's own utilities. Anyway, I will change the code to use native iteration. As for the last comment, I don't really get what you mean. Are you saying that it is better to remove elem.type === "select-one" and keep only !optionSet in the condition tests?

@gibson042 Collaborator

Yes; assuming the following works (and if it doesn't then $selectMultiple.val([]) etc. are likely broken), I would consider it better than the if/else:

if ( !optionSet ) {
    elem.selectedIndex = -1;
}

The remainder is then mostly optimizing for compression; e.g.:

var optionSet, option,
    options = elem.options,
    i = options.length,
    values = jQuery.makeArray( value );

while ( i-- ) {
    option = options[ i ];
    if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
        optionSet = true;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ruado1987

@gibson042: Moving the assignment of i to the for loop results in a smaller gz file (29476 vs. 29480).

@ruado1987

@gibson042: This issue wont be fixed if the code is just elem.selectedIndex = -1. It is still required to set elem.selectedIndex to 0 if the element is a select one.

@gibson042
Collaborator

Really? I observe consistent behavior on all browsers: http://jsbin.com/ikovev/1/

At this point, it's a question of what consistency is most appropriate. Nonexistent values are not documented as valid input, so this is an opportunity to define behavior. Implementing "value not found" as selectedIndex = -1 (and subsequent null .val()) seems to me like an improvement over selectedIndex = 0 (and subsequent non-null .val()), but this is now sufficiently vague that I'll put out the @dmethvin​-shaped signal light and we'll try to establish a consensus.

@gibson042
Collaborator

HTML5 language:

On setting, the selectedIndex attribute must set the selectedness of all the option elements in the list of options to false, and then the option element in the list of options whose index is the given new value, if any, must have its selectedness set to true.
This can result in no element having a selectedness set to true even in the case of the select element having no multiple attribute and a display size of 1.

@ruado1987

@gibson042 By "won't be fixed", I mean IE9's behavior still remains the same as described in ticket #13514. According to the HTML5 spec on this regard, I think IE9 behavior is the most appropriate though. Anyway, let's wait for @dmethvin decision.

@ruado1987

@gibson042 Is there any update on this issue? It has been idle for a week.

@gibson042
Collaborator

@dmethvin ping.

I strongly prefer the first, but as stated above, this is undefined behavior for jQuery right now:

  1. "Value not found" sets selectedIndex = -1. Subsequent .val() returns null.
  2. "Value not found" sets selectedIndex = 0 (more technically, to the lowest selectedIndex identifying a non-disabled <option>). Subsequent .val() returns the corresponding value.

(HTML5 link)

@dmethvin
Owner

I remember looking at this and my head started hurting. Same effect this week. Us defining behavior is always dangerous.

If a select has no initial option selected, that's undefined behavior according to HTML4 but nearly all browsers handle that case by choosing the first option I think. I couldn't see the similar language in the HTML5 case.

So option 1 provides functionality that isn't possible through the normal declarative markup. How cross-browser is it? In HTML5 if no option has its selectness, does that imply the spec is preventing the browser from displaying one as a default? If so this seems to contradict the HTML parsing scenario.

@gibson042
Collaborator

You're right that HTML4 leaves it undefined, but HTML5 specifies option 1, at least programatically:

Note: [setting selectedIndex] can result in no element having a selectedness set to true even in the case of the select element having no multiple attribute and a display size of 1.

It's clear that setting selectedIndex to -1 should result in all options having false selectedness, and that getting selectedIndex in such a state should return -1.

The display is unspecified, but all browsers that I tested blank out the select— including Android, iOS, and IE6+. We've previously taken a firm stance that rendering issues are not our problem, and I don't see any reason to break that now.

@dmethvin
Owner

Okay, I'm good with option 1 then. Let's do it.

@ruado1987

@gibson042 @dmethvin : Done. Please review the latest changes.

@gibson042 gibson042 closed this pull request from a commit
@ruado1987 ruado1987 Fix #13514: Set selectedIndex to -1 when non-matching value is set on…
… a select. Close gh-1191.

(cherry picked from commit 48d71d0)
c9ca9bf
@gibson042 gibson042 closed this in c9ca9bf
@gibson042
Collaborator

Thanks very much!

@mescoda mescoda referenced this pull request from a commit in mescoda/jquery
@ruado1987 ruado1987 Fix #13514: Set selectedIndex to -1 when non-matching value is set on…
… a select. Close gh-1191.
014aa50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 3, 2013
  1. @ruado1987
Commits on Mar 4, 2013
  1. @ruado1987
Commits on Mar 7, 2013
  1. @ruado1987
Commits on Mar 9, 2013
  1. @ruado1987
Commits on Mar 12, 2013
  1. @ruado1987
Commits on Mar 13, 2013
  1. @ruado1987

    force IE9 to behave like other browsers when a non-matching value is …

    ruado1987 authored
    …set on a select-one element
  2. @ruado1987

    force IE9 to behave like other browsers when a non-matching value is …

    ruado1987 authored
    …set on a select-one element
  3. @ruado1987
  4. @ruado1987

    force IE9 to behave like other browsers when a non-matching value is …

    ruado1987 authored
    …set on a select-one element
  5. @ruado1987
  6. @ruado1987
Commits on Mar 22, 2013
  1. @ruado1987

    Set selectedIdx to -1 when non-matching value is set on select-one an…

    ruado1987 authored
    …d select-multiple elements
This page is out of date. Refresh to see the latest.
Showing with 29 additions and 7 deletions.
  1. +13 −7 src/attributes.js
  2. +16 −0 test/unit/attributes.js
View
20 src/attributes.js
@@ -272,13 +272,19 @@ jQuery.extend({
},
set: function( elem, value ) {
- var values = jQuery.makeArray( value );
-
- jQuery(elem).find("option").each(function() {
- this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
- });
-
- if ( !values.length ) {
+ var optionSet, option, i,
+ options = elem.options,
+ values = jQuery.makeArray( value );
+
+ for ( i = options.length; i--; ) {
+ option = options[ i ];
+ if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
+ optionSet = true;
+ }
+ }
+
+ // force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
elem.selectedIndex = -1;
}
return values;
View
16 test/unit/attributes.js
@@ -846,6 +846,22 @@ test( "val()", function() {
equal( jQuery("<option/>").val("test").attr("value"), "test", "Setting value sets the value attribute" );
});
+test("val() with non-matching values on dropdown list", function() {
+ expect( 3 );
+
+ jQuery("#select5").val( "" );
+ equal( jQuery("#select5").val(), null, "Non-matching set on select-one" );
+
+ var select6 = jQuery("<select multiple id=\"select6\"><option value=\"1\">A</option><option value=\"2\">B</option></select>").appendTo("#form");
+ jQuery(select6).val( "nothing" );
+ equal( jQuery(select6).val(), null, "Non-matching set (single value) on select-multiple" );
+
+ jQuery(select6).val( ["nothing1", "nothing2"] );
+ equal( jQuery(select6).val(), null, "Non-matching set (array of values) on select-multiple" );
+
+ select6.remove();
+});
+
if ( "value" in document.createElement("meter") &&
"value" in document.createElement("progress") ) {
Something went wrong with that request. Please try again.