Серверная фильтрация в компоненте выбора опций строится вокруг принципа делегирования поиска внешнему API вместо локального перебора массива данных. Такой подход применяется при больших наборах данных, динамических справочниках, каталогах товаров, списках пользователей, географических сущностей и любых источниках, где клиентская загрузка полного набора невозможна или нецелесообразна.
Ключевая особенность заключается в том, что ввод в поле поиска не приводит к фильтрации уже загруженных опций. Вместо этого формируется запрос к серверу, который возвращает уже отфильтрованный набор данных.
Механизм серверного поиска в Choices.js базируется на обработке события ввода и динамическом обновлении списка опций.
Последовательность обработки:
Ключевой элемент архитектуры — отсутствие локальной фильтрации, что снимает нагрузку с клиента и переносит вычисления на сервер.
Для реализации серверной фильтрации используется режим загрузки данных через callback-функцию или пользовательский загрузчик.
Типовая конфигурация:
const choices = new Choices('#select', {
searchEnabled: true,
shouldSort: false,
placeholderValue: 'Поиск...',
searchPlaceholderValue: 'Введите запрос',
loadingText: 'Загрузка...'
});
Однако сама по себе конфигурация не включает серверный режим. Он реализуется через кастомную интеграцию загрузки данных.
Основная логика строится через обработку события поиска:
const select = document.querySelector('#select');
const choices = new Choices(select, {
searchEnabled: true,
shouldSort: false
});
let controller = null;
select.addEventListener('search', async (event) => {
const query = event.detail.value;
if (controller) {
controller.abort();
}
controller = new AbortController();
try {
const response = await fetch(`/api/users?search=${encodeURIComponent(query)}`, {
signal: controller.signal
});
const data = await response.json();
const formatted = data.map(item => ({
value: item.id,
label: item.name
}));
choices.setChoices(formatted, 'value', 'label', true);
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err);
}
}
});
Данная схема обеспечивает полное обновление списка опций на основе серверного ответа.
Серверный API должен возвращать структурированный JSON. Наиболее распространённый формат:
[
{
"id": 1,
"name": "Александр Пушкин"
},
{
"id": 2,
"name": "Лев Толстой"
}
]
Допустимы расширенные структуры:
{
"items": [
{ "id": 1, "name": "Москва", "group": "Россия" },
{ "id": 2, "name": "Минск", "group": "Беларусь" }
]
}
В этом случае требуется дополнительная нормализация перед передачей в компонент.
Перед передачей данных в Choices.js выполняется приведение структуры к формату опций:
function normalize(items) {
return items.map(item => ({
value: item.id,
label: item.name,
customProperties: {
group: item.group
}
}));
}
Дополнительные поля сохраняются в customProperties, что
позволяет использовать их в кастомном рендеринге.
При серверной фильтрации критически важна защита от избыточных запросов. Без ограничения частоты ввода сервер перегружается запросами на каждый символ.
Реализация debounce:
function debounce(fn, delay) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
Применение:
const onSea rch = debounce(async (query) => {
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
choices.setChoices(normalize(data), 'value', 'label', true);
}, 300);
Оптимальное значение задержки обычно находится в диапазоне 200–500 мс.
При быстром вводе возможна ситуация гонки запросов, когда более старый ответ приходит позже нового и перезаписывает актуальные данные.
Для решения применяется AbortController:
let controller;
async function fetchData(query) {
if (controller) controller.abort();
controller = new AbortController();
const res = await fetch(`/api/search?q=${query}`, {
signal: controller.signal
});
return res.json();
}
Это гарантирует, что только последний запрос влияет на состояние списка.
Серверная фильтрация часто сочетается с пагинацией.
Пример API:
/api/search?q=alex&page=1&limit=20
Обработка:
async function loadPage(query, page = 1) {
const res = await fetch(`/api/search?q=${query}&page=${page}`);
const data = await res.json();
return {
items: normalize(data.items),
hasMore: data.hasMore
};
}
При необходимости реализуется кнопка «показать ещё», добавляющая
новые элементы через setChoices с флагом false
(append mode).
Вместо полной перерисовки списка может использоваться добавление:
choices.setChoices(newItems, 'value', 'label', false);
Разница режимов:
true — полная замена спискаfalse — добавление к существующим даннымДля серверного поиска чаще используется режим замены, для пагинации — режим добавления.
Сервер может возвращать пустой массив, что требует корректного отображения состояния:
if (!data.length) {
choices.clearChoices();
choices.setChoices([
{ value: '', label: 'Ничего не найдено', disabled: true }
]);
}
Важно исключать возможность выбора служебного элемента.
Для уменьшения нагрузки на сервер применяется локальное кэширование результатов поиска:
const cache = new Map();
async function cachedSearch(query) {
if (cache.has(query)) {
return cache.get(query);
}
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
cache.set(query, data);
return data;
}
Кэш особенно эффективен при повторяющихся запросах (например, удаление и повторный ввод символов).
При нестабильном соединении требуется отображение fallback-состояний:
try {
const data = await fetchData(query);
choices.setChoices(normalize(data), 'value', 'label', true);
} catch (e) {
choices.setChoices([
{ value: '', label: 'Ошибка загрузки', disabled: true }
]);
}
Дополнительно возможно сохранение последнего успешного состояния списка.
Серверная фильтрация снижает нагрузку на клиент, но требует оптимизации сетевого слоя:
Особое значение имеет индексирование полей поиска в базе данных.
При построении серверной фильтрации необходимо учитывать:
Пример безопасной серверной обработки:
app.get('/api/search', async (req, res) => {
const q = String(req.query.q || '').slice(0, 50);
const results = await db.users.find({
name: { $regex: q, $options: 'i' }
});
res.json(results);
});
В некоторых сценариях часть данных загружается заранее, а остальная часть подтягивается динамически.
const initial = [
{ value: 0, label: 'Без выбора' }
];
choices.setChoices(initial, 'value', 'label', true);
Далее серверные результаты дополняют или заменяют список.
Серверная логика может включать:
Пример запроса:
/api/search?q=al&sort=popularity®ion=eu
Серверная фильтрация в Choices.js представляет собой асинхронный поток данных, где пользовательский ввод трансформируется в последовательность запросов, а результатом становится динамически обновляемый список опций без локальной фильтрации и без предварительной загрузки полного набора данных.