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");
    });
  });

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

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

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.