По умолчанию библиотека Awesomplete формирует список подсказок в виде
обычных однострочных элементов <li>. Такой подход
подходит для простых автодополнений: имён, городов, тегов, URL-адресов.
Однако в реальных интерфейсах часто требуется отображать более сложную
структуру:
Для этого используются многострочные элементы списка. Их реализация
строится вокруг переопределения функции item.
Стандартный элемент Awesomplete создаётся примерно так:
Awesomplete.ITEM = function (text, input, item_id) {
return $.create("li", {
innerHTML: text,
role: "option",
"aria-selected": "false",
id: "awesomplete_item_" + item_id
});
};
Вся визуальная часть определяется содержимым innerHTML.
Если вместо одной строки передать полноценную HTML-структуру, элемент
автоматически станет многострочным.
Базовый пример:
<input id="search">
<script>
const data = [
{
title: "JavaScript",
description: "Язык программирования для веб-разработки"
},
{
title: "Python",
description: "Универсальный язык программирования"
},
{
title: "Rust",
description: "Системный язык с акцентом на безопасность"
}
];
new Awesomplete(document.getElementById("search"), {
list: data,
item: function(item, input, item_id) {
const html = `
<div class="row">
<div class="title">${item.value.title}</div>
<div class="description">
${item.value.description}
</div>
</div>
`;
return Awesomplete.$.create("li", {
innerHTML: html,
role: "option",
id: "awesomplete_item_" + item_id,
"aria-selected": "false"
});
},
replace: function(selected) {
this.input.value = selected.value.title;
}
});
</script>
Для сложных подсказок массив строк становится неудобным:
list: ["JavaScript", "Python", "Rust"]
Гораздо эффективнее использовать объекты:
list: [
{
label: "JavaScript",
category: "Frontend",
popularity: "Высокая"
}
]
Это позволяет:
Чаще всего многострочный элемент состоит из нескольких блоков.
Пример:
<div class="item">
<div class="top">
<span class="title">JavaScript</span>
<span class="category">Frontend</span>
</div>
<div class="bottom">
Язык программирования для браузеров
</div>
</div>
Такой подход упрощает:
.awesomplete ul li {
padding: 12px;
}
.awesomplete .item {
display: flex;
flex-direction: column;
gap: 6px;
}
.awesomplete .top {
display: flex;
justify-content: space-between;
align-items: center;
}
.awesomplete .title {
font-size: 16px;
font-weight: 600;
}
.awesomplete .category {
font-size: 12px;
color: #777;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 4px;
}
.awesomplete .bottom {
font-size: 13px;
color: #555;
line-height: 1.4;
}
Иногда требуется выводить длинные описания.
Пример:
const html = `
<div class="card">
<div class="title">${item.value.title}</div>
<div class="text">
${item.value.description}
</div>
</div>
`;
CSS:
.text {
line-height: 1.5;
white-space: normal;
}
Свойство:
white-space: normal;
особенно важно, поскольку некоторые темы оформления Awesomplete используют:
white-space: nowrap;
что запрещает перенос строк.
Для длинных описаний удобно использовать CSS-обрезку.
.description {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
Такой способ:
Awesomplete не ограничивает содержимое элемента, поэтому внутрь можно добавлять изображения.
const html = `
<div class="user">
<img src="${item.value.avatar}" class="avatar">
<div class="info">
<div class="name">${item.value.name}</div>
<div class="email">${item.value.email}</div>
</div>
</div>
`;
CSS:
.user {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
display: flex;
flex-direction: column;
}
.name {
font-weight: bold;
}
.email {
font-size: 13px;
color: #777;
}
Частый сценарий — поиск товаров.
const products = [
{
title: "MacBook Pro",
price: "$1999",
description: "Apple M3, 16GB RAM"
},
{
title: "Dell XPS",
price: "$1799",
description: "Intel Core Ultra"
}
];
Формирование карточки:
item: function(item, input, item_id) {
const html = `
<div class="product">
<div class="header">
<span class="title">
${item.value.title}
</span>
<span class="price">
${item.value.price}
</span>
</div>
<div class="description">
${item.value.description}
</div>
</div>
`;
return Awesomplete.$.create("li", {
innerHTML: html,
role: "option",
id: "awesomplete_item_" + item_id
});
}
Многострочные элементы могут нарушать геометрию списка.
Полезные ограничения:
.awesomplete ul {
max-height: 400px;
overflow-y: auto;
}
Индивидуальная высота:
.awesomplete li {
min-height: 60px;
}
При использовании вложенной структуры важно стилизовать родительский
<li>.
.awesomplete li:hover {
background: #f4f7ff;
}
.awesomplete li[aria-selected="true"] {
background: #dfe8ff;
}
Не рекомендуется переносить hover-эффекты на внутренние блоки:
.item:hover
иначе клавиатурная навигация станет визуально некорректной.
Awesomplete умеет автоматически выделять совпадения через:
Awesomplete.highlight()
Пример:
item: function(item, input, item_id) {
const title = Awesomplete.highlight(
item.value.title,
input
);
const html = `
<div class="item">
<div class="title">${title}</div>
<div class="description">
${item.value.description}
</div>
</div>
`;
return Awesomplete.$.create("li", {
innerHTML: html,
role: "option",
id: "awesomplete_item_" + item_id
});
}
При генерации HTML необходимо учитывать безопасность.
Опасный вариант:
innerHTML: item.value.description
Если данные приходят с сервера, возможно внедрение вредоносного кода.
Безопаснее:
textContent: item.value.description
Либо использовать экранирование.
Большое количество сложных элементов может ухудшать производительность.
Причины:
Оптимизации:
Плохо:
<div>
<div>
<div>
<span>Text</span>
</div>
</div>
</div>
Лучше:
<div class="item">
<span class="title">Text</span>
</div>
maxItems: 5
Тяжёлые тени:
box-shadow: 0 10px 40px rgba(0,0,0,.3);
могут существенно нагружать интерфейс.
На мобильных устройствах горизонтальная структура может ломаться.
Неподходящий вариант:
.header {
display: flex;
justify-content: space-between;
}
Лучше использовать адаптацию:
@media (max-width: 600px) {
.header {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
}
Для сложных карточек удобно применять Grid.
.card {
display: grid;
grid-template-columns: 60px 1fr auto;
gap: 10px;
}
Пример структуры:
<div class="card">
<img src="avatar.png">
<div class="content">
<div class="name">Alex</div>
<div class="description">
Frontend Developer
</div>
</div>
<div class="status">
Online
</div>
</div>
Высокие элементы влияют на прокрутку списка.
Awesomplete автоматически поддерживает:
Но при использовании нестандартной верстки необходимо избегать:
overflow: hidden;
у внутренних блоков, если они могут скрывать фокус.
Многострочные элементы часто содержат служебную информацию.
Пример:
return Awesomplete.$.create("li", {
innerHTML: html,
"data-id": item.value.id,
"data-category": item.value.category
});
Это удобно для:
После выбора можно использовать дополнительные поля объекта.
replace: function(selected) {
this.input.value = selected.value.title;
console.log(selected.value.id);
console.log(selected.value.category);
}
Awesomplete позволяет превращать выпадающий список в систему карточек.
Пример:
const html = `
<div class="card">
<div class="title">
${item.value.title}
</div>
<div class="meta">
${item.value.author}
</div>
<div class="description">
${item.value.description}
</div>
<div class="footer">
${item.value.tags.join(", ")}
</div>
</div>
`;
CSS:
.card {
display: flex;
flex-direction: column;
gap: 8px;
}
.meta {
font-size: 12px;
color: #888;
}
.footer {
font-size: 11px;
color: #666;
}
Если элементы имеют разную высоту, возможны:
Часто помогает фиксированная минимальная высота:
.awesomplete li {
min-height: 72px;
}
Awesomplete не поддерживает виртуализацию из коробки. При тысячах многострочных элементов возможны:
Типичное решение:
Пример debounce:
let timer;
input.addEventListener("input", function() {
clearTimeout(timer);
timer = setTimeout(() => {
loadData(input.value);
}, 300);
});
Многострочные элементы особенно полезны при динамической подгрузке.
fetch("/search?q=" + input.value)
.then(response => response.json())
.then(data => {
awesomplete.list = data;
});
Каждый объект может содержать:
{
id: 15,
title: "JavaScript",
description: "Frontend language",
icon: "js.png"
}
Для сложной разметки удобно выделить отдельную функцию.
function renderItem(data) {
return `
<div class="item">
<div class="title">
${data.title}
</div>
<div class="description">
${data.description}
</div>
</div>
`;
}
Использование:
innerHTML: renderItem(item.value)
Такой подход: