Динамические формы

Динамические формы в связке с Slim Select предполагают работу с состоянием DOM, которое изменяется во времени: добавляются новые поля выбора, удаляются существующие, обновляются списки опций, пересобираются экземпляры компонентов и синхронизируются значения с моделью данных. Основная сложность заключается в том, что стандартный <select> после инициализации библиотеки перестаёт быть автономной сущностью и требует аккуратного управления жизненным циклом.

Каждый экземпляр Slim Select оборачивает исходный <select> и создает собственную структуру DOM. При динамическом изменении формы важно учитывать три состояния:

  • исходный HTML-элемент
  • инициализированный экземпляр Slim Select
  • актуальное состояние данных (options / value)

При добавлении или удалении элементов формы без корректного обновления экземпляра возникает рассинхронизация между DOM и внутренним состоянием библиотеки.

Базовая инициализация:

const slim = new SlimSelect({
  select: '#city'
});

После этого взаимодействие с <select> через прямую модификацию innerHTML или appendChild не отражается автоматически в интерфейсе Slim Select без обновления состояния.

Динамическое добавление новых select-полей

При генерации формы на лету создаются новые DOM-узлы, после чего для каждого нового элемента требуется отдельная инициализация.

function addSelect(id) {
  const select = document.createElement('select');
  select.id = id;

  document.querySelector('#form').appendChild(select);

  new SlimSelect({
    select: `#${id}`,
    data: [
      { text: 'A', value: 'a' },
      { text: 'B', value: 'b' }
    ]
  });
}

В такой модели каждый select становится независимым экземпляром, а управление ими распределяется по набору инстансов.

При масштабировании формы появляется необходимость хранения ссылок на созданные экземпляры:

const instances = new Map();

function registerSelect(id) {
  instances.set(id, new SlimSelect({
    select: `#${id}`
  }));
}

Удаление динамических элементов

Удаление элемента формы требует синхронного уничтожения связанного экземпляра Slim Select. В противном случае остаются «висячие» обработчики событий и DOM-узлы.

function removeSelect(id) {
  const instance = instances.get(id);

  if (instance) {
    instance.destroy();
    instances.delete(id);
  }

  document.getElementById(id)?.remove();
}

Метод destroy() возвращает select в исходное состояние, удаляя созданную обёртку библиотеки.

Обновление списка опций

В динамических формах часто требуется изменение доступных значений без пересоздания компонента. В таких случаях применяется обновление данных через API экземпляра.

instance.setData([
  { text: 'X', value: 'x' },
  { text: 'Y', value: 'y' }
]);

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

Вариант частичного обновления реализуется через манипуляцию исходным <select>:

const select = document.querySelector('#city');

select.innerHTML = `
  <option value="1">One</option>
  <option value="2">Two</option>
`;

instance.set();

Метод set() инициирует пересканирование DOM и синхронизацию состояния.

Зависимые (каскадные) select-поля

Динамические формы часто включают цепочки зависимых списков: выбор в одном поле определяет содержимое следующего.

const country = new SlimSelect({ select: '#country' });
const city = new SlimSelect({ select: '#city' });

document.querySelector('#country').addEventListener('change', (e) => {
  const value = e.target.value;

  const cities = {
    kz: [
      { text: 'Karaganda', value: 'karaganda' },
      { text: 'Almaty', value: 'almaty' }
    ],
    ru: [
      { text: 'Moscow', value: 'moscow' },
      { text: 'Kazan', value: 'kazan' }
    ]
  };

  city.setData(cities[value] || []);
});

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

Асинхронная загрузка данных

При динамических формах часто применяется загрузка опций с сервера. Slim Select не выполняет запросы самостоятельно, поэтому слой получения данных отделяется.

const userSelect = new SlimSelect({
  select: '#users'
});

async function loadUsers() {
  const res = await fetch('/api/users');
  const data = await res.json();

  userSelect.setData(
    data.map(u => ({
      text: u.name,
      value: u.id
    }))
  );
}

Асинхронная подмена данных приводит к перерасчету интерфейса без повторной инициализации компонента.

Динамическое создание групп полей

Сложные формы состоят из повторяющихся блоков, содержащих собственные select-элементы. При клонировании таких блоков требуется изоляция идентификаторов и экземпляров.

function cloneBlock(template) {
  const clone = template.cloneNode(true);

  const select = clone.querySelector('select');

  const instance = new SlimSelect({
    select
  });

  instances.set(select.name, instance);

  document.querySelector('#container').appendChild(clone);
}

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

Синхронизация с FormData

Динамические формы часто передают данные через FormData, где Slim Select не требует специальной сериализации, так как управляет стандартными <select>.

function serialize(form) {
  const data = new FormData(form);

  return Object.fromEntries(data.entries());
}

При множественном выборе значения автоматически агрегируются в виде повторяющихся ключей, что сохраняется в стандартной модели FormData.

Пересоздание экземпляра при структурных изменениях

В ситуациях, где изменяется структура <select> (например, переключение multiple, изменение атрибутов), применяется полный цикл пересоздания:

instance.destroy();

const newInstance = new SlimSelect({
  select: '#city',
  settings: {
    placeholderText: 'Select value'
  }
});

Такой подход фиксирует состояние компонента в соответствии с новой конфигурацией DOM.

Работа с множественными экземплярами

При большом количестве динамических элементов используется централизованное управление:

const registry = {
  items: new Map(),

  add(id, instance) {
    this.items.set(id, instance);
  },

  remove(id) {
    const inst = this.items.get(id);
    if (inst) inst.destroy();
    this.items.delete(id);
  },

  clear() {
    this.items.forEach(i => i.destroy());
    this.items.clear();
  }
};

Подобная структура снижает вероятность утечек памяти и потери синхронизации состояния при массовых изменениях DOM.

Обновление без потери состояния формы

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

  • сохранение текущих значений
  • обновление options
  • восстановление value при наличии совпадений
const currentValue = instance.selected();

instance.setData(newData);

instance.set(currentValue);

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