What is the most efficient way to sort an Html Select's Options by value, while preserving the currently selected item?

Viewed 98999

I have jQuery but I'm not sure if it has any built-in sorting helpers. I could make a 2d array of each item's text, value, and selected properties, but I don't think that javascript's built in Array.sort() would work correctly.

12 Answers

Extract options into a temporary array, sort, then rebuild the list:

var my_options = $("#my_select option");
var selected = $("#my_select").val();

my_options.sort(function(a,b) {
    if (a.text > b.text) return 1;
    if (a.text < b.text) return -1;
    return 0
})

$("#my_select").empty().append( my_options );
$("#my_select").val(selected);

Mozilla's sort documentation (specifically the compareFunction) and Wikipedia's Sorting Algorithm page are relevant.

If you want to make the sort case insensitive, replace text with text.toLowerCase()

The sort function shown above illustrates how to sort. Sorting non-english languages accurately can be complex (see the unicode collation algorithm). Using localeCompare in the sort function is a good solution, eg:

my_options.sort(function(a,b) {
    return a.text.localeCompare(b.text);
});

I've just wrapped Mark's idea in a jquery function

$('#your_select_box').sort_select_box();

JQuery function:

$.fn.sort_select_box = function(){
    var my_options = $("#" + this.attr('id') + ' option');
    my_options.sort(function(a,b) {
        if (a.text > b.text) return 1;
        else if (a.text < b.text) return -1;
        else return 0
    })
   return my_options;
}

Well, in IE6 it seems to sort on the nested array's [0] item:

function sortSelect(selectToSort) {
    var arrOptions = [];

    for (var i = 0; i < selectToSort.options.length; i++)  {
        arrOptions[i] = [];
        arrOptions[i][0] = selectToSort.options[i].value;
        arrOptions[i][1] = selectToSort.options[i].text;
        arrOptions[i][2] = selectToSort.options[i].selected;
    }

    arrOptions.sort();

    for (var i = 0; i < selectToSort.options.length; i++)  {
        selectToSort.options[i].value = arrOptions[i][0];
        selectToSort.options[i].text = arrOptions[i][1];
        selectToSort.options[i].selected = arrOptions[i][2];
    }
}

I'll see if this works in other browsers...

Edit: it works in Firefox too, woo hoo!

Is there an easier way than this though? is there some method built into javascript or jQuery that sorts selects that I am missing, or is this the best way?

There's a closed jQuery ticket for a sort that should work, but just wasn't included in the core.

jQuery.fn.sort = function() {
  return this.pushStack( [].sort.apply( this, arguments ), []);
};

Referenced from a Google Groups thread, I think you just pass in a function that is used to sort, like so

function sortSelect(selectToSort) {
    jQuery(selectToSort.options).sort(function(a,b){ 
        return a.value > b.value ? 1 : -1; 
    });
}

Hope it helps!

Array.sort() defaults to converting each element to a string, and comparing those values. So ["value", "text", "selected"] gets sorted as "value, text, selected". Which will probably work fine, most of the time.

If you do want to sort on value alone, or interpret value as a number, then you can pass a comparison function into sort():

arrOptions.sort(function(a,b) { return new Number(a[0]) - new Number(b[0]); });

A bit late but for what it's worth I've implemented a more complex function you can include generically. It has a few options for varied output. It can also recurse into <OPTGROUP> tags based on their label.

$.fn.sortSelect = function(options){

    const OPTIONS_DEFAULT = {
        recursive:      true,   // Recurse into <optgroup>
        reverse:        false,  // Reverse order
        useValues:      false,  // Use values instead of text for <option> (<optgruop> is always label based)
        blankFirst:     true,   // Force placeholder <option> with empty value first, ignores reverse
    }

    if (typeof options != "object" || null === options) {
        options = OPTIONS_DEFAULT;
    }

    var sortOptions = function($root, $node, options){

        if ($node.length != 1) {
            return false;
        }

        if ($node[0].tagName != "SELECT" && $node[0].tagName != "OPTGROUP") {
            return false;
        }

        if (options.recursive) {
            $node.children('optgroup').each(function(k, v){
                return sortOptions($root, $(v), options);
            });
        }

        var $options        = $node.children('option, optgroup');
        var $optionsSorted  = $options.sort(function(a, b){

            if (options.blankFirst) {
                if (a.tagName == "OPTION" && a.value == "") {
                    return -1;
                }

                if (b.tagName == "OPTION" && b.value == "") {
                    return 1;
                }
            }

            var textA = (a.tagName == "OPTION" ? (options.useValues ? a.value : a.text) : a.label);
            var textB = (b.tagName == "OPTION" ? (options.useValues ? a.value : b.text) : b.label);

            if (textA > textB) {
                return options.reverse ? -1 : 1;
            }

            if (textA < textB) {
                return options.reverse ? 1 : -1;
            }

            return 0;

        });

        $options.remove();
        $optionsSorted.appendTo($node);

        return true;

    };

    var selected = $(this).val();
    var sorted = sortOptions($(this), $(this), {...OPTIONS_DEFAULT, ...options});
    $(this).val(selected);

    return sorted;

};

You can then call the sortSelect() function on any <SELECT> tag, or just a single <OPTGROUP> to only sort a group's options.

Example:

$('select').sortSelect();

Reverse order using the "reverse" option:

$('select').sortSelect({
    reverse: true
});

You could apply this to all selects automatically, perhaps only if they include an important class (e.g. "js-sort") with this:

$('select.js-sort').each(function(k, v){
    $(v).sortSelect();
});

Seems jquery still is not particularly helpful enough for sorting options in a html select element. Here is some plain-plain javascript code for sorting options:

function sortOptionsByText(a,b) {
  // I keep an empty value option on top, b.value comparison to 0 might not be necessary if empty value is always on top...
  if (a.value.length==0 || (b.value.length>0 && a.text <= b.text)) return -1; // no sort: a, b
  return 1; // sort switches places: b, a
}
function sortOptionsByValue(a,b) {
  if (a.value <= b.value) return -1; // a, b
  return 1; // b, a
}
function clearChildren(elem) {
  if (elem) {
    while (elem.firstChild) {
      elem.removeChild(elem.firstChild);
    }
  }
}
function sortSelectElem(sel,byText) {
  const val=sel.value;
  const tmp=[...sel.options];
  tmp.sort(byText?sortOptionsByText:sortOptionsByValue);
  clearChildren(sel);
  sel.append(...tmp);
  sel.value=val;
}
RACE: <select id="list" size="6">
  <option value="">--PICK ONE--</option>
  <option value="1">HUMANOID</option>
  <option value="2">AMPHIBIAN</option>
  <option value="3">REPTILE</option>
  <option value="4">INSECTOID</option>
</select><br>
<button type="button" onclick="sortSelectElem(document.getElementById('list'));">SORT LIST BY VALUE</button><br>
<button type="button" onclick="sortSelectElem(document.getElementById('list'),true);">SORT LIST BY TEXT</button>

With jquery this worked for me in Chrome in order to sort a select made from database unsorted elements.

$(document).ready(function(){
    
    var $list = $("#my_select");
    var selected = $("#my_select").val(); //save selected value
    
    $list.children().detach().sort(function(a, b) {
        return $(a).text().localeCompare($(b).text());
      }).appendTo($list);  //do the sorting locale for latin chars
    
    $("#my_select").val(selected); //select previous selected value

});
Related