Комбинирование нескольких экземпляров Tom Select используется в интерфейсах, где выбор в одном компоненте влияет на состояние другого, либо несколько селектов работают как единая система фильтрации, конфигурации или составного ввода данных. Такая архитектура встречается в формах с зависимыми полями, сложных фильтрах, настройках товаров, географических каскадах и динамических справочниках.
Несколько независимых экземпляров Tom Select могут быть связаны через события изменения значения. Каждый экземпляр остаётся автономным, но изменение одного инициирует обновление другого.
const selectA = new TomSelect("#select-a", {
onChange(value) {
selectB.setValue(value);
}
});
const selectB = new TomSelect("#select-b", {
onChange(value) {
selectA.setValue(value);
}
});
Такой подход быстро приводит к циклическим обновлениям, поэтому требуется контроль состояния обновления.
При взаимной синхронизации необходимо вводить флаг блокировки, предотвращающий рекурсивные вызовы событий.
let syncing = false;
const selectA = new TomSelect("#select-a", {
onChange(value) {
if (syncing) return;
syncing = true;
selectB.setValue(value);
syncing = false;
}
});
const selectB = new TomSelect("#select-b", {
onChange(value) {
if (syncing) return;
syncing = true;
selectA.setValue(value);
syncing = false;
}
});
Такой механизм обеспечивает предсказуемость состояния при двусторонней связи.
Классический сценарий комбинирования — зависимые списки. Выбор в первом экземпляре ограничивает набор опций во втором.
const countrySelect = new TomSelect("#country");
const citySelect = new TomSelect("#city", {
options: []
});
const citiesByCountry = {
kazakhstan: [
{ value: "karaganda", text: "Караганда" },
{ value: "astana", text: "Астана" }
],
turkey: [
{ value: "ankara", text: "Анкара" },
{ value: "istanbul", text: "Стамбул" }
]
};
countrySelect.on("change", (country) => {
const cities = citiesByCountry[country] || [];
citySelect.clear();
citySelect.clearOptions();
citySelect.addOptions(cities);
citySelect.refreshOptions(false);
});
Здесь второй экземпляр полностью пересобирает набор данных на основе выбора первого.
В более сложных интерфейсах один селект управляет режимом загрузки другого: локальные данные, API или гибридный источник.
const modeSelect = new TomSelect("#mode");
const dataSelect = new TomSelect("#data", {
load(query, callback) {
callback([]);
}
});
modeSelect.on("change", (mode) => {
if (mode === "local") {
dataSelect.clearOptions();
dataSelect.addOptions([
{ value: "a", text: "Локальное A" },
{ value: "b", text: "Локальное B" }
]);
}
if (mode === "remote") {
dataSelect.clearOptions();
dataSelect.settings.load = function(query, callback) {
fetch(`/api/items?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(callback);
};
}
dataSelect.refreshOptions(false);
});
Такой подход позволяет динамически менять поведение экземпляра без его пересоздания.
В сценариях фильтрации часто требуется исключать уже выбранные значения в другом селекте.
const selectTagsA = new TomSelect("#tags-a", {
maxItems: 5
});
const selectTagsB = new TomSelect("#tags-b", {
maxItems: 5
});
function syncExclusion() {
const selectedA = selectTagsA.items;
selectTagsB.clearOptions();
selectTagsB.addOptions(allTags.filter(tag => !selectedA.includes(tag.value)));
selectTagsB.refreshOptions(false);
}
selectTagsA.on("change", syncExclusion);
Такая логика применяется для распределения элементов между наборами без дубликатов.
Несколько Tom Select могут использовать один общий массив данных, но отображать его в разных режимах.
const sharedOptions = [
{ value: "1", text: "Опция 1" },
{ value: "2", text: "Опция 2" },
{ value: "3", text: "Опция 3" }
];
const selectX = new TomSelect("#x", {
options: sharedOptions
});
const selectY = new TomSelect("#y", {
options: sharedOptions
});
При таком подходе важно учитывать мутацию массива: изменение данных в одном месте повлияет на все экземпляры.
Для сложных интерфейсов используется централизованный слой управления состоянием, который координирует экземпляры.
const state = {
country: null,
city: null
};
const countrySelect = new TomSelect("#country", {
onChange(value) {
state.country = value;
mediator();
}
});
const citySelect = new TomSelect("#city", {
onChange(value) {
state.city = value;
}
});
function mediator() {
const cities = getCities(state.country);
citySelect.clear();
citySelect.addOptions(cities);
citySelect.refreshOptions(false);
}
Такой подход снижает связанность между экземплярами и упрощает масштабирование логики.
В некоторых архитектурах выгоднее полностью пересоздавать экземпляр, чем обновлять его состояние.
let selectInstance = new TomSelect("#dynamic");
function rebuild(options) {
selectInstance.destroy();
selectInstance = new TomSelect("#dynamic", {
options
});
}
Этот метод используется при радикальной смене структуры данных или конфигурации.
В формах с множественными зависимыми полями часто требуется согласованная валидация и синхронизация.
const roleSelect = new TomSelect("#role");
const permissionSelect = new TomSelect("#permissions");
roleSelect.on("change", (role) => {
const permissions = getPermissionsForRole(role);
permissionSelect.clear();
permissionSelect.addOptions(permissions);
permissionSelect.refreshOptions(false);
});
Такой паттерн позволяет формировать контекстно-зависимые формы без серверной перезагрузки.
При загрузке данных из разных источников требуется синхронизация завершения запросов.
const selectA = new TomSelect("#a", {
load(query, callback) {
fetch(`/api/a?q=${query}`)
.then(r => r.json())
.then(callback);
}
});
const selectB = new TomSelect("#b", {
load(query, callback) {
fetch(`/api/b?q=${query}`)
.then(r => r.json())
.then(callback);
}
});
Promise.all([
fetch("/api/a/default").then(r => r.json()),
fetch("/api/b/default").then(r => r.json())
]).then(([aData, bData]) => {
selectA.addOptions(aData);
selectB.addOptions(bData);
selectA.refreshOptions(false);
selectB.refreshOptions(false);
});
Синхронизация загрузки повышает согласованность состояния интерфейса.
При использовании нескольких экземпляров на одной странице важно избегать конфликтов идентификаторов и повторного захвата DOM-узлов.
document.querySelectorAll(".tom-multi").forEach((el) => {
new TomSelect(el, {
create: true
});
});
Каждый экземпляр работает в своём контексте, даже при одинаковых настройках.
Для слабосвязанных компонентов используется механизм событий DOM.
const selectA = new TomSelect("#a");
const selectB = new TomSelect("#b");
selectA.on("change", (value) => {
document.dispatchEvent(new CustomEvent("select-a-change", {
detail: value
}));
});
document.addEventListener("select-a-change", (e) => {
selectB.setValue(e.detail);
});
Такой подход позволяет масштабировать взаимодействие без прямых ссылок между экземплярами.
При множественных зависимостях важно контролировать последовательность обновлений, чтобы избежать неконсистентного состояния.
function updateChain(value) {
selectA.setValue(value);
requestAnimationFrame(() => {
selectB.setValue(value);
selectC.setValue(value);
});
}
Использование очереди обновлений позволяет сгладить одновременные изменения и уменьшить количество перерисовок интерфейса.