Форматирование текста элементов

Библиотека 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. Она:

  1. получает текст элемента;
  2. ищет совпадение;
  3. оборачивает найденный фрагмент в <mark>.

Пример:

"JavaScript"

При вводе:

java

Результат:

<mark>Java</mark>Script

Это поведение можно полностью изменить.


Свойство item

Главный механизм форматирования — параметр item.

Он позволяет:

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

Пример:

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

Функция получает несколько параметров.

text

Текст элемента.

item: function(text) {
    console.log(text);
}

input

Текущее значение поля ввода.

item: function(text, input) {
    console.log(input);
}

item_id

Уникальный идентификатор элемента.

item: function(text, input, item_id) {
    console.log(item_id);
}

Создание HTML-структуры элемента

Элемент можно оформить как полноценный блок интерфейса.

Пример:

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, автоматическое выделение перестаёт работать.

Совпадения необходимо форматировать самостоятельно.


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

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;

Комбинирование textContent и HTML

Безопасное выделение совпадений часто строится через разделение строки.

Пример:

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

Добавление HTML-классов

Форматирование удобно совмещать с динамическими 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;
}

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

Можно сохранять дополнительные данные внутри элемента.

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

Стилизация formatted элементов

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

Переиспользуемые formatter-функции

Можно создавать универсальные функции подсветки.

function highlight(text, search) {

    const regex = new RegExp(search, "gi");

    return text.replace(regex, function(match) {
        return `<mark>${match}</mark>`;
    });
}

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

item: function(text, input) {

    const li = document.createElement("li");

    li.innerHTML = highlight(text, input);

    return li;
}

Производительность форматирования

При больших списках сложный HTML может замедлять интерфейс.

Основные причины:

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

Оптимизация рендеринга

Минимизация DOM

Плохо:

<li>
    <div>
        <span>
            <strong>
                JavaScript
            </strong>
        </span>
    </div>
</li>

Лучше:

<li>
    <strong>JavaScript</strong>
</li>

Снижение количества replace

Многократные вызовы replace на длинных строках могут быть дорогими.

Лучше заранее подготавливать данные.


Работа с HTML-сущностями

При вставке текста через innerHTML спецсимволы должны экранироваться.

Пример функции:

function escapeHTML(str) {

    return str
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;");
}

Форматирование и доступность

Слишком сложная разметка может ухудшать работу screen reader.

Рекомендуется:

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

Использование aria-label

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, но и как основу для сложных интерактивных поисковых интерфейсов.