Обработка FormData

В основе интеграции с серверной отправкой лежит взаимодействие между визуальным состоянием компонента и HTML-формой. Tom Select не заменяет стандартный <select>, а расширяет его поведение, сохраняя совместимость с механизмом отправки формы через FormData.

Каждое выбранное значение в Tom Select синхронизируется с исходным <select multiple> или скрытым полем, в зависимости от конфигурации. При стандартной инициализации библиотека поддерживает актуальное состояние DOM-элемента, что позволяет использовать нативный API браузера:

const formData = new FormData(document.querySelector('form'));

В этом случае значения, выбранные через Tom Select, автоматически попадают в FormData, если они отражены в оригинальном <select>.


Базовая модель синхронизации данных

Tom Select опирается на три ключевых сущности:

  • valueField — значение, отправляемое на сервер
  • labelField — отображаемый текст
  • внутренний массив выбранных элементов

Пример базовой конфигурации:

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

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


Работа с <select multiple> и FormData

При использовании стандартного select:

<form>
  <select id="tags" name="tags[]" multiple>
    <option value="1">JavaScript</option>
    <option value="2">CSS</option>
    <option value="3">HTML</option>
  </select>
</form>

Инициализация:

new TomSelect('#tags', {});

При выборе нескольких значений DOM обновляется:

<option value="1" selected></option>
<option value="3" selected></option>

И при сборе:

new FormData(form).getAll('tags[]');

результат будет:

["1", "3"]

Режим создания новых значений (create)

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

new TomSelect('#tags', {
  create: true
});

При включённом create пользователь может вводить произвольный текст. В этом случае возникает задача: как эти значения попадают в FormData.

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


Контроль формата создаваемых данных

Создаваемые элементы часто необходимо нормализовать:

new TomSelect('#tags', {
  create: function (input) {
    return {
      value: input.trim().toLowerCase(),
      text: input.trim()
    };
  }
});

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


Скрытое поле и кастомная сериализация

В некоторых конфигурациях оригинальный <select> отключается визуально, и используется скрытое поле:

<input type="hidden" name="tags" id="tags">

В этом случае Tom Select полностью управляет значением:

const ts = new TomSelect('#tags', {
  create: true,
  onChange: function (value) {
    document.querySelector('#tags').value = value.join(',');
  }
});

При отправке формы:

new FormData(form).get('tags');

возвращает строку:

"1,2,custom_value"

Работа с массивами значений

При множественном выборе важен формат передачи данных:

  • tags[] — массив значений
  • tags — строка с разделителем
  • JSON-строка — сериализация сложных объектов

Стандартный подход:

<select name="tags[]" multiple></select>

Tom Select автоматически поддерживает этот формат при синхронизации с DOM.


Перехват отправки формы

Для тонкой настройки данных часто используется перехват submit:

form.addEventListener('submit', (e) => {
  e.preventDefault();

  const data = new FormData(form);

  const payload = {
    tags: data.getAll('tags[]'),
    title: data.get('title')
  };

  fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: {
      'Content-Type': 'application/json'
    }
  });
});

Tom Select при этом не требует дополнительных вызовов синхронизации, если используется стандартный <select>.


Несоответствие DOM и внутреннего состояния

Проблемы возникают, когда внешний код изменяет значение без уведомления компонента:

document.querySelector('#tags').value = '1';

В этом случае Tom Select не всегда обновляет внутреннее состояние. Корректный способ:

const ts = new TomSelect('#tags');
ts.setValue(['1']);

Или:

ts.addItem('1');

Обработка сложных объектов

При работе с объектами:

new TomSelect('#users', {
  valueField: 'id',
  labelField: 'name'
});

в DOM сохраняется только id, а полная структура теряется. Поэтому FormData содержит только идентификаторы:

["42", "77"]

Если требуется передача объектов, используется промежуточная сериализация:

const data = new FormData(form);

const payload = {
  users: ts.getValue().map(id => ({
    id,
    role: 'member'
  }))
};

Валидация перед отправкой

Tom Select не выполняет серверную валидацию, но позволяет контролировать состояние:

form.addEventListener('submit', (e) => {
  if (ts.getValue().length === 0) {
    e.preventDefault();
  }
});

Дополнительно можно использовать ограничения:

new TomSelect('#tags', {
  maxItems: 5
});

Динамическое изменение FormData-потока

Иногда требуется изменить данные перед отправкой:

form.addEventListener('submit', (e) => {
  e.preventDefault();

  const data = new FormData(form);

  const tags = ts.getValue();

  data.delete('tags[]');
  tags.forEach(tag => data.append('tags[]', tag));

  fetch('/api', {
    method: 'POST',
    body: data
  });
});

Такой подход используется при сложной бизнес-логике, когда DOM-значения недостаточны.


Автосоздание и серверная синхронизация

При create: true часто требуется синхронизация с сервером:

new TomSelect('#tags', {
  create: function (input, callback) {
    fetch('/api/tags', {
      method: 'POST',
      body: JSON.stringify({ name: input })
    })
      .then(res => res.json())
      .then(data => callback(data));
  }
});

После ответа сервера создаётся полноценная сущность с id, которая затем попадает в FormData.


Очистка и сброс состояния

Сброс формы не всегда полностью очищает Tom Select:

form.reset();
ts.clear();

Без clear() внутреннее состояние может остаться синхронизированным с предыдущими значениями, что приводит к некорректной отправке через FormData.


Особенности сериализации при множественных инстансах

При наличии нескольких селектов важно учитывать, что каждый инстанс управляет своей областью DOM:

const ts1 = new TomSelect('#tags');
const ts2 = new TomSelect('#users');

FormData будет собирать значения независимо, если корректно заданы name атрибуты:

<select name="tags[]" multiple></select>
<select name="users[]" multiple></select>

Обработка пустых значений

Пустые состояния могут по-разному интерпретироваться:

  • отсутствие <option selected>
  • пустой массив в getValue()
  • отсутствие ключа в FormData

Контроль:

const tags = ts.getValue();
if (!tags || tags.length === 0) {
  // логика пустого состояния
}

Итоговая модель взаимодействия

В связке Tom Select + FormData формируется трёхуровневая модель:

  1. Визуальный слой (UI компонента)
  2. DOM-состояние <select> или hidden input
  3. Объект FormData при отправке

Корректная архитектура опирается на то, что источником истины остаётся либо DOM, либо инстанс Tom Select, но не оба одновременно при внешнем ручном изменении значений.