XSS атаки и защита

Awesomplete — лёгкая JavaScript-библиотека для реализации автодополнения в текстовых полях ввода. Она создаёт выпадающий список подсказок на основе введённых пользователем символов и позволяет быстро выбирать подходящие варианты из заранее подготовленного или динамически загружаемого набора данных.

Основная идея библиотеки заключается в минимализме. В отличие от крупных UI-фреймворков, Awesomplete содержит только необходимую функциональность для построения автокомплита и практически не навязывает структуру приложения.

Типичный процесс работы выглядит следующим образом:

  1. Пользователь вводит текст.
  2. Библиотека анализирует текущее значение поля.
  3. Выполняется фильтрация списка элементов.
  4. Формируется список совпадений.
  5. Отображаются наиболее релевантные результаты.
  6. Пользователь выбирает подходящий вариант.
  7. Значение автоматически вставляется в поле ввода.

Подключение библиотеки

Подключение состоит из двух компонентов:

  • JavaScript-файла;
  • CSS-файла.

Пример подключения через локальные файлы:

<link rel="stylesheet" href="awesomplete.css">

<input id="search">

<script src="awesomplete.js"></script>

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

const input = document.querySelector("#search");

const awesomplete = new Awesomplete(input, {
    list: [
        "JavaScript",
        "TypeScript",
        "Python",
        "PHP",
        "Java"
    ]
});

После инициализации поле начинает автоматически отображать совпадающие варианты.


Автоматическая инициализация через HTML

Awesomplete поддерживает декларативный подход.

Достаточно указать специальный класс:

<input
    class="awesomplete"
    data-list="JavaScript,TypeScript,Python,PHP">

После загрузки страницы библиотека самостоятельно создаст экземпляр компонента.

Такой подход удобен для небольших проектов и статических страниц.


Создание экземпляра вручную

Более гибким считается программный способ инициализации.

const input = document.querySelector("#city");

const autocomplete = new Awesomplete(input);

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

autocomplete.list = [
    "Almaty",
    "Astana",
    "Karaganda",
    "Shymkent"
];

Преимущество заключается в возможности динамически изменять источник данных в любой момент работы приложения.


Источники данных

Массив строк

Самый распространённый вариант.

new Awesomplete(input, {
    list: [
        "Apple",
        "Samsung",
        "Xiaomi",
        "Huawei"
    ]
});

Каждый элемент массива представляет отдельную подсказку.


Строка со значениями

Разрешается передавать строку.

new Awesomplete(input, {
    list: "HTML,CSS,JavaScript,TypeScript"
});

Библиотека автоматически преобразует строку в набор элементов.


Элемент datalist

Awesomplete может использовать стандартный HTML-элемент datalist.

<input id="language">

<datalist id="languages">
    <option>JavaScript</option>
    <option>Python</option>
    <option>Java</option>
    <option>Go</option>
</datalist>
new Awesomplete("#language", {
    list: "#languages"
});

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


Настройка минимального количества символов

По умолчанию список может открываться после ввода первого символа.

Параметр minChars определяет минимальную длину строки поиска.

new Awesomplete(input, {
    minChars: 3
});

Теперь подсказки появятся только после ввода трёх символов.

Это полезно при работе с большими объёмами данных.


Ограничение количества результатов

Параметр maxItems задаёт максимальное число отображаемых вариантов.

new Awesomplete(input, {
    maxItems: 5
});

Даже если найдено двадцать совпадений, пользователю будут показаны только первые пять.


Автоматическое выделение первого результата

Свойство autoFirst включает автоматическую фокусировку первого элемента списка.

new Awesomplete(input, {
    autoFirst: true
});

После открытия выпадающего списка первый вариант будет сразу активен для выбора клавишей Enter.


Обновление данных во время работы

Список может изменяться динамически.

autocomplete.list = [
    "React",
    "Vue",
    "Angular",
    "Svelte"
];

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


Использование объектов вместо строк

Во многих приложениях требуется отображать один текст, а сохранять другой.

Например:

[
    {
        label: "JavaScript",
        value: 1
    },
    {
        label: "Python",
        value: 2
    }
]

Для таких случаев используются функции обработки данных.

new Awesomplete(input, {
    list: data
});

Далее настраиваются собственные методы фильтрации и отображения.


Механизм фильтрации

Awesomplete предоставляет встроенную функцию фильтрации.

По умолчанию выполняется поиск совпадений по введённому тексту.

Пример пользовательского фильтра:

new Awesomplete(input, {
    filter: function(text, inputValue) {
        return text.startsWith(inputValue);
    }
});

Теперь будут отображаться только элементы, начинающиеся с введённой последовательности символов.


Поиск без учёта регистра

Для большинства интерфейсов важна нечувствительность к регистру.

filter: function(text, inputValue) {
    return text
        .toLowerCase()
        .includes(inputValue.toLowerCase());
}

Такой подход обеспечивает более удобный пользовательский опыт.


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

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

filter(item, inputValue) {
    return (
        item.name.includes(inputValue) ||
        item.code.includes(inputValue)
    );
}

Подобный механизм часто применяется в каталогах товаров и справочниках.


