Tanuj Purohit
Tanuj Purohit

Reputation: 9

Add editable drop-down with multi select items through html css or vanilla js

Just wanted to know easiest way to achieve this

enter code here

Upvotes: 0

Views: 659

Answers (1)

vanowm
vanowm

Reputation: 10221

Perhaps something like this:

(()=>
{
  const formEls = document.querySelectorAll(".input-tags");
  for(let i = 0; i < formEls.length; i++)
  {
    const formEl = formEls[i],
          inputEl = document.createElement("input"),
          tagsEl = document.createElement("span"),
          listEl = document.createElement("datalist");

    formEl.tags = [];
    Object.defineProperties(formEl, {
      list: {
        get(){return getData(this, "list")},
        set(val){this.dataset.list = val}
      },
      tags: {
        get(){return getData(this, "tags")},
        set(val){this.dataset.tags = val}
      },
      value:
      {
        get(){return this.dataset.value || ""},
        set(val){this.dataset.value = val}
      }
    });
    const list = formEl.list;
    listEl.id = "input-tags-datalist" + i;
    inputEl.setAttribute("list", listEl.id);
    inputEl.type = "text";
    tagsEl.className = "tags";
    for(let i = 0, optionEl = document.createElement("option"); i < list.length; i++)
    {
      optionEl = optionEl.cloneNode(false);
      optionEl.value = list[i];
      listEl.appendChild(optionEl);
    }
    formEl.appendChild(tagsEl);
    formEl.appendChild(inputEl);
    formEl.appendChild(listEl);
    inputEl._isClicked = true;
    inputEl.addEventListener("keydown", e => inputEl._isClicked = !e.keyCode || e.keyCode==13);
    inputEl.addEventListener("keyup", e => inputEl._isClicked = true);
    inputEl.addEventListener("input", e =>
    {
      formEl.value = inputEl.value;
      if (!inputEl._isClicked && !inputEl.value.match(/(^[^"']+ $)|(^(["']).+\3$)/))
      {
        dispatchEvent(formEl, "input");
        return inputWidth(formEl);
      }
      const val = inputEl.value.replace(/^\s*((["'])([^"']+)\2|([^"']+)\s+)$/, "$4$3").replace(/[^\w -_]+/g, "").replace(/[ ]{2,}/g, " ");
      if (formEl.dataset.autotags !== undefined || formEl.list.indexOf(val) != -1)
      {
        inputEl.value = val;
        addTag(inputEl);
      }
      formEl.value = inputEl.value;
      dispatchEvent(formEl, "input");
      inputWidth(formEl);
    });//inputEl.oninput()

    tagsEl.addEventListener("click", e =>
    {
      if (!e.target.parentNode.classList.contains("tag"))
        return;

      const tag = e.target.parentNode.textContent,
            list = formEl.list,
            tags = formEl.tags,
            index = list.indexOf(tag),
            optionEl = listEl.children[index];

      if (optionEl.classList.contains("new"))
      {
        list.splice(index, 1);
        optionEl.parentNode.removeChild(optionEl);
      }
      else
        optionEl.disabled = false;

      tags.splice(tags.indexOf(tag), 1);
      formEl.tags = tags;
      formEl.list = list;
      e.target.parentNode.parentNode.removeChild(e.target.parentNode);
      inputWidth(formEl);
      e.stopPropagation();
      formEl.click();
      dispatchEvent(formEl, "input");
    });//tagsEl.onclick()

    formEl.addEventListener("click", e => inputEl.focus());
    inputWidth(formEl);
  }

  function dispatchEvent(el, type, opts)
  {
    return el.dispatchEvent(new Event(type, opts));
  }

  function inputWidth(formEl)
  {

    const inputEl = formEl.querySelector("input");
    inputEl.style.width = "1em"; //min width
    const inputStyle = window.getComputedStyle(inputEl),
          formStyle = window.getComputedStyle(inputEl.parentNode),
          inputRect = inputEl.getBoundingClientRect(),
          formRect = inputEl.parentNode.getBoundingClientRect(),
          canvas = document.createElement('canvas'),
          ctx = canvas.getContext("2d");

    ctx.font = inputStyle.font;
    const widthText = (ctx.measureText(inputEl.value).width
                + parseFloat(inputStyle.paddingLeft)
                + parseFloat(inputStyle.paddingRight)
                + parseFloat(inputStyle.textIndent)
                + parseFloat(inputStyle.borderLeftWidth)
                + parseFloat(inputStyle.borderRightWidth)
                + 1
                ),
          widthBox = formRect.right - inputRect.left - parseFloat(formStyle.paddingLeft) - parseFloat(formStyle.paddingRight) - 1;
    inputEl.style.width = Math.max(widthText, widthBox) + "px";
  }

  function getData(el, key)
  {
    return el.dataset[key] ? el.dataset[key].split(",") : [];
  }

  function addTag(input)
  {
    const formEl = input.parentNode,
          tag = input.value.trim(),
          list = formEl.list,
          tags = formEl.tags;


    if (tag === "" || tags.indexOf(tag) != -1)
      return;

    const tagsEl = formEl.querySelector(".tags"),
          tagEl = document.createElement("span"),
          datalistEl = formEl.querySelector("datalist");

    if (formEl.dataset.autotags !== undefined && list.indexOf(tag) == -1)
    {
      const option = document.createElement("option");
      option.value = tag;
      option.className = "new";
      datalistEl.appendChild(option);
      list[list.length] = tag;
    }
    tags[tags.length] = tag;
    formEl.list = list;
    formEl.tags = tags;
    const index = list.indexOf(tag);
    datalistEl.children[index].disabled = true;

    tagEl.className = "tag";
    tagEl.textContent = tag;
    tagEl.appendChild(document.createElement("span"));
    tagsEl.appendChild(tagEl);
    input.value = "";
  }
})();

//example:
const test = document.getElementById("test");
test.addEventListener("input", e => 
{
  if (e.target !== test)
    return;

  console.log('value:', test.value);
  console.log("tags:", JSON.stringify(test.tags));
  console.log("list:", JSON.stringify(test.list));
}, false);
.input-tags
{
  display: inline-block;
  border: 1px solid black;
  font-size: 0.8em;
  padding: 0.1em 0.1em 0.1em 0.05em;
  width: 100%;
  line-height: 1em;
}

.input-tags > input,
.input-tags > input:focus,
.input-tags > input:active
{
  outline: none;
  border: none;
  margin: 0.15em 0;
  vertical-align: middle;
  max-width: 100%;
  box-sizing: border-box;
}
.input-tags > input::-webkit-calendar-picker-indicator
{
  display: none !important;
}
.input-tags > .tags
{
  vertical-align: middle;
}
.input-tags .tags .tag
{
  display: inline-block;
  background-color: lightblue;
  border: 1px solid blue;
  border-radius: 2px;
  font-family: "Segoe UI","Liberation Sans",sans-serif;
  margin: 0.1em;
  padding: 0 0.2em;
  line-height: 1.3em;
}

.input-tags .tags .tag > span
{
  margin: -0.05em -0.2em 0 0.05em;
  cursor: pointer;
  display: inline-block;
  font-size: 1.3em;
  transform: rotate(45deg);
  border-radius: 2em;
  line-height: 0.7em;
  float: right;
}

.input-tags .tags .tag > span:before
{
  content: "+";
  position: relative;
  top: -0.1em;
}
.input-tags .tags .tag > span:hover
{
  background-color: #60B3CE;
}


/* stackoverflow console */
.as-console-wrapper{max-height:7em!important;}
<div style="display: grid; grid-template-columns: auto auto">
  <span>Auto-add new tags, suggestions:</span>
  <div style="display: inline-block; width: 50vw;">
    <div id="test" class="input-tags" data-autotags data-list="test,sometag,SOMETAG,another tag,another tag2,another tag3,another,tag"></div>
  </div>
  <span>Auto-add new tags, no suggestions:</span>
  <div style="display: inline-block; width: 50vw;">
    <span class="input-tags" data-autotags></span>
  </div>
  <span>No new tags, suggestions:</span>
  <div style="display: inline-block; min-width: 10em;">
    <div class="input-tags" data-list="test,some tag,very long tag,blah"></div>
  </div>
<div>

Upvotes: 2

Related Questions