Socrates
Socrates

Reputation: 189

jQuery unwanted sorting of array output in Chrome and IE (FF working fine)

I have a jquery script which converts the options of a dropdown select box to ul list items using an array. Each option in the dropdown has a numerical option value, e.g.

<option value="12">Size S</option>
<option value="34">Size M</option>
<option value="7">Size L</option>

which get converted to a list like

<ul>
<li class="opt_12">Size S</li>
<li class="opt_34">Size M</li>
<li class="opt_7">Size L</li>
</ul>

In Firefox everything works as expected and the list items appear in the same order as the dropdown options. However IE and Chrome seem to sort the option values of the array automatically by option value in descending order. So instead of size order S,M,L,XL like in the dropdown, in Chrome & IE I get a list with something like XL,M,S,L.

I noticed one thing: When I use var options = Array; to construct the array, Firefox displays the list elements in the right order and Chrome and IE the wrong one. When I use var options = [];, all three tested browsers display the list in the wrong order.

Below is the relevant code of the script which I use to transform the dropdown into list items:

(function($) {
    $.fn.visualAttribute = function(options) {
        var defaults = {
            useTitle: false,
            attrClicked : function(data) {
                return true;
            },
            attrUpdated : function(data) {
            }
        };
        var settings = $.extend(defaults, options);

        //loop all attributes
        var selectbox_counter = 0;
        return this.each(function() {
            //use counter for a unique class for each wrapper
            selectbox_counter++;

            //store reference to attribute selectbox
            var selectbox = $(this);

            //hide the default dropdown (but keep it in dom for posting the required values)
            $(this).css('position', 'absolute').css('left', '-100000px').show();

            //insert wrapper for options
            var wrapper = $('<ul />')
                    .attr("class", "la_wrapper")
                    .attr("id", "la_wrapper_" + selectbox_counter)
                    .appendTo($(this).parent());

            $(this).parent().append('<div style="clear:both"></div>');

            if (selectbox.attr("id") != "") {
                wrapper.attr("rel", selectbox.attr("id"));
            }

            //store all values of the dropdown in an array
            var options = [];
            var option_counter = 0;
            var description = '';

            selectbox.children('option').each(function() {
                option_counter++;

                if (option_counter == 1) {
                    //first option contains the description, e.g. 'please select size'
                    description = $(this).text();
                }

                //only use option if has a value
                var value = $(this).val();
                if (value != '') {
                    options[value] = ({value : value, text : $(this).text()});
                }
            });

            //loop all stored options and create custom html
            if (options.length) {
                for (var index in options) {
                    if (!isNaN(index)) {
                        var value = index;
                        var text = options[index].text;
                        if (!settings.useTitle) {
                            description = '';
                        }
                        wrapper.append('<li title="' + description + '" class="opt_' + value + '"><a href="#' + value + '">' + text + '</a></li>');
                    }
                }
            }

            //set custom options to same value as current selectbox value (only needed in rare cases)
            var current_value = selectbox.val();
            if (current_value > 0) {
                $("#la_wrapper_" + selectbox_counter + ' li.opt_' + current_value + ' a').addClass('selected');
            }

            //event handler for catching a clicked attribute
            $("#la_wrapper_" + selectbox_counter + ' li a').click(function() {

                var value = $(this).attr("href").split('#')[1];

                //use callback
                if (!settings.attrClicked(options[value])) {
                    return false;
                }

                //set value and class
                selectbox.val(value);
                $("#la_wrapper_" + selectbox_counter + ' .selected').removeClass('selected');
                $(this).addClass('selected');

                //use callback
                settings.attrUpdated(options[value]);

                return false;
            });
        });
    };
    })(jQuery);

How can I prevent IE and Chrome from "autosorting" the array and keep/transfer the original order of the dropdown options in the resulting list?

Upvotes: 2

Views: 1880

Answers (3)

nbrooks
nbrooks

Reputation: 18233

If the order is important, don't use the value of the option as the array key, just do an append (P.S. use [], not Array—that isn't doing what you think it's doing)

var options = [];
var option_counter = 0;
var description = '';

selectbox.children('option').each(function() {
    option_counter++;

    //only use option if has a value
    var value = $(this).val();
    if ( value ) {
        options.push({value : value, text : $(this).text()});
    }
});

Then, when looping, get rid of the if (options.length) altogether, and use a regular for loop:

for (var i=0; i<options.length; i++) {
    var value = options[i].value;
    var text = options[i].text;
    if (!settings.useTitle) {
        description = '';
    }
    wrapper.append('<li title="' + description +
                   '" class="opt_' + value +
                   '"><a href="#' + value + '">' + text +
                   '</a></li>');
}

Upvotes: 2

jfriend00
jfriend00

Reputation: 707746

When you iterate an array with this:

for (var index in options) 

you are not getting a guaranteed order as you are just iterating properties of an object (not array indexes) which by specification have no guaranteed order. You should be using this:

for (var i = 0; i < options.length; i++)

to iterate an array in the array order. The first form iterates properties of an object in no particular order. The latter form iterates array elements in array index order (which gives you the actual array elements in their array order).

In addition, you declare an array with this:

var options = [];

or

var options = new Array();

You should not be using:

var options = Array;

Upvotes: 1

Boris Zbarsky
Boris Zbarsky

Reputation: 35074

Your problem is that you're using for...in to enumerate the properties.

Enumeration order for properties of an object (including an array) is undefined in ECMAScript so far. There is a proposal for ECMAScript 6 to define the order as follows, more or less: "first all properties whose names look like integers, in numeric order, then all other properties in the order they were added". This is the behavior Chrome and IE implement. The Firefox behavior is somewhat complicated and depends on whether your object is an array or not and if it's an array on which exact property names got used and in what order and what the length of the array is and a few other things.

In any case, if you want to enumerate the values in order, just store them in an array using array.push() and then enumerate the array's indices. So replace options[value] = ... with options.push(...) and replace for (var index in options) with for (var index = 0; index < options.length; ++index) and get the value from options[index].value just like you get the text already.

Upvotes: 0

Related Questions