Сортировка результатов

После фильтрации найденные элементы могут сортироваться.

sort: function(a, b) {
    return a.localeCompare(b);
}

Сортировка выполняется перед отображением списка.


Пользовательские алгоритмы сортировки

Например, можно поднимать наиболее точные совпадения вверх.

sort: function(a, b) {
    return a.length - b.length;
}

Короткие совпадения окажутся выше длинных.


Настройка отображения элементов

Метод item отвечает за формирование HTML-разметки каждого результата.

item: function(text) {
    const li = document.createElement("li");

    li.textContent = text;

    return li;
}

Функция должна возвращать DOM-элемент.


Выделение найденного текста

Часто совпадающая часть строки подсвечивается.

item: function(text, inputValue) {
    const li = document.createElement("li");

    li.innerHTML = text.replace(
        new RegExp(inputValue, "gi"),
        "<mark>$&</mark>"
    );

    return li;
}

Подсветка делает поиск более наглядным.


Пользовательское заполнение поля

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

replace: function(text) {
    this.input.value = text.toUpperCase();
}

В приведённом примере выбранное значение преобразуется в верхний регистр.


Асинхронная загрузка данных

Одним из наиболее востребованных сценариев является работа с сервером.

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

    const response = await fetch(
        "/api/search?q=" + input.value
    );

    const data = await response.json();

    awesomplete.list = data;
});

После получения ответа список автоматически обновляется.


Защита от избыточных запросов

Частые обращения к серверу могут создавать лишнюю нагрузку.

Поэтому обычно применяется debounce.

function debounce(fn, delay) {

    let timer;

    return function(...args) {

        clearTimeout(timer);

        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);

    };
}

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

input.addEventListener(
    "input",
    debounce(loadData, 300)
);

Теперь запросы будут отправляться не чаще одного раза за 300 миллисекунд.


Работа с событиями

Awesomplete генерирует набор специальных событий.

Наиболее важные:

  • awesomplete-open;
  • awesomplete-close;
  • awesomplete-highlight;
  • awesomplete-select;
  • awesomplete-selectcomplete.

Открытие списка

input.addEventListener(
    "awesomplete-open",
    function() {
        console.log("Список открыт");
    }
);

Закрытие списка

input.addEventListener(
    "awesomplete-close",
    function() {
        console.log("Список закрыт");
    }
);

Выбор элемента

input.addEventListener(
    "awesomplete-select",
    function(event) {

        console.log(
            event.text.value
        );

    }
);

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


Завершение выбора

input.addEventListener(
    "awesomplete-selectcomplete",
    function(event) {

        console.log(
            event.text.value
        );

    }
);

На этом этапе значение уже вставлено в поле ввода.


Программное открытие списка

Иногда требуется управлять компонентом вручную.

awesomplete.open();

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


Программное закрытие списка

awesomplete.close();

Выпадающее меню немедленно скроется.


Оценка производительности

При работе с тысячами записей возникают дополнительные требования к оптимизации.

Основные методы повышения производительности:

  • ограничение числа результатов;
  • предварительная индексация данных;
  • серверная фильтрация;
  • использование debounce;
  • кэширование запросов;
  • сокращение объёма DOM-операций.

Для массивов размером в несколько десятков тысяч элементов предпочтительно выполнять поиск на серверной стороне.


Интеграция с REST API

Пример загрузки данных из внешнего сервиса:

async function search(query) {

    const response = await fetch(
        `/api/products?q=${query}`
    );

    return response.json();
}

Подключение:

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

        const results =
            await search(input.value);

        awesomplete.list = results;
    }
);

Такой подход позволяет работать практически с неограниченными объёмами данных.


Кастомизация внешнего вида

Awesomplete создаёт собственную HTML-структуру.

Пример изменения оформления списка:

.awesomplete ul {
    border-radius: 8px;
    box-shadow: 0 5px 20px rgba(0,0,0,.15);
}

Настройка активного элемента:

.awesomplete li[aria-selected="true"] {
    background: #2d6cdf;
    color: white;
}

Оформление полностью контролируется обычными CSS-правилами.


Доступность и клавиатурная навигация

Библиотека поддерживает:

  • перемещение стрелками вверх и вниз;
  • выбор клавишей Enter;
  • закрытие клавишей Escape;
  • работу с атрибутами ARIA;
  • взаимодействие со скринридерами.

Это позволяет использовать компонент в интерфейсах с повышенными требованиями к доступности.


Типичные области применения

Awesomplete часто используется для реализации:

  • поиска товаров;
  • выбора городов;
  • поиска пользователей;
  • выбора тегов;
  • поиска документов;
  • адресных справочников;
  • внутренних корпоративных каталогов;
  • систем управления контентом;
  • административных панелей;
  • CRM-систем;
  • ERP-систем;
  • образовательных платформ.

Благодаря небольшому размеру, гибкой настройке фильтрации, поддержке пользовательского отображения, асинхронной загрузке данных и развитой системе событий Awesomplete остаётся удобным инструментом для создания производительных механизмов автодополнения в современных JavaScript-приложениях.