Настройка AJAX запросов

AJAX-загрузка данных в Tom Select строится вокруг механизма динамического запроса данных по мере ввода пользователя. Ключевой точкой интеграции выступает параметр load, который определяет функцию получения данных с удалённого источника и преобразование ответа в формат, понятный компоненту.

Функция load вызывается при вводе текста в поле поиска. Она принимает два аргумента: строку запроса и callback-функцию для возврата результатов.

new TomSelect('#select', {
  valueField: 'id',
  labelField: 'title',
  searchField: 'title',

  load: function(query, callback) {
    fetch(`/api/items?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => {
        callback(data.items);
      })
      .catch(() => {
        callback();
      });
  }
});

Структура ответа должна быть приведена к массиву объектов, где каждый объект содержит как минимум поля, соответствующие valueField и labelField. В противном случае элементы не будут корректно отображаться или идентифицироваться.

Контроль частоты запросов

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

new TomSelect('#select', {
  loadThrottle: 300,
  load: function(query, callback) {
    fetch(`/api/search?q=${query}`)
      .then(res => res.json())
      .then(callback);
  }
});

Значение loadThrottle задаёт минимальный интервал между запросами. Это предотвращает перегрузку сервера при быстром вводе текста.

Дополнительно может использоваться проверка длины запроса:

load: function(query, callback) {
  if (query.length < 2) return callback();

  fetch(`/api/search?q=${query}`)
    .then(res => res.json())
    .then(callback);
}

Отложенная загрузка и preload

Поведение первичной загрузки данных регулируется параметром preload. Возможные значения:

  • true — загрузка сразу при инициализации
  • false — загрузка только по запросу
  • "focus" — загрузка при фокусе на поле
new TomSelect('#select', {
  preload: 'focus',
  load: function(query, callback) {
    fetch('/api/items')
      .then(res => res.json())
      .then(callback);
  }
});

Использование preload: "focus" часто применяется в справочниках и больших справочных списках, где начальная выборка уже полезна пользователю.

Формирование параметров запроса

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

load: function(query, callback) {
  const params = new URLSearchParams({
    search: query,
    limit: 20,
    lang: 'ru'
  });

  fetch(`/api/items?${params.toString()}`)
    .then(res => res.json())
    .then(data => callback(data.items));
}

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

Поддержка пагинации

При больших объёмах данных сервер часто возвращает результаты порциями. Tom Select может работать с пагинацией через расширение логики load.

load: function(query, callback) {
  fetch(`/api/items?q=${query}&page=1`)
    .then(res => res.json())
    .then(data => {
      callback(data.items);
    });
}

Более сложная реализация включает хранение состояния страницы и подгрузку дополнительных данных при прокрутке списка. В этом случае управление страницами выносится в замыкание или внешний стор.

Обработка ответа и трансформация данных

Часто API возвращает данные в формате, отличающемся от ожидаемого. В таких случаях используется промежуточное преобразование.

load: function(query, callback) {
  fetch(`/api/users?search=${query}`)
    .then(res => res.json())
    .then(data => {
      const items = data.results.map(user => ({
        id: user.user_id,
        title: user.full_name
      }));

      callback(items);
    });
}

Такая нормализация позволяет изолировать компонент от структуры backend API.

Обработка ошибок

При работе с сетью важно учитывать возможные сбои. Tom Select ожидает вызов callback даже в случае ошибки, иначе интерфейс может остаться в состоянии загрузки.

load: function(query, callback) {
  fetch(`/api/search?q=${query}`)
    .then(res => {
      if (!res.ok) throw new Error('Network error');
      return res.json();
    })
    .then(data => callback(data.items))
    .catch(() => {
      callback();
    });
}

Дополнительно может использоваться fallback-логика с локальными данными или кэшированием предыдущих результатов.

Кэширование результатов

Для снижения количества запросов применяется кэширование. Оно реализуется вручную внутри load.

const cache = {};

new TomSelect('#select', {
  load: function(query, callback) {
    if (cache[query]) {
      callback(cache[query]);
      return;
    }

    fetch(`/api/search?q=${query}`)
      .then(res => res.json())
      .then(data => {
        cache[query] = data.items;
        callback(data.items);
      });
  }
});

Такой подход особенно эффективен при повторяющихся запросах и ограниченной серверной производительности.

Управление отменой запросов

При быстром вводе текста устаревшие AJAX-запросы могут возвращать данные позже актуальных. Для предотвращения гонок используется AbortController.

let controller = null;

new TomSelect('#select', {
  load: function(query, callback) {
    if (controller) {
      controller.abort();
    }

    controller = new AbortController();

    fetch(`/api/search?q=${query}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => callback(data.items))
      .catch(() => callback());
  }
});

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

Интеграция с заголовками и авторизацией

При работе с защищёнными API необходимо добавление заголовков авторизации.

load: function(query, callback) {
  fetch(`/api/items?q=${query}`, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json'
    }
  })
    .then(res => res.json())
    .then(data => callback(data.items));
}

При обновлении токенов часто требуется динамическое формирование заголовков внутри функции load, а не на уровне инициализации компонента.

Работа с минимальной длиной запроса и фильтрацией

Снижение нагрузки на сервер достигается ограничением минимального количества символов:

new TomSelect('#select', {
  shouldLoad: function(query) {
    return query.length > 2;
  }
});

Комбинация shouldLoad и load позволяет гибко управлять моментом отправки запроса.

Использование кастомных HTTP клиентов

Вместо fetch может использоваться любой HTTP-клиент, например Axios, что упрощает обработку ошибок и интерсепторов.

load: function(query, callback) {
  axios.get('/api/search', {
    params: { q: query }
  })
  .then(res => {
    callback(res.data.items);
  })
  .catch(() => callback());
}

Такая интеграция особенно полезна при сложной инфраструктуре фронтенда.

Синхронизация с серверной логикой поиска

Эффективная работа AJAX в Tom Select зависит от согласованности клиентской и серверной логики. Сервер должен поддерживать:

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

Клиентская часть при этом должна оставаться максимально легковесной, выполняя только трансформацию и отображение данных.

Управление состоянием загрузки

Во время выполнения запроса компонент автоматически переходит в состояние загрузки, однако при кастомных реализациях может потребоваться явное управление UX через callback-пустые состояния.

load: function(query, callback) {
  if (!query) return callback();

  fetch(`/api/search?q=${query}`)
    .then(res => res.json())
    .then(data => {
      if (!data.items.length) return callback();
      callback(data.items);
    });
}

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

Комплексная схема AJAX-интеграции

Комбинированный подход включает:

  • throttle для ограничения частоты запросов
  • кэширование повторных запросов
  • AbortController для отмены устаревших запросов
  • трансформацию данных под формат Tom Select
  • минимальную длину запроса
  • обработку ошибок и fallback

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