javascript - Alphabetize options in a select list, retain event listeners - Stack Overflow

admin2025-04-30  0

I need to alphabetize the options in a select list. I can't just switch the innerHTML and value attributes because some options have classes and event listeners attached. Here is what I have so far:

function alphabetizeSelectListItems(theOldList) {
    var theNewList;
    var insertOptionHere;
    theNewList = theOldList.cloneNode(true);
    while(theOldList.options.length > 0) {
        theOldList.options[0].remove();
    }
    while(theNewList.options.length > 0) {
        insertOptionHere = null;
        for(var optionCounter = 0; optionCounter < theOldList.length; optionCounter++) {
            if(theNewList.options[0].innerHTML.toLocaleUpperCase().localeCompare(theOldList.options[optionCounter].innerHTML.toLocaleUpperCase()) < 0) {
                insertOptionHere = optionCounter;
                break;
            }
        }
        if(insertOptionHere == null) {
            theOldList.appendChild(theNewList.options[0]);
        } else {
            theOldList.insertBefore(theNewList.options[0], theOldList.options[insertOptionHere]);
        }
    }
    theNewList.remove();
}

This clones the old list into a temp element, empties the options from the old list, and then copies each option from the temp list back into the old list in its alphabetical place. Then it deletes the temp list.

Problem with this is it copies everything except the event listeners. So I have to use the same select list with the same options but move them around. I've looked at bubble sorting examples and tried to adapt them to option elements instead of array elements, but when I swap two elements, I have to store one in a temporary element, and that removes the event listener.

Is this possible? Is there a way to swap around select list options while retaining the event listeners?

I need to alphabetize the options in a select list. I can't just switch the innerHTML and value attributes because some options have classes and event listeners attached. Here is what I have so far:

function alphabetizeSelectListItems(theOldList) {
    var theNewList;
    var insertOptionHere;
    theNewList = theOldList.cloneNode(true);
    while(theOldList.options.length > 0) {
        theOldList.options[0].remove();
    }
    while(theNewList.options.length > 0) {
        insertOptionHere = null;
        for(var optionCounter = 0; optionCounter < theOldList.length; optionCounter++) {
            if(theNewList.options[0].innerHTML.toLocaleUpperCase().localeCompare(theOldList.options[optionCounter].innerHTML.toLocaleUpperCase()) < 0) {
                insertOptionHere = optionCounter;
                break;
            }
        }
        if(insertOptionHere == null) {
            theOldList.appendChild(theNewList.options[0]);
        } else {
            theOldList.insertBefore(theNewList.options[0], theOldList.options[insertOptionHere]);
        }
    }
    theNewList.remove();
}

This clones the old list into a temp element, empties the options from the old list, and then copies each option from the temp list back into the old list in its alphabetical place. Then it deletes the temp list.

Problem with this is it copies everything except the event listeners. So I have to use the same select list with the same options but move them around. I've looked at bubble sorting examples and tried to adapt them to option elements instead of array elements, but when I swap two elements, I have to store one in a temporary element, and that removes the event listener.

Is this possible? Is there a way to swap around select list options while retaining the event listeners?

Share Improve this question edited Jan 4 at 21:16 jonrsharpe 122k30 gold badges268 silver badges476 bronze badges asked Jan 4 at 21:14 Steve G.Steve G. 4092 gold badges10 silver badges24 bronze badges 5
  • 1 A simpler approach might be changing how your event listeners are applied, if you add a single listener to a parent and tailor the logic based on the child element that triggered the event, the listener wouldn't be lost when the child elements are re-built. – DBS Commented Jan 4 at 21:17
  • So would you mean applying it to the select list itself? – Steve G. Commented Jan 4 at 21:35
  • I don't see the event handlers you mentioned. I can answer without really knowing them, event delegation is very flexible. – zer00ne Commented Jan 4 at 22:05
  • Don't rewrite the innerHTML. Use appendChild() or insertAdjacentElement() to move the elements around themselves. – Barmar Commented Jan 4 at 23:03
  • 1 It isn't obvious nor recommended to add event listeners to options of a select in the first place – IT goldman Commented Jan 5 at 0:41
