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...

9
  • Please share your sorting code. Commented May 15, 2019 at 17:52
  • please provide sorting code, so that we can further proceed Commented May 15, 2019 at 17:55
  • There's no logical correlation between value and text content I assume? Commented May 15, 2019 at 17:57
  • You are correct zer00ne Commented May 15, 2019 at 17:59
  • 1
    Look up using localeCompare with numeric flag set or look into a natural sort algorithm Commented May 15, 2019 at 18:02

4 Answers 4

2

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>

Sign up to request clarification or add additional context in comments.

Comments

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>

5 Comments

That's not working yet, as it's still got Business day 10 coming before business day 2...
Dude, Thank you. I'm gonna study how you created that as I'm still SO Jr
@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?"
thank you. I'm gonna read through those and see about the FridvWed at the bottom
You can take a peek at this question for some more information.
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>

1 Comment

It's close, but I'm hoping to keep all the "Business Day"s together, and all the "Calendar Day"s together
-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>

4 Comments

You're sorting by string. The only reason your result looks correct is because your numbers are in "alphabetical" order. Try adding some two digit numbers.
have a look now, I updated the code with required data
Correct, and it now highlights the issue I alluded to. You have Business Day - 2 coming after Business Day - 15, for example.
yaaa okay, I got it, seems nice problem, I will get back to you awesome guys

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.