При работе с удалёнными источниками данных Tom Select опирается на функцию загрузки, в которой ключевую роль играют параметры запроса. Именно они определяют структуру URL, формат передаваемых данных, поведение поиска на сервере и возможность расширения логики под конкретный API.
Базовый механизм загрузки строится вокруг функции:
load: function(query, callback) {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => callback(data.items))
}
Здесь query — это строка поиска, формируемая
компонентом, а callback — функция, принимающая результат.
Однако реальная работа с параметрами значительно шире, чем простая
передача q.
В стандартной конфигурации Tom Select использует параметр запроса
q. Он передаётся на сервер как ключ поиска:
/api/search?q=term
Изменение имени параметра выполняется через настройку:
new TomSelect("#select", {
valueField: "id",
labelField: "title",
searchField: "title",
loadThrottle: 300,
load: function(query, callback) {
const url = `/api/search?query=${encodeURIComponent(query)}`;
fetch(url)
.then(res => res.json())
.then(json => callback(json.items))
}
});
В этом примере параметр q заменён на query.
Такое переименование важно при интеграции с API, где используются
нестандартные схемы фильтрации.
В реальных проектах запрос редко ограничивается одной строкой поиска. Чаще требуется передавать дополнительные параметры: тип сущности, язык, статус, фильтры доступа.
Пример расширенного формирования URL:
load: function(query, callback) {
const params = new URLSearchParams({
q: query,
type: "user",
status: "active",
limit: 20
});
fetch(`/api/search?${params.toString()}`)
.then(res => res.json())
.then(json => callback(json.items));
}
Такой подход позволяет централизованно управлять структурой запроса и легко добавлять новые фильтры без изменения логики компонента.
Параметры запроса часто зависят от состояния интерфейса: выбранных фильтров, роли пользователя или контекста формы.
load: function(query, callback) {
const activeOnly = document.querySelector("#activeOnly").checked;
const role = document.querySelector("#role").value;
const params = new URLSearchParams({
q: query,
active: activeOnly ? 1 : 0,
role: role
});
fetch(`/api/search?${params.toString()}`)
.then(res => res.json())
.then(json => callback(json.items));
}
Здесь запрос становится реактивным относительно внешнего состояния интерфейса. Это часто используется в административных панелях и CRM-системах.
При вводе текста пользователь может генерировать десятки запросов в секунду. Для управления нагрузкой используется параметр:
loadThrottle: 300
Он задаёт задержку в миллисекундах между последовательными запросами. Фактически реализуется поведенческий debounce на уровне компонента.
Пример:
new TomSelect("#select", {
valueField: "id",
labelField: "name",
searchField: "name",
loadThrottle: 500,
load: function(query, callback) {
fetch(`/api/items?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(json => callback(json.data));
}
});
При значении 500 запрос отправляется только после паузы
в полсекунды, что снижает нагрузку на сервер и предотвращает гонку
ответов.
При усложнении логики формирования параметров удобно выносить генерацию URL в отдельный слой:
function buildSearchUrl(query, context) {
const params = new URLSearchParams();
params.set("q", query);
params.set("lang", context.lang);
params.set("tenant", context.tenantId);
if (context.onlyActive) {
params.set("active", "1");
}
return `/api/search?${params.toString()}`;
}
Использование в Tom Select:
load: function(query, callback) {
const url = buildSearchUrl(query, {
lang: "ru",
tenantId: "crm_1",
onlyActive: true
});
fetch(url)
.then(res => res.json())
.then(json => callback(json.items));
}
Такой подход отделяет бизнес-логику формирования запроса от UI-компонента.
Некоторые API требуют передачу фильтров в теле запроса. В этом случае используется POST:
load: function(query, callback) {
fetch("/api/search", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
q: query,
limit: 30,
includeArchived: false
})
})
.then(res => res.json())
.then(json => callback(json.items));
}
POST-формат особенно важен при большом количестве параметров или чувствительных данных, которые нежелательно передавать через URL.
Tom Select часто используется внутри форм, где параметры запроса зависят от других полей:
const countrySelect = new TomSelect("#country");
const citySelect = new TomSelect("#city", {
load: function(query, callback) {
const countryId = countrySelect.getValue();
const params = new URLSearchParams({
q: query,
country: countryId
});
fetch(`/api/cities?${params.toString()}`)
.then(res => res.json())
.then(json => callback(json.items));
}
});
В этом сценарии запрос становится зависимым от другого компонента интерфейса, формируя каскадную фильтрацию.
При больших объёмах данных сервер часто возвращает результаты
постранично. В запрос добавляются параметры page и
limit:
load: function(query, callback) {
const params = new URLSearchParams({
q: query,
page: 1,
limit: 50
});
fetch(`/api/search?${params.toString()}`)
.then(res => res.json())
.then(json => callback(json.items));
}
Более сложная версия учитывает динамическое переключение страниц:
load: function(query, callback) {
let page = 1;
const loadPage = () => {
const params = new URLSearchParams({
q: query,
page: page
});
fetch(`/api/search?${params}`)
.then(res => res.json())
.then(json => {
callback(json.items);
if (json.hasMore) {
page++;
}
});
};
loadPage();
}
Не всегда требуется отправлять запрос при любом вводе. Для фильтрации лишних обращений используется:
shouldLoad: function(query) {
return query.length >= 2;
}
Это ограничивает формирование запросов до тех пор, пока пользователь не введёт минимум символов. В связке с параметрами запроса это снижает количество мусорных обращений:
new TomSelect("#select", {
shouldLoad: function(query) {
return query.length > 1;
},
load: function(query, callback) {
const params = new URLSearchParams({
q: query,
minScore: 0.5
});
fetch(`/api/search?${params}`)
.then(res => res.json())
.then(json => callback(json.items));
}
});
Параметры запроса включают не только query string, но и HTTP-заголовки. Это важно при работе с защищёнными API:
load: function(query, callback) {
fetch(`/api/search?q=${encodeURIComponent(query)}`, {
headers: {
"Authorization": `Bearer ${localStorage.getItem("token")}`,
"Accept": "application/json"
}
})
.then(res => res.json())
.then(json => callback(json.items));
}
Заголовки позволяют отделить параметры фильтрации от параметров доступа, сохраняя структуру URL чистой.
Иногда требуется передавать параметры, зависящие от окружения приложения, например multi-tenant системы:
const context = {
tenant: window.APP_TENANT,
locale: navigator.language
};
new TomSelect("#select", {
load: function(query, callback) {
const params = new URLSearchParams({
q: query,
tenant: context.tenant,
locale: context.locale
});
fetch(`/api/search?${params.toString()}`)
.then(res => res.json())
.then(json => callback(json.items));
}
});
Такой механизм позволяет использовать один и тот же компонент в разных окружениях без изменения логики загрузки.
При повторяющихся запросах полезно учитывать параметры как часть ключа кэша:
const cache = new Map();
function getCacheKey(query, params) {
return `${query}:${JSON.stringify(params)}`;
}
load: function(query, callback) {
const extra = { type: "user" };
const key = getCacheKey(query, extra);
if (cache.has(key)) {
callback(cache.get(key));
return;
}
const urlParams = new URLSearchParams({
q: query,
type: extra.type
});
fetch(`/api/search?${urlParams}`)
.then(res => res.json())
.then(json => {
cache.set(key, json.items);
callback(json.items);
});
}
Кэширование становится зависимым не только от строки запроса, но и от всех параметров, влияющих на результат.
Внутренняя логика формирования запроса в Tom Select может быть представлена как последовательность трансформаций:
queryshouldLoadfetch или AJAXcallbackКаждый этап может быть расширен или переопределён, что делает систему гибкой для интеграции с любыми backend-архитектурами.