Choices.js применяется как слой поверх стандартных
<select> и <input> элементов,
добавляя поиск, фильтрацию, мультиселект и асинхронную подгрузку данных.
Для задачи автодополнения городов библиотека используется в режиме
динамического источника данных, где список вариантов формируется не из
статического массива, а из API геокодинга или сервиса городов.
Базовая структура HTML для поля поиска:
<sel ect id="city-select"></select>
Инициализация Choices.js:
import Choices fr om 'choices.js';
const citySelect = document.getElementById('city-select');
const choices = new Choices(citySelect, {
searchEnabled: true,
shouldSort: false,
placeholder: true,
placeholderValue: 'Введите название города',
itemSelectText: '',
});
На этом этапе компонент уже готов к работе, но пока не содержит данных.
Автодополнение городов строится вокруг трёх ключевых элементов:
Типичный поток выглядит так:
setChoicesВ качестве источника данных часто используется GeoDB Cities API или аналогичные сервисы.
Пример функции запроса:
async function fetchCities(query) {
const response = await fetch(
`https://wft-geo-db.p.rapidapi.com/v1/geo/cities?namePrefix=${encodeURIComponent(query)}&limit=10`,
{
headers: {
'X-RapidAPI-Key': 'YOUR_API_KEY',
'X-RapidAPI-Host': 'wft-geo-db.p.rapidapi.com'
}
}
);
const data = await response.json();
return data.data;
}
Результат содержит массив городов с метаданными: страна, регион, население, координаты.
Choices.js ожидает структуру:
{
value: 'Moscow',
label: 'Moscow, Russia'
}
Функция преобразования:
function mapCitiesToChoices(cities) {
return cities.map(city => ({
value: `${city.city}, ${city.country}`,
label: `${city.city}, ${city.region || city.country}`,
}));
}
Choices.js поддерживает обновление списка через метод
setChoices.
Добавляется обработка ввода:
let debounceTimeout;
citySelect.addEventListener('search', (event) => {
const query = event.detail.value;
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(async () => {
if (query.length < 2) return;
const cities = await fetchCities(query);
const choicesData = mapCitiesToChoices(cities);
choices.setChoices(choicesData, 'value', 'label', true);
}, 300);
});
Ключевые аспекты:
setChoices(..., true) заменяет старые значенияПри работе с API автодополнения важно учитывать:
const cache = new Map();
async function getCities(query) {
if (cache.has(query)) {
return cache.get(query);
}
const result = await fetchCities(query);
cache.set(query, result);
return result;
}
Кэш уменьшает количество запросов и ускоряет интерфейс.
Debounce является базовым решением, но при высокой нагрузке можно использовать throttle-подход:
function throttle(fn, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
}
Choices.js позволяет изменять отображение элементов списка.
Пример расширенного отображения города:
const choices = new Choices(citySelect, {
searchEnabled: true,
callbackOnCreateTemplates: function (template) {
return {
item: (classNames, data) => {
return template(`
<div class="${classNames.item} ${data.highlighted
? classNames.highlightedState
: classNames.itemSelectable}">
<span>${data.label}</span>
</div>
`);
},
choice: (classNames, data) => {
return template(`
<div class="${classNames.item} ${classNames.itemChoice}">
<strong>${data.value}</strong>
</div>
`);
}
};
}
});
Такой подход позволяет выводить:
<select>Choices.js может работать и с <input>:
<input id="city-input" type="text">
const input = document.getElementById('city-input');
const choices = new Choices(input, {
searchEnabled: true,
itemSelectText: '',
});
В этом режиме список формируется полностью динамически, без
предопределённых <option>.
После выбора значения часто требуется извлечь дополнительные данные.
citySelect.addEventListener('change', (event) => {
const selectedCity = event.detail.value;
console.log('Выбран город:', selectedCity);
});
В продвинутых сценариях сюда добавляется:
Если API возвращает широту и долготу, их можно сохранять отдельно:
function mapCitiesToChoices(cities) {
return cities.map(city => ({
value: {
name: `${city.city}, ${city.country}`,
lat: city.latitude,
lon: city.longitude
},
label: `${city.city}, ${city.country}`,
}));
}
Далее:
citySelect.addEventListener('change', (event) => {
const data = event.detail.value;
console.log(data.lat, data.lon);
});
Choices.js предоставляет методы управления:
choices.clearStore();
choices.clearChoices();
choices.removeActiveItems();
Использование при смене контекста поиска:
if (query === '') {
choices.clearChoices();
}
При работе с внешним API неизбежны ошибки:
async function safeFetchCities(query) {
try {
return await fetchCities(query);
} catch (error) {
console.error('Ошибка загрузки городов:', error);
return [];
}
}
UI должен оставаться стабильным даже при отсутствии сети.
Choices.js легко встраивается в формы:
<form id="form">
<select id="city-select" name="city"></select>
<button type="submit">Отправить</button>
</form>
document.getElementById('form').addEventListener('submit', (e) => {
e.preventDefault();
const value = choices.getValue(true);
console.log('Отправка:', value);
});
При масштабировании автодополнения:
limit на APIChoices.js в этом случае выполняет только UI-роль, не участвуя в фильтрации больших массивов.
Иногда требуется комбинировать локальные и удалённые данные:
function hybridSearch(query) {
const localResults = localCities.filter(city =>
city.name.toLowerCase().includes(query.toLowerCase())
);
return fetchCities(query).then(remoteResults => {
return [...localResults, ...remoteResults];
});
}
Choices.js автоматически обрабатывает:
При кастомизации важно не ломать нативные события, иначе нарушается доступность интерфейса.
Автодополнение городов в связке с Choices.js строится как многослойная система:
Такая архитектура обеспечивает масштабируемость и предсказуемое поведение при работе с динамическими географическими данными.