Библиотека Awesomplete позволяет не только выводить список подсказок, но и гибко управлять отображением каждого элемента. Форматирование текста используется для:
По умолчанию библиотека автоматически выделяет совпадения тегом
<mark>, однако механизм форматирования значительно
шире и позволяет полностью переопределять HTML содержимое элементов.
Базовый список формируется автоматически на основе массива значений:
<input id="languages">
new Awesomplete(document.querySelector("#languages"), {
list: ["JavaScript", "TypeScript", "Python", "Rust"]
});
При вводе текста библиотека создаёт структуру:
<ul>
<li aria-selected="false">
<mark>Java</mark>Script
</li>
</ul>
Совпавшая часть автоматически помещается в тег
<mark>.
Стандартное форматирование работает через внутреннюю функцию
ITEM. Она:
<mark>.Пример:
"JavaScript"
При вводе:
java
Результат:
<mark>Java</mark>Script
Это поведение можно полностью изменить.
Главный механизм форматирования — параметр item.
Он позволяет:
Пример:
new Awesomplete(input, {
list: ["JavaScript", "Java", "TypeScript"],
item: function(text, input) {
const li = document.createElement("li");
li.innerHTML = `
<strong>${text}</strong>
`;
return li;
}
});
Теперь каждый элемент будет содержать тег
<strong>.
Функция получает несколько параметров.
Текст элемента.
item: function(text) {
console.log(text);
}
Текущее значение поля ввода.
item: function(text, input) {
console.log(input);
}
Уникальный идентификатор элемента.
item: function(text, input, item_id) {
console.log(item_id);
}
Элемент можно оформить как полноценный блок интерфейса.
Пример:
new Awesomplete(input, {
list: [
"JavaScript",
"TypeScript",
"Python"
],
item: function(text) {
const li = document.createElement("li");
li.innerHTML = `
<div class="title">${text}</div>
<div class="description">
Язык программирования
</div>
`;
return li;
}
});
Если полностью переопределяется item, автоматическое
выделение перестаёт работать.
Совпадения необходимо форматировать самостоятельно.
item: function(text, input) {
const li = document.createElement("li");
const regex = new RegExp(input, "gi");
const highlighted = text.replace(regex, function(match) {
return `<mark>${match}</mark>`;
});
li.innerHTML = highlighted;
return li;
}
При работе с innerHTML необходимо учитывать
XSS-уязвимости.
Опасный вариант:
li.innerHTML = text;
Если данные приходят с сервера, пользователь может внедрить HTML.
Безопаснее использовать:
li.textContent = text;
Безопасное выделение совпадений часто строится через разделение строки.
Пример:
item: function(text, input) {
const li = document.createElement("li");
const start = text
.toLowerCase()
.indexOf(input.toLowerCase());
if (start === -1) {
li.textContent = text;
return li;
}
const end = start + input.length;
li.append(
text.slice(0, start)
);
const mark = document.createElement("mark");
mark.textContent = text.slice(start, end);
li.append(mark);
li.append(
text.slice(end)
);
return li;
}
Такой подход защищён от вставки вредоносного HTML.
Часто список состоит не из строк, а из объектов.
Пример структуры:
const languages = [
{
label: "JavaScript",
type: "Frontend"
},
{
label: "Python",
type: "Backend"
}
];
new Awesomplete(input, {
list: languages,
item: function(item) {
const li = document.createElement("li");
li.innerHTML = `
<div class="name">
${item.label}
</div>
<div class="type">
${item.type}
</div>
`;
return li;
},
replace: function(item) {
this.input.value = item.label;
}
});
Форматирование часто используется для отображения иконок.
item: function(text) {
const li = document.createElement("li");
li.innerHTML = `
<span class="icon">?</span>
<span class="text">${text}</span>
`;
return li;
}
Подсказки можно визуально группировать.
const items = [
{
label: "Array",
category: "JavaScript"
},
{
label: "SELECT",
category: "SQL"
}
];
item: function(item) {
const li = document.createElement("li");
li.innerHTML = `
<div class="category">
${item.category}
</div>
<div class="label">
${item.label}
</div>
`;
return li;
}
Форматирование удобно совмещать с динамическими CSS-классами.
item: function(item) {
const li = document.createElement("li");
li.classList.add("suggestion");
if (item.popular) {
li.classList.add("popular");
}
li.textContent = item.label;
return li;
}
Можно сохранять дополнительные данные внутри элемента.
item: function(item) {
const li = document.createElement("li");
li.textContent = item.label;
li.dataset.id = item.id;
return li;
}
Длинные элементы часто обрезаются.
.awesomplete li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
Для сложных подсказок используется вертикальная структура.
.awesomplete li {
display: flex;
flex-direction: column;
gap: 4px;
}
Иногда требуется выделять все найденные вхождения.
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;
}
Можно выделять только префикс.
item: function(text, input) {
const li = document.createElement("li");
if (
text.toLowerCase()
.startsWith(input.toLowerCase())
) {
li.innerHTML = `
<mark>${text.slice(0, input.length)}</mark>
${text.slice(input.length)}
`;
}
return li;
}
Для сложного поиска строку можно разбивать на токены.
item: function(text, input) {
const li = document.createElement("li");
const words = input.split(" ");
let result = text;
words.forEach(word => {
const regex = new RegExp(word, "gi");
result = result.replace(regex, function(match) {
return `<mark>${match}</mark>`;
});
});
li.innerHTML = result;
return li;
}
Пример сложной карточки:
const users = [
{
name: "Alex",
role: "Administrator",
avatar: "avatar.png"
}
];
item: function(user) {
const li = document.createElement("li");
li.innerHTML = `
<div class="user-card">
<img
src="${user.avatar}"
class="avatar"
>
<div class="info">
<div class="name">
${user.name}
</div>
<div class="role">
${user.role}
</div>
</div>
</div>
`;
return li;
}
.user-card {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
.name {
font-weight: bold;
}
.role {
font-size: 12px;
color: #666;
}
При большом количестве логики форматирование выносится отдельно.
function createItemTemplate(item) {
return `
<div class="item">
${item.label}
</div>
`;
}
item: function(item) {
const li = document.createElement("li");
li.innerHTML = createItemTemplate(item);
return li;
}
Можно создавать универсальные функции подсветки.
function highlight(text, search) {
const regex = new RegExp(search, "gi");
return text.replace(regex, function(match) {
return `<mark>${match}</mark>`;
});
}
item: function(text, input) {
const li = document.createElement("li");
li.innerHTML = highlight(text, input);
return li;
}
При больших списках сложный HTML может замедлять интерфейс.
Основные причины:
innerHTML;Плохо:
<li>
<div>
<span>
<strong>
JavaScript
</strong>
</span>
</div>
</li>
Лучше:
<li>
<strong>JavaScript</strong>
</li>
Многократные вызовы replace на длинных строках могут
быть дорогими.
Лучше заранее подготавливать данные.
При вставке текста через innerHTML спецсимволы должны
экранироваться.
Пример функции:
function escapeHTML(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
Слишком сложная разметка может ухудшать работу screen reader.
Рекомендуется:
item: function(text) {
const li = document.createElement("li");
li.textContent = text;
li.setAttribute(
"aria-label",
`Подсказка ${text}`
);
return li;
}
.awesomplete ul {
background: #1e1e1e;
color: white;
}
.awesomplete mark {
background: #ff9800;
color: black;
}
.awesomplete li {
transition: background 0.2s;
}
.awesomplete li:hover {
background: rgba(255,255,255,0.1);
}
.awesomplete li[aria-selected="true"] {
background: #2979ff;
color: white;
}
Через item можно фактически превратить стандартный
список в полноценный dropdown-компонент.
Пример:
item: function(product) {
const li = document.createElement("li");
li.innerHTML = `
<div class="product">
<img src="${product.image}">
<div class="content">
<div class="title">
${product.title}
</div>
<div class="price">
${product.price}
</div>
</div>
</div>
`;
return li;
}
Такой подход позволяет использовать Awesomplete не только как обычный autocomplete, но и как основу для сложных интерактивных поисковых интерфейсов.