Add a comment  | 

3 Answers 3

Reset to default 1

You can have an array of the elements and sort it. Then reintroduce the elements into the parent, using appendChild which will move them without cloning.

From MDN: If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position

function alphabetizeSelectListItems(theOldList) {
  const children = Array.from(theOldList.options);
  children.sort(function(a, b) {
    return a.innerText.toLocaleUpperCase().localeCompare(b.innerText.toLocaleUpperCase())
  })
  
  children.forEach(child => theOldList.appendChild(child));
}

alphabetizeSelectListItems(select)
<select id="select">
  <option>Alpha</option>
  <option>Gamma</option>
  <option>Epsilon</option>
  <option>Beta</option>
</select>

Update

I'm not exactly certain if you wanted the sorting when loading or by the user so my answer covers both scenarios.

Event delegation is a programming paradigm that requires:

  • The element that is registered to an event should be an ancestor of the element intended for interaction (eg. a <button> to "click", an <input> to type into, etc).

  • Write the event handler to conditionally accept only the elements intended for interaction. Exclude the rest by not including them.

An ancestor element contains children elements.

The advantages are as follows:

  • Any children elements of the registered ancestor can be delegated to the registered event(s). That also includes any elements added dynamically in the future.

  • There is no limit to the amount of elements that events can be delegated to.

  • These advantages give you almost full control of what does what, how, when, and why.

Details are commented in the example.

User Sorts with <button>

// Reference <form>
const ui = document.forms.ui;
/**
 * Reference all form controls in <form>
 * In this particular layout they are:
 *   - <button>
 *   - <select>
 *   - <output>
 */
const io = ui.elements;
// Reference <select>
const pick = io.pick;
// Reference <output>
const logo = io.logo;
// Array of <option>s
const opts = Array.from(pick.children);

/**
 * For some reason <select> wouldn't stay in
 * order. I have a workaround:
 *   1. Add [multiple] attribute to <select> tag.
 *   2. Next, after <select> has been referenced
 *      remove the attribute.
 */
pick.removeAttribute("multiple");

/**
 * This event handler is triggered when <form> is
 * "click"ed. It delegates the "click" event to the
 * button#sort and excludes all other elements.
 * It does this by setting conditions...
 */
const order = e => {
  /**
   * e.target is the element that the user clicked.
   *  So this handler will do nothing unless the user
   * clicked an element with the #id of "sort".
   */
  if (e.target.id === "sort") {
    // Sort the array of <option>s...
    opts.sort((a, b) => a.value.localeCompare(b.value))
      // Add them back to select#pick...
      .forEach(o => pick.append(o));
    // Adjust <select> and <output> to the first position.
    opts[0].selected = true;
    logo.value = pick.options[pick.selectedIndex].text.slice(0, 2);
  }
};

/**
 * This event handler just displays a larger icon. 
 * It was added to show that the <select> is safe. 
 */
const show = e => {
  if (e.target.id === "pick") {
    logo.value = e.target.options[e.target.selectedIndex].text.slice(0, 2);
  }
};

ui.addEventListener("click", order);
ui.addEventListener("change", show);
:root {
  font: 3ch/1.5 "Segoe UI"
}

form {
  display: flex;
  justify-content: space-evenly;
  align-items: center
}

select,
button,
output {
  display: block;
  padding: 5px;
  font: inherit
}

#pick, #sort {
  min-height: 2.25rem;
  cursor: pointer;
}

#logo {
  font-size: 3rem
}
<form id="ui">
  <button id="sort" type="button">Sort</button>
  <select id="pick" multiple>
    <option value="dragon">
转载请注明原文地址:http://anycun.com/QandA/1746027183a91535.html