Библиотека Awesomplete по умолчанию формирует элементы выпадающего
списка как обычный текст. Каждый пункт представляет собой элемент
<li>, содержимое которого вставляется в DOM без
дополнительной HTML-разметки. Такое поведение безопасно и подходит для
простых сценариев автодополнения, однако в реальных интерфейсах часто
требуется:
Для решения подобных задач в Awesomplete используются переопределяемые функции:
itemreplacefiltersortКлючевую роль при вставке HTML играет именно метод item,
отвечающий за создание DOM-элемента списка.
Стандартный item принимает строку и возвращает элемент
<li>.
Простейший вариант кастомизации выглядит так:
new Awesomplete(input, {
list: ["JavaScript", "TypeScript", "Python"],
item: function(text, input) {
const li = document.createElement("li");
li.innerHTML = `
<strong>${text}</strong>
`;
return li;
}
});
В результате каждый элемент списка будет содержать HTML-тег
<strong>.
При генерации элементов можно использовать два подхода:
li.textContent = text;
Текст вставляется безопасно. HTML-теги экранируются.
li.innerHTML = `<strong>${text}</strong>`;
Строка интерпретируется как HTML.
Именно innerHTML позволяет внедрять полноценную
разметку.
Одно из наиболее популярных применений HTML в Awesomplete — подсветка совпавшей части строки.
new Awesomplete(input, {
list: [
"JavaScript",
"Java",
"TypeScript"
],
item: function(text, input) {
const li = document.createElement("li");
const regex = new RegExp(input, "gi");
li.innerHTML = text.replace(regex, function(match) {
return `<mark>${match}</mark>`;
});
return li;
}
});
Тег <mark> особенно удобен для визуального
выделения совпадений.
mark {
background: gold;
color: black;
}
Такой подход позволяет реализовать поведение, аналогичное поисковым системам.
Awesomplete способен отображать не только строки, но и сложные объекты.
const languages = [
{
name: "JavaScript",
type: "Frontend"
},
{
name: "Node.js",
type: "Backend"
}
];
new Awesomplete(input, {
list: languages,
item: function(item, input) {
const li = document.createElement("li");
li.innerHTML = `
<div class="title">${item.name}</div>
<div class="subtitle">${item.type}</div>
`;
return li;
},
replace: function(item) {
this.input.value = item.name;
}
});
.title {
font-weight: bold;
}
.subtitle {
font-size: 12px;
color: gray;
}
Теперь элементы выпадающего списка становятся похожими на карточки.
HTML внутри элементов позволяет внедрять иконки несколькими способами.
li.innerHTML = `
<span>?</span>
<span>${text}</span>
`;
li.innerHTML = `
<svg width="16" height="16">
<circle cx="8" cy="8" r="6" fill="green"></circle>
</svg>
<span>${text}</span>
`;
li.innerHTML = `
<i class="icon-search"></i>
<span>${text}</span>
`;
Такой подход особенно удобен при использовании:
Awesomplete можно использовать как компонент поиска пользователей, товаров или медиафайлов.
const users = [
{
name: "Alex",
avatar: "avatar1.jpg"
},
{
name: "Maria",
avatar: "avatar2.jpg"
}
];
new Awesomplete(input, {
list: users,
item: function(user) {
const li = document.createElement("li");
li.innerHTML = `
<img src="${user.avatar}" class="avatar">
<span>${user.name}</span>
`;
return li;
},
replace: function(user) {
this.input.value = user.name;
}
});
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 10px;
}
li {
display: flex;
align-items: center;
}
При сложной разметке удобнее выделять шаблоны в отдельные функции.
function renderItem(product) {
return `
<div class="product">
<div class="name">${product.name}</div>
<div class="price">
$${product.price}
</div>
</div>
`;
}
item: function(product) {
const li = document.createElement("li");
li.innerHTML = renderItem(product);
return li;
}
Такой подход делает код более читаемым.
Использование innerHTML удобно, но иногда
предпочтительнее создавать элементы вручную.
item: function(text) {
const li = document.createElement("li");
const strong = document.createElement("strong");
strong.textContent = text;
li.appendChild(strong);
return li;
}
Снижается вероятность XSS-уязвимостей.
Можно точечно управлять:
При сложных интерфейсах иногда уменьшается количество перерасчётов DOM.
HTML-разметка может содержать дополнительные данные.
item: function(product) {
const li = document.createElement("li");
li.dataset.id = product.id;
li.innerHTML = `
<span>${product.name}</span>
`;
return li;
}
input.addEventListener("awesomplete-selectcomplete", function(event) {
console.log(event.text.dataset.id);
});
Метод replace определяет, что именно попадёт в поле
ввода после выбора.
Даже если элемент содержит сложную HTML-разметку, в input обычно вставляется только текст.
replace: function(item) {
this.input.value = item.name;
}
Это важная архитектурная особенность.
Используется для визуального интерфейса.
Используется для логики приложения.
Например:
| Отображение | Значение |
|---|---|
| Карточка товара | ID товара |
| Пользователь с аватаром | username |
| Элемент меню | slug |
Awesomplete можно превратить в визуальный каталог.
li.innerHTML = `
<div class="category">
<span class="icon">
${item.icon}
</span>
<span class="title">
${item.title}
</span>
</div>
`;
При вставке HTML важно не ухудшить доступность интерфейса.
li.setAttribute("role", "option");
li.innerHTML = `
<span class="visually-hidden">
Результат поиска:
</span>
<span>${text}</span>
`;
Использование innerHTML требует осторожности.
li.innerHTML = userInput;
Если строка содержит:
<script>alert(1)</script>
код будет выполнен.
Плохо:
innerHTML = userData;
function escapeHTML(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
element.textContent = userData;
item: function(text) {
const li = document.createElement("li");
const span = document.createElement("span");
span.textContent = text;
li.appendChild(span);
return li;
}
Большое количество сложной HTML-разметки может замедлять:
Особенно затратны:
Плохо:
<div>
<div>
<div>
<span>Item</span>
</div>
</div>
</div>
Лучше:
<span>Item</span>
maxItems: 5
<img loading="lazy">
Awesomplete совместим с современными подходами компонентной архитектуры.
Можно интегрировать:
<template>;<template id="item-template">
<div class="user">
<img class="avatar">
<span class="name"></span>
</div>
</template>
item: function(user) {
const template =
document.querySelector("#item-template");
const clone =
template.content.cloneNode(true);
clone.querySelector(".avatar").src =
user.avatar;
clone.querySelector(".name").textContent =
user.name;
const li = document.createElement("li");
li.appendChild(clone);
return li;
}
Элементы списка могут содержать интерактивные элементы:
li.innerHTML = `
<span>${item.name}</span>
<button class="remove">
Удалить
</button>
`;
Следует учитывать, что Awesomplete управляет:
Из-за этого вложенные элементы могут конфликтовать с основной логикой списка.
button.addEventListener("click", function(event) {
event.stopPropagation();
});
li.innerHTML = `
<a href="/profile/${user.id}">
${user.name}
</a>
`;
Клик по ссылке может:
Подобные сценарии требуют дополнительной настройки событий.
HTML-разметка внутри Awesomplete отлично сочетается с Flexbox.
li {
display: flex;
justify-content: space-between;
align-items: center;
}
li.innerHTML = `
<span>${item.title}</span>
<small>${item.description}</small>
`;
HTML позволяет визуально разделять результаты.
li.innerHTML = `
<div class="group">
${item.group}
</div>
<div class="value">
${item.value}
</div>
`;
.group {
font-size: 11px;
text-transform: uppercase;
opacity: 0.6;
}
if (item.featured) {
li.classList.add("featured");
}
.featured {
background: #fff8d9;
}
li.innerHTML = `
<span>${item.name}</span>
<span class="badge">
NEW
</span>
`;
Типичная структура кастомного HTML-элемента в Awesomplete включает:
Именно переопределение item превращает стандартный
autocomplete в полноценный UI-компонент с гибкой HTML-разметкой и
сложным визуальным поведением.