html - How can I deselect a multiple select option programmatically, with JavaScript? - Stack Overflow

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

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="/[email protected]/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>

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/npm/[email protected]/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?

Share Improve this question edited Feb 23 at 14:19 Razvan Zamfir asked Feb 23 at 14:13 Razvan ZamfirRazvan Zamfir 4,7047 gold badges47 silver badges283 bronze badges 0
Add a comment  | 

4 Answers 4

Reset to default 6

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

You are looking for

  Array.from(selectBox.options).filter(item => (item.innerText === tagText))[0].selected = false;

Explanation:

  • you get the array
  • from the options of the select box
  • and you filter it
  • by the inner text matching the tag text
  • you will have exactly one element as a result if your texts are unique and all tag texts are among the options
  • so you set the selected attribute of the 0th element of the filter result to false

Snippet:

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 tagText = clickedTag.parentNode.querySelector(".value").textContent
  results = results.filter((tag) => tag != tagText)
  Array.from(selectBox.options).filter(item => (item.innerText === tagText))[0].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/npm/[email protected]/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>

a "shorter"(?) way to do that...

const
  tagSelector   = document.querySelector('#fruits')
, tagsContainer = document.querySelector('ul.tags-list')
  ;
tagSelector.value = ''
  ;
tagSelector.addEventListener('click', evt => 
  {
  if (!evt.target.matches('option')) return
    ;
  while (tagsContainer.firstChild)  
    tagsContainer.removeChild(tagsContainer.lastChild)
    ;
  [...tagSelector.selectedOptions].forEach( opt => 
    {
    let newTag = new Range().createContextualFragment(
      `<li class="tag">
        <span class="value">${opt.value}</span>
        <button>&times;</button>
      </li>`)
      ;
    tagsContainer.append(newTag);
    });
  });

tagsContainer.addEventListener('click', ({target: li_btnTag}) =>
  {
  if (!li_btnTag.matches('li.tag > button')) return
    ;
  let
    liTag    = li_btnTag.closest('li')
  , outValue = liTag.querySelector('span').textContent
    ;
  tagsContainer.removeChild(liTag);
  tagSelector.querySelector(`option[value="${outValue}"]`).selected = false;
  })
/* ADDED */
ul.form-control.tags-list:empty::before {
  content        : 'Select one or more tags from the list below';
  pointer-events : none;
  color          : lightslategrey;
  }
/* ----- */

.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/npm/[email protected]/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"></ul>
    <select id="fruits" name="fruits" class="form-select" multiple size="5">
      <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>

Don't use setAttribute here.

I would also suggest to just have a render function that aligns both widgets with what you have in a memory Set, and let the two event handlers adapt that Set, after which they can synchronise the display with a call to that render function:

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

let selectedTags = new Set; // This has the current state

function render() {
    tagsList.innerHTML = [...selectedTags].sort().map(tag =>
        `<li class="tag"
            ><span class="value">${tag}</span>
            <button>&times;</button>
         </li>`
    ).join("") || '<li class="text-muted">Select one or more tags from the list below</li>';
    for (const option of tagSelector.options) {
        option.selected = selectedTags.has(option.textContent);
    }
}

tagsList.addEventListener("click", function(event) {
    if (event.target.tagName !== "BUTTON") return;
    const span = event.target.closest("LI").children[0];
    selectedTags.delete(span.textContent);
    render();
});

tagSelector.addEventListener("change", function () {
    selectedTags = new Set(
      Array.from(tagSelector.options)
           .filter(option => option.selected)
           .map(option => option.textContent)
    );
    render();
});

// Define the initial selected items, if any:
selectedTags.add("Banana");
render();
.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/npm/[email protected]/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">
       <li class="text-muted">Select one or more tags from the list below</li>
    </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>

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1741316067a4340212.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信