I am trying to implement the following tags with my design.
I am using the class Tags
to simply create tags within my input field, however when I initialize the library I get an error.
const ACTIVE_CLASS = "bg-light";
const VALUE_ATTRIBUTE = "data-value";
class Tags {
/**
* @param {HTMLSelectElement} selectElement
*/
constructor(selectElement) {
this.selectElement = selectElement;
this.selectElement.style.display = "none";
this.placeholder = this.getPlaceholder();
this.allowNew = selectElement.dataset.allowNew ? true : false;
// Create elements
this.holderElement = document.createElement("div");
this.containerElement = document.createElement("div");
this.dropElement = document.createElement("ul");
this.searchInput = document.createElement("input");
this.holderElement.appendChild(this.containerElement);
this.containerElement.appendChild(this.searchInput);
this.holderElement.appendChild(this.dropElement);
// insert after
this.selectElement.parentNode.insertBefore(this.holderElement, this.selectElement.nextSibling);
// Configure them
this.configureSearchInput();
this.configureHolderElement();
this.configureDropElement();
this.configureContainerElement();
this.buildSuggestions();
}
/**
* Attach to all elements matched by the selector
* @param {string} selector
*/
static init(selector = "select[multiple]") {
let list = document.querySelectorAll(selector);
for (let i = 0; i < list.length; i++) {
let el = list[i];
let inst = new Tags(el);
}
}
/**
* @returns {string}
*/
getPlaceholder() {
let firstOption = this.selectElement.querySelector("option");
if (!firstOption) {
return;
}
if (!firstOption.value) {
let placeholder = firstOption.innerText;
firstOption.remove();
return placeholder;
}
if (this.selectElement.getAttribute("placeholder")) {
return this.selectElement.getAttribute("placeholder");
}
if (this.selectElement.getAttribute("data-placeholder")) {
return this.selectElement.getAttribute("data-placeholder");
}
return "";
}
configureDropElement() {
this.dropElement.classList.add("dropdown-menu");
}
configureHolderElement() {
this.holderElement.classList.add("form-control");
this.holderElement.classList.add("dropdown");
}
configureContainerElement() {
this.containerElement.addEventListener("click", (event) => {
this.searchInput.focus();
});
// add initial values
let initialValues = this.selectElement.querySelectorAll("option[selected]");
for (let j = 0; j < initialValues.length; j++) {
let initialValue = initialValues[j];
if (!initialValue.value) {
continue;
}
this.addItem(initialValue.innerText, initialValue.value);
}
}
configureSearchInput() {
this.searchInput.type = "text";
this.searchInput.autocomplete = false;
this.searchInput.style.border = 0;
this.searchInput.style.outline = 0;
this.searchInput.style.maxWidth = "100%";
this.adjustWidth();
this.searchInput.addEventListener("input", (event) => {
this.adjustWidth();
if (this.searchInput.value.length >= 1) {
this.showSuggestions();
} else {
this.hideSuggestions();
}
});
// keypress doesn't send arrow keys
this.searchInput.addEventListener("keydown", (event) => {
if (event.code == "Enter") {
let selection = this.getActiveSelection();
if (selection) {
this.addItem(selection.innerText, selection.getAttribute(VALUE_ATTRIBUTE));
this.resetSearchInput();
this.hideSuggestions();
} else {
// We use what is typed
if (this.allowNew) {
this.addItem(this.searchInput.value);
this.resetSearchInput();
this.hideSuggestions();
}
}
event.preventDefault();
return;
}
if (event.code == "ArrowUp") {
this.moveSelectionUp();
}
if (event.code == "ArrowDown") {
this.moveSelectionDown();
}
if (event.code == "Backspace") {
if (this.searchInput.value.length == 0) {
this.removeLastItem();
this.adjustWidth();
}
}
});
}
moveSelectionUp() {
let active = this.getActiveSelection();
if (active) {
let prev = active.parentNode;
do {
prev = prev.previousSibling;
} while (prev && prev.style.display == "none");
if (!prev) {
return;
}
active.classList.remove(ACTIVE_CLASS);
prev.querySelector("a").classList.add(ACTIVE_CLASS);
}
}
moveSelectionDown() {
let active = this.getActiveSelection();
if (active) {
let next = active.parentNode;
do {
next = next.nextSibling;
} while (next && next.style.display == "none");
if (!next) {
return;
}
active.classList.remove(ACTIVE_CLASS);
next.querySelector("a").classList.add(ACTIVE_CLASS);
}
}
/**
* Adjust the field to fit its content
*/
adjustWidth() {
if (this.searchInput.value) {
this.searchInput.size = this.searchInput.value.length + 1;
} else {
// Show the placeholder only if empty
if (this.getSelectedValues().length) {
this.searchInput.placeholder = "";
this.searchInput.size = 1;
} else {
this.searchInput.size = this.placeholder.length;
this.searchInput.placeholder = this.placeholder;
}
}
}
/**
* Add suggestions from element
*/
buildSuggestions() {
let options = this.selectElement.querySelectorAll("option");
for (let i = 0; i < options.length; i++) {
let opt = options[i];
if (!opt.getAttribute("value")) {
continue;
}
let newChild = document.createElement("li");
let newChildLink = document.createElement("a");
newChild.append(newChildLink);
newChildLink.classList.add("dropdown-item");
newChildLink.setAttribute(VALUE_ATTRIBUTE, opt.getAttribute("value"));
newChildLink.setAttribute("href", "#");
newChildLink.innerText = opt.innerText;
this.dropElement.appendChild(newChild);
// Hover sets active item
newChildLink.addEventListener("mouseenter", (event) => {
this.removeActiveSelection();
newChild.querySelector("a").classList.add(ACTIVE_CLASS);
});
newChildLink.addEventListener("click", (event) => {
event.preventDefault();
this.addItem(newChildLink.innerText, newChildLink.getAttribute(VALUE_ATTRIBUTE));
this.resetSearchInput();
this.hideSuggestions();
});
}
}
resetSearchInput() {
this.searchInput.value = "";
this.adjustWidth();
}
/**
* @returns {array}
*/
getSelectedValues() {
let selected = this.selectElement.querySelectorAll("option:checked");
return Array.from(selected).map((el) => el.value);
}
/**
* The element create with buildSuggestions
*/
showSuggestions() {
if (!this.dropElement.classList.contains("show")) {
this.dropElement.classList.add("show");
}
// Position next to search input
this.dropElement.style.left = this.searchInput.offsetLeft + "px";
// Get search value
let search = this.searchInput.value.toLocaleLowerCase();
// Get current values
let values = this.getSelectedValues();
// Filter the list according to search string
let list = this.dropElement.querySelectorAll("li");
let found = false;
let firstItem = null;
for (let i = 0; i < list.length; i++) {
let item = list[i];
let text = item.innerText.toLocaleLowerCase();
let link = item.querySelector("a");
// Remove previous selection
link.classList.remove(ACTIVE_CLASS);
// Hide selected values
if (values.indexOf(link.getAttribute(VALUE_ATTRIBUTE)) != -1) {
item.style.display = "none";
continue;
}
if (text.indexOf(search) !== -1) {
item.style.display = "list-item";
found = true;
if (!firstItem) {
firstItem = item;
}
} else {
item.style.display = "none";
}
}
// Special case if nothing matches
if (!found) {
this.dropElement.classList.remove("show");
}
// Always select first item
if (firstItem) {
if (this.holderElement.classList.contains("is-invalid")) {
this.holderElement.classList.remove("is-invalid");
}
firstItem.querySelector("a").classList.add(ACTIVE_CLASS);
} else {
// No item and we don't allow new items => error
if (!this.allowNew) {
this.holderElement.classList.add("is-invalid");
}
}
}
/**
* The element create with buildSuggestions
*/
hideSuggestions(dropEl) {
if (this.dropElement.classList.contains("show")) {
this.dropElement.classList.remove("show");
}
if (this.holderElement.classList.contains("is-invalid")) {
this.holderElement.classList.remove("is-invalid");
}
}
/**
* @returns {HTMLElement}
*/
getActiveSelection() {
return this.dropElement.querySelector("a." + ACTIVE_CLASS);
}
removeActiveSelection() {
let selection = this.getActiveSelection();
if (selection) {
selection.classList.remove(ACTIVE_CLASS);
}
}
removeLastItem() {
let items = this.containerElement.querySelectorAll("span");
if (!items.length) {
return;
}
let lastItem = items[items.length - 1];
this.removeItem(lastItem.getAttribute(VALUE_ATTRIBUTE));
}
/**
* @param {string} text
* @param {string} value
*/
addItem(text, value) {
if (!value) {
value = text;
}
let span = document.createElement("span");
span.classList.add("badge");
span.classList.add("bg-primary");
span.classList.add("me-2");
span.setAttribute(VALUE_ATTRIBUTE, value);
span.innerText = text;
this.containerElement.insertBefore(span, this.searchInput);
// update select
let opt = this.selectElement.querySelector('option[value="' + value + '"]');
if (opt) {
opt.setAttribute("selected", "selected");
} else {
// we need to create a new option
opt = document.createElement("option");
opt.value = value;
opt.innerText = text;
opt.setAttribute("selected", "selected");
this.selectElement.appendChild(opt);
}
}
/**
* @param {string} value
*/
removeItem(value) {
let item = this.containerElement.querySelector("span[" + VALUE_ATTRIBUTE + '="' + value + '"]');
if (!item) {
return;
}
item.remove();
// update select
let opt = this.selectElement.querySelector('option[value="' + value + '"]');
if (opt) {
opt.removeAttribute("selected");
}
}
}
export default Tags;
import Tags
Tags.init();
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.80.0">
<title>Insider</title>
<link rel="canonical" href="">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.0/font/bootstrap-icons.css">
<!-- Favicons -->
<link rel="apple-touch-icon" href="https://getbootstrap.com/docs/5.0/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="https://getbootstrap.com/docs/5.0/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="https://getbootstrap.com/docs/5.0/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="https://getbootstrap.com/docs/5.0/assets/img/favicons/manifest.json">
<link rel="mask-icon" href="https://getbootstrap.com/docs/5.0/assets/img/favicons/safari-pinned-tab.svg" color="#7952b3">
<link rel="icon" href="https://getbootstrap.com/docs/5.0/assets/img/favicons/favicon.ico">
<meta name="theme-color" content="#7952b3">
<!-- Custom styles for this template -->
<link href="https://unpkg.com/bootstrap-table@1.18.3/dist/bootstrap-table.min.css" rel="stylesheet">
</head>
<body>
<header class="p-3 bg-dark text-white">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li>
<button class="btn btn-outline-light" type="button" data-bs-toggle="modal" data-bs-target="#exampleModal">Check
</button>
</li>
</ul>
</div>
</div>
</header>
<main class="container pt-3">
<div class="row mt-3">
<h2>Content</h2>
</div>
</main>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Your Check</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<br>
<p class="h2">Check</p>
<p>Input your symbols and we will send you all relevant information.</p>
<br>
<form>
<div class="col">
<select class="form-select" id="validationTags" multiple="" data-allow-new="true" style="display: none;">
<option value="1" selected="selected">JavaScript</option>
<option value="2">HTML5</option>
<option value="3">CSS3</option>
<option value="4">jQuery</option>
<option value="5">React</option>
<option value="6">Angular</option>
<option value="7">Vue</option>
<option value="8">Python</option>
</select>
<div class="form-control dropdown">
<div><span class="badge bg-primary me-2" data-value="1">JavaScript</span><input type="text" autocomplete="false" placeholder="" size="1" style="border: 0px; outline: 0px; max-width: 100%;">
</div>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-value="1" href="#">JavaScript</a></li>
<li><a class="dropdown-item" data-value="2" href="#">HTML5</a></li>
<li><a class="dropdown-item" data-value="3" href="#">CSS3</a></li>
<li><a class="dropdown-item" data-value="4" href="#">jQuery</a></li>
<li><a class="dropdown-item" data-value="5" href="#">React</a></li>
<li><a class="dropdown-item" data-value="6" href="#">Angular</a></li>
<li><a class="dropdown-item" data-value="7" href="#">Vue</a></li>
<li><a class="dropdown-item" data-value="8" href="#">Python</a></li>
</ul>
</div>
<div class="invalid-feedback">Please select a valid tag.</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</div>
<!-- Modal END -->
<footer class="text-muted py-5">
<div class="container">
<p class="mb-1">Footer</p>
</div>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
</body>
</html>
As you can see the script does not work.
To be honest I am not quite sure why. I am guessing there is a problem when initializing the Tags-class. I currently do it the following:
import Tags
Tags.init();
Any suggestions what is wrong or how to correctly call the init()
-function from the Tags-class?
I appreciate your replies!
from Instantiate ES6 class
No comments:
Post a Comment