How can I filter the options of a (multiple) select with a search input?

Should I use filter method or somenthing like that?, how should I proceed?

the HTML

<select multiple data-placeholder="Agregar Etiqueta">
      <input id="search-input" type="text" placeholder="Search..." />
      <option value="0">C</option>
      <option value="1">C++</option>
      <option value="2">Php</option>
      <option value="3">Java</option>
      <option value="4">Kotlin</option>
      <option value="5">Swift</option>
      <option value="6">Ruby</option>
      <option value="7">Go</option>
      <option value="8">JavaScript</option>
      <option value="9">Python</option>
      <option value="10">Dart</option>
    </select>

this is the actual JavaScript code

var select = document.querySelector("select[multiple]");
var selectOptions = select.querySelectorAll("option");

var newSelect = document.createElement("div");
newSelect.classList.add("selectMultiple");
var active = document.createElement("div");
active.classList.add("active");

var optionList = document.createElement("ul");
var placeholder = select.dataset.placeholder;

var span = document.createElement("span");
span.innerText = placeholder;
active.appendChild(span);

selectOptions.forEach((option) => {
  let text = option.innerText;

  if (option.selected) {
    let tag = document.createElement("a");
    tag.dataset.value = option.value;
    tag.innerHTML = "<em>" + text + "</em><i></i>";
    active.appendChild(tag);
    span.classList.add("hide");
  } else {
    let item = document.createElement("li");
    item.dataset.value = option.value;
    item.innerHTML = text;
    optionList.appendChild(item);
  }
});
var arrow = document.createElement("div");
arrow.classList.add("arrow");
active.appendChild(arrow);

newSelect.appendChild(active);
newSelect.appendChild(optionList);

select.parentElement.append(newSelect);
span.appendChild(select);

// newSelect.appendChild(select);
//select.style.display = "none";

//document.querySelectorAll('.selectMultiple ul li').forEach((li) => {
document.querySelector(".selectMultiple ul").addEventListener("click", (e) => {
  let li = e.target.closest("li");
  if (!li) {
    return;
  }
  let select = li.closest(".selectMultiple");
  if (!select.classList.contains("clicked")) {
    select.classList.add("clicked");
    if (li.previousElementSibling) {
      li.previousElementSibling.classList.add("beforeRemove");
    }
    if (li.nextElementSibling) {
      li.nextElementSibling.classList.add("afterRemove");
    }
    li.classList.add("remove");
    let a = document.createElement("a");
    a.dataset.value = li.dataset.value;
    a.innerHTML = "<em>" + li.innerText + "</em><i></i>";
    a.classList.add("notShown");
    // a.style.display = "none";
    select.querySelector("div").appendChild(a); //might have to check later
    let selectEl = select.querySelector("select");
    let opt = selectEl.querySelector(
      'option[value="' + li.dataset.value + '"]'
    );
    opt.setAttribute("selected", "selected");
    setTimeout(() => {
      a.classList.add("shown");
      select.querySelector("span").classList.add("hide");
      // if(select.querySelector('option').innerText == li.innerText){
      // 	select.querySelector('option').selected
      // }
    }, 300);
   
    setTimeout(() => {
      let styles = window.getComputedStyle(li);
      let liHeight = styles.height;
      let liPadding = styles.padding;
      let removing = li.animate(
        [
          {
            height: liHeight,
            padding: liPadding,
          },
          {
            height: "0px",
            padding: "0px",
          },
        ],
        {
          duration: 300,
          easing: "ease-in-out",
        }
      );
      removing.onfinish = () => {
        if (li.previousElementSibling) {
          li.previousElementSibling.classList.remove("beforeRemove");
        }
        if (li.nextElementSibling) {
          li.nextElementSibling.classList.remove("afterRemove");
        }
        li.remove();
        select.classList.remove("clicked");
      };
      //             setTimeout(() => {
      //                 if(li.previousElementSibling){
      //                     li.previousElementSibling.classList.remove('beforeRemove');
      //                 }
      //                 if(li.nextElementSibling){
      //                     li.nextElementSibling.classList.remove('afterRemove');
      //                 }

      //             }, 200);
    }, 300); //600
    //2nd
  }
});

//document.querySelectorAll('.selectMultiple > div a').forEach((a) => {
document
  .querySelector(".selectMultiple > div")
  .addEventListener("click", (e) => {
    let a = e.target.closest("a");
    let select = e.target.closest(".selectMultiple");
    if (!a) {
      return;
    }
    a.className = "";
    a.classList.add("remove");
    select.classList.add("open");
    let selectEl = select.querySelector("select");
    let opt = selectEl.querySelector('option[value="' + a.dataset.value + '"]');
    opt.removeAttribute("selected");
    //setTimeout(() => {
    a.classList.add("disappear");
    setTimeout(() => {
      // start animation
      let styles = window.getComputedStyle(a);
      let padding = styles.padding;
      let deltaWidth = styles.width;
      let deltaHeight = styles.height;

      let removeOption = a.animate(
        [
          {
            width: deltaWidth,
            height: deltaHeight,
            padding: padding,
          },
          {
            width: "0px",
            height: "0px",
            padding: "0px",
          },
        ],
        {
          duration: 0,
          easing: "ease-in-out",
        }
      );

      let li = document.createElement("li");
      li.dataset.value = a.dataset.value;
      li.innerText = a.querySelector("em").innerText;
      li.classList.add("show");
      select.querySelector("ul").appendChild(li);
      setTimeout(() => {
        if (!selectEl.selectedOptions.length) {
          select.querySelector("span").classList.remove("hide");
        }
        li.className = "";
      }, 350);

      removeOption.onfinish = () => {
        a.remove();
      };
      //end animation
    }, 300);
    //}, 400);
  });
//});
//3
document
  .querySelectorAll(".selectMultiple > div .arrow, .selectMultiple > div span")
  .forEach((el) => {
    el.addEventListener("click", (e) => {
      el.closest(".selectMultiple").classList.toggle("open");
    });
  });

So as a user types into the input, you want the select element’s options to dynamically change?

yes, one example would be if I write the letter “J” it filters the options and only shows “Java” and “JavaScript”

One way would be to listen for an input event on the input element with id="search-input" and iterate through each option (you can get all the options using document.querySelectorAll("select[multiple] option"). If the lower case version of the option text starts with the lower case version of the input element’s value, then set the display property of the option’s style to 'block' or else set it to 'none'. This will hide the options that do not start with the same characters as the input element’s value.

1 Like

I’m struggling to add the input dynamically from the js file, I’m using createElement and append but is not working :frowning:

I think using CSS is simpler than dynamically populating the select element.

Something that might help is giving the option elements an attribute like a name and using an attribute selector with the “begins with” ^ qualifier (attribute selectors) with a querySelectorAll.

  1. Loop the elements and hide them (I’d use a class and not inline CSS)
  2. Get the search string.
  3. Select the elements using querySelectorAll with an attribute selector that matches on the start of the search string.
  4. Unhide the elements that match the selector.

Just as an aside, an input element is not permitted content for the select element.

all the li and ul are already beeing created dynamically so I wanted to code accordingly to that, but I guess I will just use a plugin.

Then what is the HTML you posted? Is that added dynamically or is it just in the starting HTML?

Also, just because something is initially added dynamically doesn’t mean you can’t hide/show it using CSS. I’d also imagine it is more performant using CSS instead of updating the elements in the DOM on each keystroke.


Here is a simple example of what I was talking about.

1 Like