3

I need help with sorting the options of a dropdown menu. I have the following:

<select name="models" id="models">
    <option value="14b">14b</option>
    <option value="ab">ab</option>
    <option value="14">14</option>
    <option value="bc">bc</option>
    <option value="15">15</option>
    <option value="101">101</option>
    <option value="13">13</option>
</select>

I would like to sort the options so they appear as following:

<select name="models" id="models">
    <option value="ab">ab</option>
    <option value="bc">bc</option>
    <option value="13">13</option>
    <option value="14">14</option>
    <option value="14b">14b</option>
    <option value="15">15</option>
    <option value="101">101</option>
</select>

The dropdown would have 2 types of entries, ones that start with a number and ones that start with a letter. I'd like to first simply just sort the ones that start with a letter alphabetically and place them at the beginning of the dropdown. Then, some of the ones that start with a number can occasionally have a letter or two at the end. The ones with letters would be considered one step greater than the ones without and would be positioned afterwards so an order would be 14 > 14b > 15 for example

I've searched and found several posts on how to sort lists / dropdowns but this is a bit more complicated and I can't seem to figure out on my own how to piece everything together. Can anyone please help? Thank you!

2 Answers 2

4

Try this

function natcmp(a, b) {
    var ra = a.innerText.match(/\D+|\d+/g);
    var rb = b.innerText.match(/\D+|\d+/g);
    var r = 0;

    while(!r && ra.length && rb.length) {
        var x = ra.shift(), y = rb.shift(),
            nx = parseInt(x), ny = parseInt(y);
        if(isNaN(nx) && isNaN(ny))
           { r = x > y ? 1 : -1}
        else if(isNaN(nx) || isNaN(ny))
           { r = x < y ? 1 : (x > y ? -1 : 0); }
        else
            r = nx - ny;
    }
    return r || ra.length - rb.length;
}


$("#models").html(
Array.prototype.slice.call($("#models")[0].querySelectorAll("option"), 0).sort(natcmp)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select name="models" id="models">
    <option value="14b">14b</option>
    <option value="ab">ab</option>
    <option value="14">14</option>
    <option value="bc">bc</option>
    <option value="15">15</option>
    <option value="101">101</option>
    <option value="13">13</option>
</select>

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

3 Comments

Thank you for taking the time to answer my question! This works great, pretty much plug and play. I picked the other post as the "answer" because it was first and it had comments but I have a feeling this might be a more elegant approach simply because it's harder for me to understand as a novice. Regardless I appreciate you taking the time to help :)
You ‘re welcome :) we are here for help. Not for points . And I answered first not him :) your tag could be in active so u can saw his post above me :) but again not important for me who u choose :) just use the best one for u
Oh I'm sorry, I thought the answers were posted chronologically but I see now. I will definitely come back to your answer in a few weeks once I get a little bit more experience, thanks again!
1

We can do it like this (vanilla JS):

(Essentially, put the children in an array, sort them and then append them again in the order of the sorted array - essentially moving them into the correct order)

(The sort function, basically checks if both a and b have integers that can be parsed - if so compare those and if they are equal compare and letters after the numbers. If both are only letters do a textual comparison only. If a is letters only and b has at least numbers, then a goes first and vice versa)

const select = document.querySelector('#models');

[...select.children].sort(mySort).map(node => select.appendChild(node));
console.log([...select.children]);

function mySort(a,b) {  
  const [a_val, b_val]  = [a.value, b.value];
  
  if (Math.abs(parseInt(a_val) - parseInt(b_val)) >= 0){
    //both are numbers (or at least start with numbers)
    if (parseInt(a_val) - parseInt(b_val) == 0){
      //then we need to compare the letters after too:
      const a_letters = a_val.replace(parseInt(a_val), "");
      const b_letters = b_val.replace(parseInt(b_val), "");
      return a_letters.localeCompare(b_letters)
    } else {
      //the letters after don't matter for comparison:
      return parseInt(a_val) - parseInt(b_val)
    }        
  } else if(Math.abs(parseInt(a_val)) >= 0) {
    //only a is a number, so b goes first:
    return 1;    
  } else if(Math.abs(parseInt(b_val)) >= 0) {
    //only b is a number, so a goes first:
    return -1;    
  } else {
    //both are letters only so do a textual comparison:
    return a_val.localeCompare(b_val)
  }  
    
}
<select name="models" id="models">
    <option value="14b">14b</option>
    <option value="ab">ab</option>
    <option value="14">14</option>
    <option value="bc">bc</option>
    <option value="15">15</option>
    <option value="101">101</option>
    <option value="13">13</option>
</select>

Output:

enter image description here

2 Comments

Thank you for taking the time to answer my question and also for adding comments to your code. As I'm pretty novice with JavaScript this helped me understand step by step what you were doing. For me personally this was perfect!
You're very welcome :) - happy I could help. And my suggestion would be to master vanilla JS before JQuery (or don't use JQuery at all, if you can help it). This way your fundamental knowledge will be stronger and rely less on a library or framework.

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.