В интерфейсах с использованием Choices.js ключевая задача отправки
данных на сервер заключается в корректной сериализации выбранных
значений и их согласовании с форматом API. Элемент
<select> или <input> под
управлением Choices.js продолжает оставаться стандартным DOM-элементом
формы, что позволяет использовать нативную отправку формы или перехват
данных через JavaScript.
Базовая структура данных зависит от режима работы компонента:
value и labelChoices.js хранит внутреннее представление в виде массива объектов выбора:
[
{ value: '1', label: 'JavaScript', selected: true },
{ value: '2', label: 'TypeScript', selected: true }
]
Для отправки на сервер обычно извлекается только поле
value.
Choices.js предоставляет API экземпляра, через который можно получить текущее состояние выбора:
const choices = new Choices('#skills');
const selected = choices.getValue(true);
Передача аргумента true возвращает упрощённый массив
значений:
['1', '2']
Без аргумента возвращается массив объектов, содержащих дополнительные данные.
В простейшем случае Choices.js не требует дополнительной логики
отправки. Значение синхронизируется с исходным
<select>:
<form action="/api/skills" method="POST">
<select id="skills" name="skills[]" multiple>
<option value="1">JavaScript</option>
<option value="2">TypeScript</option>
</select>
<button type="submit">Send</button>
</form>
Инициализация:
new Choices('#skills');
При отправке формы браузер автоматически сформирует payload:
skills[]=1&skills[]=2
В современных SPA-подходах отправка часто выполняется вручную через
fetch, что позволяет контролировать формат данных и
обработку ответа.
const form = document.querySelector('#form');
const choices = new Choices('#skills');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const payload = {
skills: choices.getValue(true)
};
const response = await fetch('/api/skills', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const result = await response.json();
});
Choices.js часто используется в интерфейсах, где данные отправляются не при сабмите формы, а при каждом изменении выбора.
Для этого используется событие change:
const choices = new Choices('#skills');
const sendUpdate = async (values) => {
await fetch('/api/skills/update', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ skills: values })
});
};
document.querySelector('#skills').addEventListener('change', () => {
sendUpdate(choices.getValue(true));
});
При множественном выборе или поиске в remote-режиме частота запросов может быть высокой. Для оптимизации применяется debounce.
const debounce = (fn, delay) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
};
const sendUpdate = debounce((values) => {
fetch('/api/skills/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ skills: values })
});
}, 300);
Некоторые серверные API ожидают multipart/form-data. В
этом случае используется FormData.
const formData = new FormData();
const values = choices.getValue(true);
values.forEach(v => {
formData.append('skills[]', v);
});
fetch('/api/skills', {
method: 'POST',
body: formData
});
Преимущество данного подхода заключается в автоматической обработке сервером массивов и совместимости с файловыми загрузками.
Choices.js позволяет хранить не только простые значения, но и сложные структуры:
new Choices('#users', {
choices: [
{ value: '1', label: 'Alex', customProperties: { role: 'admin' } },
{ value: '2', label: 'Maria', customProperties: { role: 'editor' } }
]
});
При отправке можно включать дополнительные поля:
const data = choices.getValue().map(item => ({
id: item.value,
role: item.customProperties.role
}));
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ users: data })
});
В сценариях с динамическими данными Choices.js часто работает совместно с API поиска:
const choices = new Choices('#city', {
searchEnabled: true
});
document.querySelector('#city').addEventListener('search', async (e) => {
const query = e.detail.value;
const response = await fetch(`/api/cities?q=${query}`);
const data = await response.json();
choices.setChoices(data, 'id', 'name', true);
});
Здесь сервер возвращает данные в формате:
[
{ "id": "10", "name": "Almaty" },
{ "id": "11", "name": "Astana" }
]
Различные серверные платформы требуют разной структуры данных:
{
"skills": ["js", "ts"]
}
skills[]=js&skills[]=ts
{
"input": {
"skills": ["js", "ts"]
}
}
Choices.js не навязывает формат, поэтому преобразование выполняется на уровне JavaScript перед отправкой.
При отсутствии выбора важно контролировать поведение payload:
const values = choices.getValue(true);
const payload = {
skills: values.length ? values : null
};
Альтернативный вариант — отправка пустого массива:
skills: []
Выбор стратегии зависит от логики серверной валидации.
После успешной отправки сервер может возвращать актуализированные данные, которые синхронизируются с интерфейсом:
const response = await fetch('/api/skills', {
method: 'POST',
body: JSON.stringify({ skills: choices.getValue(true) })
});
const result = await response.json();
choices.clearStore();
choices.setValue(result.skills);
Choices.js не выполняет строгую бизнес-валидацию, поэтому проверка выполняется вручную:
const values = choices.getValue(true);
if (values.length < 2) {
throw new Error('Минимум два значения');
}
Дополнительно может проверяться наличие запрещённых значений:
const forbidden = ['admin'];
const valid = values.filter(v => !forbidden.includes(v));
В сложных формах Choices.js часто используется совместно с другими input-элементами:
const payload = {
username: document.querySelector('#username').value,
skills: choices.getValue(true),
role: document.querySelector('#role').value
};
fetch('/api/user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
Согласованность структуры данных обеспечивает единый контракт между клиентом и сервером.
В защищённых приложениях добавляются токены:
fetch('/api/skills', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf"]').content
},
body: JSON.stringify({
skills: choices.getValue(true)
})
});
Для JWT используется заголовок Authorization:
headers: {
'Authorization': `Bearer ${token}`
}
При интенсивной работе с Choices.js важна минимизация количества запросов:
Пример отправки только изменений:
let previous = [];
const diff = (current) =>
current.filter(x => !previous.includes(x));
document.querySelector('#skills').addEventListener('change', () => {
const current = choices.getValue(true);
const changed = diff(current);
previous = current;
fetch('/api/skills/diff', {
method: 'POST',
body: JSON.stringify({ changed })
});
});