0

I'm trying to sort an array of Select options, but the options contain a number at the end of the string. So how can I sort not only by the beginning letter, but also by the number?

<select id="DueOn">
    <option value="**SelectValue**">Enter custom value</option>
    <option value="1182" >Business Day - 1</option>
    <option value="1199" >Business Day - 10</option>
    <option value="1801" >Business Day - 12</option>
    <option value="1285" >Business Day - 15</option>
    <option value="1198" >Business Day - 2</option>
    <option value="1232" >Calendar Day - 4</option>
    <option value="1191" >Calendar Day - 5</option>
    <option value="1306" >Calendar Day - 7</option>
    <option value="1782" >Calendar Day - 9</option>
    <option value="1757" >Day of the Week Day - Friday</option>
    <option value="1770" >Day of the Week Day - Wednesday</option>
</select>

I modified this to run and it sorted it alphabetically:

function sortSelect(selElem) {
    var tmpAry = new Array();
    for (var i=0;i<selElem.options.length;i++) {
        tmpAry[i] = new Array();
        tmpAry[i][0] = selElem.options[i].text;
        tmpAry[i][1] = selElem.options[i].value;
    }
    tmpAry.sort();
    console.log(tmpAry )
    while (selElem.options.length > 0) {
        selElem.options[0] = null;
    }
    for (var i=0;i<tmpAry.length;i++) {
        var op = new Option(tmpAry[i][0], tmpAry[i][1]);
        selElem.options[i] = op;
    }
    return;
}
sortSelect(document.querySelector("#DueOn"))

So far the only results I've gotten are Business Day - 1 Business Day - 10 Business Day - 12 Business Day - 15 Business Day - 2...

4 Answers4

0

If you have a consistent delimiter (like " - " in your case), you could split on it so that you're left with a string and a number.

If the strings are the same, then compare by number.

If the strings are not the same, use standard sorting logic.

const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

//Make a re-usable sort function that takes a select element
const sortSelectOptions = (select) => {
  let options = select.options;
  let sortedOptions = [...options].sort((a, b) => {  //Compare A to B
    a = a.text;  //We're only concerned with text, not values
    b = b.text;
    let aParts = a.split(" - ");  //Split the text into two pieces at " - "
    let bParts = b.split(" - ");

    //If the first parts are identical, we'll compare the second parts
    //However we need to know whether they're strings or numbers
    if (aParts[0] === bParts[0] && aParts.length > 1 && bParts.length > 1) {
      a = aParts[1];
      b = bParts[1];
    }

    //Special sort cases
    //NUMBERS
    if (!isNaN(a)) a = Number(a);
    if (!isNaN(b)) b = Number(b);

    //DAYS OF WEEK
    if (daysOfWeek.indexOf(a) >= 0) a = daysOfWeek.indexOf(a);
    if (daysOfWeek.indexOf(b) >= 0) b = daysOfWeek.indexOf(b);

    //basic sort
    if (a < b) return -1
    else if (a > b) return 1
    else return 0;
  });
  
  //Update each option in the select list
  for (index in sortedOptions)
    select.options[index] = sortedOptions[index];
 
};

//Get the "DueOn" select, and sort it using our new function
const select = document.getElementById("DueOn");
sortSelectOptions(select);
<select id="DueOn">
  <option value="**SelectValue**">Enter custom value</option>
  <option value="1182">Business Day - 1</option>
  <option value="1199">Business Day - 10</option>
  <option value="1801">Business Day - 12</option>
  <option value="1285">Business Day - 15</option>
  <option value="1198">Business Day - 2</option>
  <option value="1232">Calendar Day - 4</option>
  <option value="1191">Calendar Day - 5</option>
  <option value="1306">Calendar Day - 7</option>
  <option value="1782">Calendar Day - 9</option>
  <option value="1757">Day of the Week Day - Friday</option>
  <option value="1770">Day of the Week Day - Wednesday</option>
