Многострочные элементы

По умолчанию библиотека Awesomplete формирует список подсказок в виде обычных однострочных элементов <li>. Такой подход подходит для простых автодополнений: имён, городов, тегов, URL-адресов. Однако в реальных интерфейсах часто требуется отображать более сложную структуру:

  • название и описание;
  • заголовок и категорию;
  • товар и цену;
  • имя пользователя и email;
  • код и пояснение;
  • несколько строк форматированного текста.

Для этого используются многострочные элементы списка. Их реализация строится вокруг переопределения функции 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;
}

Hover-состояния для сложных элементов

При использовании вложенной структуры важно стилизовать родительский <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 внутри описаний

При генерации HTML необходимо учитывать безопасность.

Опасный вариант:

innerHTML: item.value.description

Если данные приходят с сервера, возможно внедрение вредоносного кода.

Безопаснее:

textContent: item.value.description

Либо использовать экранирование.


Производительность многострочных списков

Большое количество сложных элементов может ухудшать производительность.

Причины:

  • большое количество DOM-узлов;
  • изображения;
  • сложные CSS-эффекты;
  • тени;
  • анимации;
  • градиенты;
  • частое обновление списка.

Оптимизации:

Минимизация вложенности

Плохо:

<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;
    }

}

Использование CSS Grid

Для сложных карточек удобно применять 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 автоматически поддерживает:

  • стрелки вверх/вниз;
  • Enter;
  • Esc;
  • Tab.

Но при использовании нестандартной верстки необходимо избегать:

overflow: hidden;

у внутренних блоков, если они могут скрывать фокус.


Использование data-атрибутов

Многострочные элементы часто содержат служебную информацию.

Пример:

return Awesomplete.$.create("li", {
    innerHTML: html,
    "data-id": item.value.id,
    "data-category": item.value.category
});

Это удобно для:

  • аналитики;
  • событий;
  • AJAX-запросов;
  • интеграции с backend.

Обработка выбора элемента

После выбора можно использовать дополнительные поля объекта.

replace: function(selected) {

    this.input.value = selected.value.title;

    console.log(selected.value.id);
    console.log(selected.value.category);
}

Создание полноценных карточек в dropdown

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;
}

Проблемы с высотой списка

Если элементы имеют разную высоту, возможны:

  • скачки прокрутки;
  • дёргание списка;
  • смещение hover;
  • нестабильная навигация.

Часто помогает фиксированная минимальная высота:

.awesomplete li {
    min-height: 72px;
}

Виртуализация больших списков

Awesomplete не поддерживает виртуализацию из коробки. При тысячах многострочных элементов возможны:

  • лаги;
  • медленная прокрутка;
  • задержки ввода.

Типичное решение:

  • серверный поиск;
  • debounce;
  • ограничение результатов;
  • ленивые изображения.

Пример debounce:

let timer;

input.addEventListener("input", function() {

    clearTimeout(timer);

    timer = setTimeout(() => {

        loadData(input.value);

    }, 300);

});

Интеграция с AJAX

Многострочные элементы особенно полезны при динамической подгрузке.

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)

Такой подход:

  • улучшает читаемость;
  • упрощает поддержку;
  • снижает дублирование;
  • позволяет переиспользовать шаблоны.