I have made a "tags selector" in plain JavaScript, without using any plugin.
const tagsList = document.querySelector(".tags-list")
const tagActions = document.getElementById("tagActions")
const tagSelector = document.getElementById("tags")
const tagToggler = document.getElementById("tagSelectorToggler")
var preSelectedTags = Array.from(tagSelector.options)
.filter(option => option.selected)
.map(option => option.text);
var selectedTags = new Set()
function filterTags(event) {
var query = event.target.value
var availableTags = Array.from(tagSelector.options)
availableTags.forEach(function (option) {
if (!option.text.toLowerCase().includes(query.toLowerCase())) {
option.classList.add("d-none")
} else {
option.classList.remove("d-none")
}
})
}
function toggleTagSelector(event) {
if (event.target.tagName !== "BUTTON" && event.target.tagName !== "SPAN") {
tagActions.classList.toggle("d-block")
tagToggler.classList.toggle("active")
}
}
function renderTags() {
tagsList.innerHTML =
[...selectedTags]
.sort()
.map(
(tag) =>
`<li class="tag"
><span class="value">${tag}</span>
<button>×</button>
</li>`,
)
.join("") ||
`<li class="text-muted">Use [Ctrl] + [Click] to select one or more tags from the list</li>`
for (const option of tagSelector.options) {
option.selected = selectedTags.has(option.textContent)
}
}
function addPreselectedTags(){
preSelectedTags.forEach(function(tag){
selectedTags.add(tag)
});
renderTags();
}
tagsList.addEventListener("click", function (event) {
if (event.target.tagName !== "BUTTON") return
const span = event.target.closest("LI").children[0]
selectedTags.delete(span.textContent)
renderTags()
})
tagSelector.addEventListener("change", function () {
selectedTags = new Set(
Array.from(tagSelector.options)
.filter((option) => option.selected)
.map((option) => option.textContent),
)
renderTags()
})
addPreselectedTags();
.form-control:focus, .form-select:focus {
box-shadow: none;
}
/* Tags */
.tags-list {
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: 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%;
}
.tag-actions {
display: none;
}
.toggler {
position: absolute;
right: 5px;
margin-top: 8px;
color: #777;
cursor: pointer;
}
.toggler.active {
transform: rotate(180deg);
}
.tag-select {
height: 180px;
}
.tag-select option {
padding: 3px 8px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container my-2">
<form action="">
<div class="form-group position-relative">
<label for="tags" class="text-muted fw-bold pb-1">Tags</label>
<div class="position-relative">
<span
id="tagSelectorToggler"
class="toggler"
onclick="toggleTagSelector(event)"
>
<i class="fas fa-chevron-up"></i>
</span>
<ul
class="form-control tags-list mb-1 pe-4"
onclick="toggleTagSelector(event)"
>
<li class="text-muted">
Use [Ctrl] + [Click] to select one or more tags from the list
</li>
</ul>
</div>
<div id="tagActions" class="tag-actions d-block">
<input
oninput="filterTags(event)"
type="search"
class="form-control mb-1"
placeholder="Filter available tags"
/>
<select id="tags" name="tags" class="form-select tag-select" multiple>
<option value="Apple" selected>Apple</option>
<option value="Banana">Banana</option>
<option value="Blackberry" selected>Blackberry</option>
<option value="Blueberry">Blueberry</option>
<option value="Cherry">Cherry</option>
<option value="Cranberry">Cranberry</option>
<option value="Grapes">Grapes</option>
<option value="Kiwi">Kiwi</option>
<option value="Mango">Mango</option>
<option value="Orange">Orange</option>
<option value="Peach">Peach</option>
<option value="Pear">Pear</option>
<option value="Pineapple">Pineapple</option>
<option value="Raspberry">Raspberry</option>
<option value="Strawberry">Strawberry</option>
<option value="Watermelon">Watermelon</option>
</select>
</div>
</div>
<div class="form-group mt-3">
<label for="body" class="text-muted fw-bold pb-1">Content</label>
<textarea class="form-control" name="body" id="body" cols="30" rows="5"></textarea>
</div>
<div class="mt-2">
<button class="btn btn-success btn-sm w-100">Send</button>
</div>
</form>
</div>
I am interested to know the following:
- Is the tags selector interface intuitive and easy to use?
- How can the app's usefulness or user-experience be improved?
- Are there any suboptimal pieces of code that should be improved and if so, how?