</select>
Tyler Roper
  • 20,529
  • 6
  • 30
  • 51
  • That's not working yet, as it's still got Business day 10 coming before business day 2... – Christ0pherDertr0it May 15 '19 at 18:25
  • Dude, Thank you. I'm gonna study how you created that as I'm still SO Jr – Christ0pherDertr0it May 15 '19 at 18:36
  • @Christ0pherDertr0it I've added comments. I'm not sure if you want Wednesday and Friday to be ordered by day of the week as opposed to alphabetically, but if so, you would have to modify the condition that checks "Are these numbers?", to additionally check "Are these days of the week?" – Tyler Roper May 15 '19 at 18:49
  • thank you. I'm gonna read through those and see about the FridvWed at the bottom – Christ0pherDertr0it May 15 '19 at 18:49
  • You can take a peek at [this question](https://stackoverflow.com/questions/34066752/sort-object-of-weekdays-like-sunday-monday-saturday) for some more information. – Tyler Roper May 15 '19 at 18:50
0

You need a custom compare function:

function sortSelect(selElem) {
    var tmpAry = new Array();
    for (var i=0;i<selElem.options.length;i++) {
        tmpAry[i] = new Array();
        tmpAry[i][0] = selElem.options[i].text;
        tmpAry[i][1] = selElem.options[i].value;
    }
    tmpAry.sort(compare);
    console.log(tmpAry )
    while (selElem.options.length > 0) {
        selElem.options[0] = null;
    }
    for (var i=0;i<tmpAry.length;i++) {
        var op = new Option(tmpAry[i][0], tmpAry[i][1]);
        selElem.options[i] = op;
    }
    return;
}
sortSelect(document.querySelector("#DueOn"))

function compare([a], [b]) {
  const isANumber = a.match(/\d+$/);
  const isBNumber = b.match(/\d+$/);
  if (isANumber && isBNumber) {
    return isANumber[0] - isBNumber[0];
  }
  if (isANumber) {
    return -1;
  }
    if (isBNumber) {
    return 1;
  }
  return a - b;
}
<select id="DueOn">
    <option value="**SelectValue**">Enter custom value</option>
    <option value="1182" >Business Day - 1</option>
    <option value="1199" >Business Day - 10</option>
    <option value="1801" >Business Day - 12</option>
    <option value="1285" >Business Day - 15</option>
    <option value="1198" >Business Day - 2</option>
    <option value="1232" >Calendar Day - 4</option>
    <option value="1191" >Calendar Day - 5</option>
    <option value="1306" >Calendar Day - 7</option>
    <option value="1782" >Calendar Day - 9</option>
    <option value="1757" >Day of the Week Day - Friday</option>
    <option value="1770" >Day of the Week Day - Wednesday</option>
</select>
marzelin
  • 7,822
  • 1
  • 18
  • 35
0

This is a common enough requirement that there's a sort algorithm already built for it:

a.localeCompare(b, 'en-u-kn-true');

localeCompare returns negative, 0, or positive depending on whether a comes before or after b in English (en) with numeric collation (u-kn-true). tmpAry.sort() expects just such a function, so give it one:

tmpAry.sort((a, b) => a[0].localeCompare(b[0], 'en-u-kn-true'));

The elements of tmpAry are arrays themselves, so you need the [0]s to compare on just the text. localeCompare is a method of String, not Array.

Instead of rebuilding all the <option>s, you can move them to their sorted positions. selElem.appendChild() will move nodes if they already exist in the DOM. Since you're already using a custom sort function, you can pull out the textContent from each <option> and sort by that directly. There's no longer any need to pull out the text and value separately, so tmpAry can contain raw <option>s:

function sortSelect(selElem) {
  const tmpAry = [...selElem.options];
  tmpAry.sort((optionA, optionB) => optionA.textContent.localeCompare(optionB.textContent, 'en-u-kn-true'));
  for (const option of tmpAry) {
    selElem.appendChild(option);
  }
}
sortSelect(document.querySelector("#DueOn"));
<select id="DueOn">
    <option value="**SelectValue**">Enter custom value</option>
    <option value="1182" >Business Day - 1</option>
    <option value="1199" >Business Day - 10</option>
    <option value="1801" >Business Day - 12</option>
    <option value="1285" >Business Day - 15</option>
    <option value="1198" >Business Day - 2</option>
    <option value="1232" >Calendar Day - 4</option>
    <option value="1191" >Calendar Day - 5</option>
    <option value="1306" >Calendar Day - 7</option>
    <option value="1782" >Calendar Day - 9</option>
    <option value="1757" >Day of the Week Day - Friday</option>
    <option value="1770" >Day of the Week Day - Wednesday</option>
</select>
AuxTaco
  • 4,257
  • 1
  • 8
  • 24
-1

I Used basic example and using sort of array provided by javascript and you requirement is fully filled, have a look to code snippet and run it.

var options = [ 'Business Day - 15', 'Business Day - 1', 'Business Day - 12',  'Day of the Week Day - Friday',  'Business Day - 2', 'Calendar Day - 4',  'Business Day - 10', 'Calendar Day - 7', 'Calendar Day - 9', 'Day of the Week Day - Wednesday', 'Calendar Day - 5'];


options  = options.sort();

var select  = document.getElementById('selectTag');

options.forEach(function(value){
    select.options[select.options.length] = new Option(value, select.options.length);
})
<select id='selectTag'>
                <option value='0'>-select-</option>
            </select>
Dupinder Singh
  • 4,165
  • 3
  • 19
  • 45