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

JavascriptJqueryArraysSortingHtml Select

Javascript Problem Overview


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.

Javascript Solutions


Solution 1 - Javascript

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][1] (specifically the compareFunction) and [Wikipedia's Sorting Algorithm page][2] are relevant.

[1]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort "Mozilla's sort documentation" [2]: http://en.wikipedia.org/wiki/Sorting_algorithm

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);
});

Solution 2 - Javascript

Modified Tom's answer above slightly so that it actually modifies the contents of the select box to be sorted, rather than just returning the sorted elements.

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

jQuery function:

$.fn.sort_select_box = function(){
    // Get options from select box
    var my_options = $("#" + this.attr('id') + ' option');
    // sort alphabetically
    my_options.sort(function(a,b) {
        if (a.text > b.text) return 1;
        else if (a.text < b.text) return -1;
        else return 0
    })
   //replace with sorted my_options;
   $(this).empty().append( my_options );

   // clearing any selections
   $("#"+this.attr('id')+" option").attr('selected', false);
}

Solution 3 - Javascript

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;
}

Solution 4 - Javascript

The solution I mentioned in my comment to @Juan Perez

$.fn.sortOptions = function(){
	$(this).each(function(){
		var op = $(this).children("option");
		op.sort(function(a, b) {
			return a.text > b.text ? 1 : -1;
		})
		return $(this).empty().append(op);
	});
}

Usage:

$("select").sortOptions();

This can still be improved on, but I didn't need to add any more bells or whistles :)

Solution 5 - Javascript

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?

Solution 6 - Javascript

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!

Solution 7 - Javascript

This is a better solution. Declare a global function to JQuery

$.fn.sortSelect = function() {
    var op = this.children("option");
    op.sort(function(a, b) {
        return a.text > b.text ? 1 : -1;
    })
    return this.empty().append(op);
}

And call the function from the code.

$("#my_select").sortSelect();

Solution 8 - Javascript

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]); });

Solution 9 - Javascript

Remember: if you want to use context selector, just concatenate the ID will not work

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

// Usando:
$("select#ProdutoFornecedorId", $($context)).sort_select_box();

Solution 10 - Javascript

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();
});

Solution 11 - Javascript

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>

Solution 12 - Javascript

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

});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontravisView Question on Stackoverflow
Solution 1 - JavascriptMarkView Answer on Stackoverflow
Solution 2 - JavascriptJaredCView Answer on Stackoverflow
Solution 3 - JavascriptTom MaeckelbergheView Answer on Stackoverflow
Solution 4 - JavascriptTom PietrosantiView Answer on Stackoverflow
Solution 5 - JavascripttravisView Answer on Stackoverflow
Solution 6 - JavascriptbdukesView Answer on Stackoverflow
Solution 7 - JavascriptJuan PerezView Answer on Stackoverflow
Solution 8 - JavascriptShog9View Answer on Stackoverflow
Solution 9 - JavascriptJoãoView Answer on Stackoverflow
Solution 10 - JavascriptAdambeanView Answer on Stackoverflow
Solution 11 - JavascriptCARLOS AGUIRREView Answer on Stackoverflow
Solution 12 - JavascriptlisandroView Answer on Stackoverflow