Автодополнение городов

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: '',
});

На этом этапе компонент уже готов к работе, но пока не содержит данных.


Архитектура автодополнения городов

Автодополнение городов строится вокруг трёх ключевых элементов:

  • ввод пользователя
  • асинхронный запрос к API
  • обновление списка Choices.js

Типичный поток выглядит так:

  1. Пользователь вводит текст
  2. Срабатывает обработчик input
  3. Выполняется запрос к API городов
  4. Результаты преобразуются в формат Choices.js
  5. Список обновляется через setChoices

Подключение API геокодинга

В качестве источника данных часто используется 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

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

Ключевые аспекты:

  • debounce предотвращает лишние запросы
  • минимальная длина запроса снижает нагрузку
  • 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);
    }
  };
}

Улучшение UX через кастомные шаблоны

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

В продвинутых сценариях сюда добавляется:

  • загрузка погоды
  • получение координат
  • сохранение в localStorage

Работа с координатами

Если 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 на API
  • серверная фильтрация
  • минимизация payload
  • использование индексированных поисковых сервисов

Choices.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 автоматически обрабатывает:

  • стрелки вверх/вниз
  • Enter для выбора
  • Esc для закрытия списка

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


Итоговая структура поведения

Автодополнение городов в связке с Choices.js строится как многослойная система:

  • UI-слой (Choices.js)
  • слой ввода и debounce
  • слой API запросов
  • слой преобразования данных
  • слой состояния и кэширования

Такая архитектура обеспечивает масштабируемость и предсказуемое поведение при работе с динамическими географическими данными.