Комбинирование нескольких экземпляров

Комбинирование нескольких экземпляров 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-структуре

При использовании нескольких экземпляров на одной странице важно избегать конфликтов идентификаторов и повторного захвата 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);
  });
}

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