В основе интеграции с серверной отправкой лежит взаимодействие между
визуальным состоянием компонента и 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"]
Одной из ключевых особенностей является возможность создания новых элементов, которые не существуют в исходном списке.
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 — строка с разделителемСтандартный подход:
<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>.
Проблемы возникают, когда внешний код изменяет значение без уведомления компонента:
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
});
Иногда требуется изменить данные перед отправкой:
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 формируется трёхуровневая модель:
<select> или hidden inputFormData при отправкеКорректная архитектура опирается на то, что источником истины остаётся либо DOM, либо инстанс Tom Select, но не оба одновременно при внешнем ручном изменении значений.