Обработка ответов сервера в Choices.js опирается на строгую нормализацию данных, единый контракт формата и управление асинхронными состояниями, поскольку библиотека изначально рассчитана на работу как со статическими списками, так и с удалёнными источниками данных. При интеграции с API ключевым становится этап преобразования произвольного ответа сервера в структуру, ожидаемую компонентом выбора.
Типовой ответ API для наполнения Choices.js представляет собой массив объектов, где каждый элемент описывает вариант выбора. Базовая структура ориентирована на минимальный набор полей:
[
{
"value": "1",
"label": "Москва"
},
{
"value": "2",
"label": "Санкт-Петербург"
}
]
Choices.js ожидает наличие как минимум value и
label. Однако серверные ответы часто содержат расширенные
данные:
[
{
"id": 10,
"name": "Красный",
"hex": "#ff0000",
"available": true
}
]
В таком случае требуется слой адаптации, преобразующий данные в формат библиотеки:
const normalize = (item) => ({
value: item.id,
label: item.name,
disabled: !item.available,
customProperties: {
hex: item.hex
}
});
Choices.js не навязывает единственный способ загрузки данных,
предоставляя возможность ручного управления через
setChoices. На практике загрузка осуществляется через
fetch, axios или любой другой HTTP-клиент.
const choices = new Choices('#select');
fetch('/api/cities')
.then(res => res.json())
.then(data => {
const formatted = data.map(item => ({
value: item.id,
label: item.name
}));
choices.setChoices(formatted, 'value', 'label', true);
});
Метод setChoices принимает четыре параметра:
Такой подход позволяет полностью заменить содержимое списка при каждом ответе сервера.
Серверные ответы редко соответствуют UI-ожиданиям напрямую. Нормализация становится центральным этапом обработки.
Часто встречающиеся трансформации:
id → value,
title → label)Пример обработки вложенных данных:
{
"items": [
{
"meta": {
"id": 5,
"title": "Категория A"
}
}
]
}
Преобразование:
const transformResponse = (response) =>
response.items.map(item => ({
value: item.meta.id,
label: item.meta.title
}));
Нормализация часто выделяется в отдельный слой, чтобы отделить API-логику от UI-компонента.
При работе с удалёнными источниками важно учитывать состояния загрузки. Choices.js предоставляет встроенные текстовые индикаторы, которые могут быть переопределены:
const choices = new Choices('#select', {
loadingText: 'Загрузка...',
noResultsText: 'Ничего не найдено',
noChoicesText: 'Нет доступных вариантов'
});
Асинхронный процесс обычно включает три стадии:
Пример управления состоянием:
choices.setChoices([], 'value', 'label', true);
fetch('/api/items')
.then(res => res.json())
.then(data => {
choices.setChoices(
data.map(x => ({ value: x.id, label: x.name })),
'value',
'label',
true
);
});
Сетевые запросы требуют устойчивой обработки ошибок, поскольку Choices.js не управляет транспортным уровнем.
Базовый паттерн обработки:
fetch('/api/items')
.then(res => {
if (!res.ok) {
throw new Error('Network error');
}
return res.json();
})
.then(data => {
choices.setChoices(
data.map(i => ({ value: i.id, label: i.name })),
'value',
'label',
true
);
})
.catch(() => {
choices.clearChoices();
});
Расширенные сценарии включают:
Fallback-логика:
.catch(() => {
choices.setChoices(
[{ value: 'error', label: 'Данные недоступны', disabled: true }],
'value',
'label',
true
);
});
При больших наборах данных сервер часто возвращает результаты порциями. Choices.js не реализует пагинацию автоматически, поэтому механизм строится поверх API.
Пример запроса с параметрами:
let page = 1;
const loadPage = () => {
fetch(`/api/items?page=${page}`)
.then(res => res.json())
.then(data => {
const formatted = data.items.map(i => ({
value: i.id,
label: i.name
}));
choices.setChoices(formatted, 'value', 'label', false);
page += 1;
});
};
Здесь важно отличие последнего параметра setChoices:
false позволяет добавлять элементы, а не заменять
ихЭто формирует основу для бесконечной прокрутки или догрузки при поиске.
Серверная фильтрация используется вместо клиентской при больших объёмах данных.
Типовой сценарий:
const search = (query) => {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
choices.setChoices(
data.map(item => ({
value: item.id,
label: item.title
})),
'value',
'label',
true
);
});
};
Choices.js предоставляет событие поиска, которое может быть связано с этим запросом:
const choices = new Choices('#select', {
searchEnabled: true
});
document.querySelector('#select').addEventListener('search', (e) => {
search(e.detail.value);
});
При частых запросах применяется дебаунсинг:
let timer;
const debouncedSearch = (value) => {
clearTimeout(timer);
timer = setTimeout(() => {
search(value);
}, 300);
};
Повторяющиеся запросы к серверу создают избыточную нагрузку. Кэширование решает проблему повторной загрузки одинаковых данных.
Простейшая реализация:
const cache = new Map();
const fetchCached = (url) => {
if (cache.has(url)) {
return Promise.resolve(cache.get(url));
}
return fetch(url)
.then(res => res.json())
.then(data => {
cache.set(url, data);
return data;
});
};
Интеграция с Choices.js:
fetchCached('/api/items')
.then(data => {
choices.setChoices(
data.map(i => ({ value: i.id, label: i.name })),
'value',
'label',
true
);
});
Некоторые API возвращают данные с метаинформацией, пагинацией и вложенными объектами одновременно:
{
"meta": {
"total": 100,
"page": 1
},
"results": [
{
"id": 1,
"title": "Элемент"
}
]
}
Обработка:
const transform = (response) => response.results.map(item => ({
value: item.id,
label: item.title
}));
Дополнительные метаданные могут использоваться для управления UI:
if (response.meta.total > 100) {
// включение режима пагинации
}
Choices.js позволяет многократно обновлять список без пересоздания инстанса. Это критично при работе с серверными источниками, где данные меняются в зависимости от контекста пользователя.
const updateChoices = async (params) => {
const res = await fetch(`/api/items?filter=${params}`);
const data = await res.json();
choices.setChoices(
data.map(i => ({
value: i.id,
label: i.name
})),
'value',
'label',
true
);
};
Такой подход формирует реактивную модель: сервер становится источником истины, а Choices.js — только слоем отображения и взаимодействия.
При асинхронных обновлениях возможны состояния гонки запросов. Более поздний запрос может завершиться раньше предыдущего, перезаписав актуальные данные.
Защита через токен запроса:
let requestId = 0;
const load = async () => {
const currentId = ++requestId;
const res = await fetch('/api/items');
const data = await res.json();
if (currentId !== requestId) return;
choices.setChoices(
data.map(i => ({ value: i.id, label: i.name })),
'value',
'label',
true
);
};
Такой механизм предотвращает устаревшие обновления интерфейса и обеспечивает согласованность отображаемых данных с последним запросом.