javascripthtmlcss

How can I deselect a multiple select option programmatically, with JavaScript?


I am working on the "tags selector" below in JavaScript, without using any plugin.

var results = []

function renderTags() {
  var tagsContainer = document.querySelector(".tags-list")
  var tags = ""
  results = [...new Set(results)]
  results = results.sort()
  results.forEach(function(tag) {
    var tag = `<li class="tag"
        ><span class="value">${tag}</span>
        <button>&times;</button>
      </li>`
    tags = tags + tag
    tagsContainer.innerHTML = tags
  })
}

function selectTags(event) {
  var select = event.target
  var options = select && select.options
  for (var i = 0; i < options.length; i++) {
    if (options[i].selected) {
      results.push(options[i].text)
    } else {
      results = results.filter((tag) => tag !== options[i].text)
    }
  }
  renderTags()
}

function removeTags(event) {
  var elementToRemove = null
  var clickedTag = event.target
  var selectBox = clickedTag.closest("div").querySelector("select")
  var selectOptions = Array.from(selectBox.options)
  var tagText = clickedTag.parentNode.querySelector(".value").textContent
  results = results.filter((tag) => tag != tagText)
  if (!results.includes(tagText)) {
    elementToDeselect = selectOptions.find((o) => o.text == tagText)
    elementToDeselect.setAttribute('selected', false)
  }
  renderTags()
}

const tagSelector = document.getElementById("fruits")
const tagsList = document.querySelector(".tags-list")

tagsList.addEventListener("click", function(event) {
  if (event.target.parentNode.tagName === "LI") {
    removeTags(event)
  }
})

tagSelector.addEventListener("change", selectTags)
.tags-list {
  margin: 0 0 4px 0;
  min-height: 40px;
  list-style-type: none;
}

.tags-list .tag {
  line-height: 1;
  white-space: nowrap;
  background: #f2f2f2;
  border: 1px solid #e6e3e3;
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  font-size: 13px;
  padding: 3px 8px;
  margin-bottom: 1px;
}

.tags-list .tag button {
  background: #ff9292;
  color: #720000;
  border: none;
  width: 18px;
  height: 18px;
  font-size: 12px;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  margin-left: 6px;
  border-radius: 50%;
}

select[multiple] option {
  padding: 4px 8px;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />

<div class="container">
  <div class="form-group">
    <label for="fruits">Fruits</label>
    <ul class="form-control tags-list">
      <p class="m-0">Select one or more tags from the list below</p>
    </ul>
    <select id="fruits" name="fruits" class="form-select" multiple>
      <option value="apple">Apple</option>
      <option value="banana">Banana</option>
      <option value="blackberry">Blackberry</option>
      <option value="blueberry">Blueberry</option>
      <option value="watermelon">Watermelon</option>
    </select>
  </div>
</div>

The removeTags(event) method should not only remove a tag from the top tags list, but deselect the option corresponding to the removed tag from the <select multiple> element.

For this purpose, I have used:

if (!results.includes(tagText)) {
    elementToDeselect = selectOptions.find((o) => o.text == tagText)
    elementToDeselect.setAttribute('selected', false)
} 

For a reason I was unable to figure out, the option corresponding to the removed tag appears selected.

How can I obtain the desired result?


Solution

  • Use elementToDeselect.selected = false to set the selected property (which ends up setting the attribute), not setAttribute (which would just set a HTML attribute).