Механизм удаления выбранных элементов в интерфейсе автодополнения на основе Awesomplete строится вокруг раздельного хранения данных и их визуального представления. Awesomplete сам по себе не реализует «теги» или множественный выбор, поэтому логика удаления всегда относится к внешнему слою, который управляет состоянием выбранных значений.
Основой является массив, в котором фиксируется текущее состояние выбранных элементов:
let selectedItems = [];
Каждое значение, добавленное через автодополнение, попадает в этот массив. При удалении элемент должен быть синхронно удалён как из массива состояния, так и из DOM-представления.
Ключевой принцип: DOM не является источником истины, он лишь
отражает состояние массива selectedItems.
Awesomplete предоставляет событие выбора элемента
awesomplete-selectcomplete, которое используется как точка
добавления значения в список:
input.addEventListener("awesomplete-selectcomplete", function (e) {
const value = e.text.value;
if (!selectedItems.includes(value)) {
selectedItems.push(value);
renderTags();
}
input.value = "";
});
Здесь важно, что Awesomplete завершает ввод, но не управляет коллекцией — это полностью внешняя ответственность.
Для отображения используется контейнер, в который добавляются «теги»:
<div id="tags"></div>
<input id="input" />
Функция отрисовки полностью пересобирает DOM на основе массива:
function renderTags() {
const container = document.getElementById("tags");
container.innerHTML = "";
selectedItems.forEach(item => {
const tag = document.createElement("span");
tag.className = "tag";
const text = document.createElement("span");
text.className = "tag-text";
text.textContent = item;
const removeBtn = document.createElement("button");
removeBtn.className = "tag-remove";
removeBtn.textContent = "×";
removeBtn.addEventListener("click", () => removeItem(item));
tag.appendChild(text);
tag.appendChild(removeBtn);
container.appendChild(tag);
});
}
Ключевая особенность подхода — отсутствие частичных изменений DOM. Перерисовка выполняется целиком, что снижает риск рассинхронизации состояния.
Удаление из списка реализуется через фильтрацию массива:
function removeItem(value) {
selectedItems = selectedItems.filter(item => item !== value);
renderTags();
}
Фильтрация обеспечивает иммутабельное обновление состояния, что упрощает контроль изменений.
При необходимости допускается более строгая модель с индексами, особенно если значения могут повторяться:
function removeItemByIndex(index) {
selectedItems.splice(index, 1);
renderTags();
}
В этом случае при рендере необходимо передавать индекс:
selectedItems.forEach((item, index) => {
removeBtn.addEventListener("click", () => removeItemByIndex(index));
});
Типичный сценарий — удаление последнего элемента при нажатии Backspace, когда поле ввода пустое.
input.addEventListener("keydown", function (e) {
if (e.key === "Backspace" && input.value === "") {
selectedItems.pop();
renderTags();
}
});
Поведение повторяет логику многих tag-input компонентов: пользователь интуитивно ожидает удаление последнего добавленного элемента.
Awesomplete оперирует строковым значением поля ввода и списком подсказок. После удаления элемента важно учитывать два аспекта:
Если список источника статический:
const list = ["Apple", "Banana", "Orange", "Mango"];
фильтрация доступных значений может выполняться динамически:
function getFilteredList() {
return list.filter(item => !selectedItems.includes(item));
}
Инициализация Awesomplete:
const awesomplete = new Awesomplete(input, {
list: getFilteredList()
});
После каждого изменения состояния требуется обновление:
function refreshAwesomplete() {
awesomplete.list = getFilteredList();
}
И вызов вместе с рендером:
function removeItem(value) {
selectedItems = selectedItems.filter(item => item !== value);
renderTags();
refreshAwesomplete();
}
Удаление тесно связано с защитой от повторного добавления. Проверка выполняется до вставки:
input.addEventListener("awesomplete-selectcomplete", function (e) {
const value = e.text.value;
if (selectedItems.indexOf(value) === -1) {
selectedItems.push(value);
renderTags();
refreshAwesomplete();
}
input.value = "";
});
Это гарантирует консистентность списка.
Расширенный сценарий предполагает удаление последнего элемента при потере фокуса или очистке поля:
input.addEventListener("blur", function () {
if (input.value === "" && selectedItems.length === 0) return;
});
Часто применяется более строгая логика, при которой blur не удаляет элементы, но может использоваться для валидации состояния.
При большом количестве выбранных элементов пересоздание DOM может становиться затратным. Альтернативный подход — точечное удаление узлов:
function removeItem(value) {
const index = selectedItems.indexOf(value);
if (index === -1) return;
selectedItems.splice(index, 1);
const container = document.getElementById("tags");
const tagElements = container.querySelectorAll(".tag");
tagElements[index].remove();
}
Однако такой подход требует строгого соответствия порядка массива и DOM, что усложняет поддержку.
Awesomplete допускает использование объектов:
const list = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" }
];
В этом случае удаление должно учитывать поле value:
function removeItem(value) {
selectedItems = selectedItems.filter(item => item.value !== value);
renderTags();
}
И добавление:
input.addEventListener("awesomplete-selectcomplete", function (e) {
const value = e.text.value;
const label = e.text.label;
if (!selectedItems.find(i => i.value === value)) {
selectedItems.push({ value, label });
renderTags();
}
});
Удаление элементов требует учёта нескольких ситуаций:
Операции удаления должны быть безопасными:
if (selectedItems.length === 0) return;
Фильтрация уже обрабатывает этот случай, но при индексном удалении требуется проверка границ.
При работе с неуникальными строками предпочтительнее объектная модель или использование уникальных идентификаторов.
Наиболее устойчивый подход включает три слоя:
Удаление всегда инициирует цепочку:
function removeItem(value) {
selectedItems = selectedItems.filter(item => item !== value);
renderTags();
refreshAwesomplete();
syncHiddenInput();
}
Дополнительно часто используется скрытое поле для отправки данных формы:
function syncHiddenInput() {
document.getElementById("hiddenInput").value =
JSON.stringify(selectedItems);
}
При интенсивном вводе и удалении важно избегать гонок состояния. Решение заключается в том, чтобы не смешивать прямые DOM-операции и изменение массива без единого источника обновления.
Рекомендуемая модель:
function updateState(mutator) {
mutator();
renderTags();
refreshAwesomplete();
syncHiddenInput();
}
Использование:
function removeItem(value) {
updateState(() => {
selectedItems = selectedItems.filter(item => item !== value);
});
}
Удаление выбранных элементов в связке с Awesomplete требует строгого соответствия между:
Любое расхождение этих трёх уровней приводит к рассинхронизации подсказок, появлению дублей и некорректному поведению автодополнения при повторных взаимодействиях.