create и его роль в серверной архитектуреВ Tom Select создание новых элементов через пользовательский ввод
реализуется через параметр create, который
активирует режим добавления отсутствующих в списке значений. В
классической конфигурации этот механизм работает исключительно на
клиенте, однако в реальных приложениях почти всегда требуется
синхронизация с сервером.
Ключевая проблема заключается в том, что локально созданный элемент не имеет устойчивого идентификатора, а сервер должен гарантировать уникальность, валидность и сохранение данных в базе.
Основные задачи серверной обработки создания:
Tom Select включает возможность создания новых элементов через простую настройку:
new TomSelect("#select", {
create: true
});
В этом режиме библиотека автоматически добавляет новый элемент, если он отсутствует в списке. Однако без серверной интеграции такой подход ограничен: данные существуют только в памяти браузера.
create с AJAXДля подключения серверной логики используется комбинация
create и событийного перехвата:
new TomSelect("#select", {
create: true,
onOptionAdd: function(value, data) {
// локальный хук, не предназначенный для async-логики
}
});
Но для полноценной серверной обработки используется паттерн с
перехватом создания через create как функцию:
new TomSelect("#select", {
create: function(input, callback) {
fetch("/api/tags/create", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ name: input })
})
.then(res => res.json())
.then(data => {
callback({
value: data.id,
text: data.name
});
})
.catch(() => {
callback(false);
});
}
});
В этой модели:
input — строка, введённая пользователем;callback — функция завершения создания;id и name.Сервер обязан возвращать структурированные данные, иначе Tom Select не сможет корректно интегрировать новый элемент в список.
Пример ответа:
{
"id": 125,
"name": "Новая категория"
}
Клиент преобразует его в:
{
value: 125,
text: "Новая категория"
}
Серверная часть должна обеспечивать проверку и сохранение данных:
app.post("/api/tags/create", async (req, res) => {
const name = req.body.name.trim();
if (!name) {
return res.status(400).json({ error: "Empty value" });
}
const exists = await db.tags.findOne({ name });
if (exists) {
return res.json({
id: exists.id,
name: exists.name
});
}
const created = await db.tags.insert({
name
});
res.json({
id: created.id,
name: created.name
});
});
Одна из ключевых задач — предотвращение создания одинаковых записей при конкурентных запросах.
Используются стратегии:
CREATE UNIQUE INDEX unique_tag_name ON tags(name);
const existing = await db.tags.findOne({ name });
if (existing) return existing;
const result = await db.tags.upsert({
where: { name },
update: {},
create: { name }
});
Tom Select ожидает завершения операции создания через callback. Это означает, что интерфейс блокируется до получения ответа сервера.
Важно учитывать:
Пример обработки ошибок:
create: function(input, callback) {
fetch("/api/tags/create", {
method: "POST",
body: JSON.stringify({ name: input })
})
.then(r => {
if (!r.ok) throw new Error("Server error");
return r.json();
})
.then(data => {
callback({ value: data.id, text: data.name });
})
.catch(() => {
callback(false);
});
}
Если передать false, Tom Select отменяет создание
элемента.
При быстром вводе пользователь может инициировать множественные запросы. Для оптимизации применяется debounce-логика:
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Использование:
const createRemote = debounce((input, callback) => {
fetch("/api/create", {
method: "POST",
body: JSON.stringify({ name: input })
})
.then(r => r.json())
.then(data => callback({ value: data.id, text: data.name }));
}, 300);
load и create одновременноВ сложных сценариях создание новых элементов сочетается с удалённой загрузкой:
new TomSelect("#select", {
load: function(query, callback) {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(callback);
},
create: function(input, callback) {
fetch("/api/create", {
method: "POST",
body: JSON.stringify({ name: input })
})
.then(res => res.json())
.then(data => {
callback({ value: data.id, text: data.name });
});
}
});
В этом случае система работает как гибрид:
load отвечает за поиск существующих значений;create отвечает за добавление новых.Tom Select требует строго определённый формат:
{
value: "unique_id",
text: "Отображаемое имя"
}
Дополнительные поля допускаются, но не используются ядром:
{
value: 42,
text: "Категория",
meta: {
created_at: "2026-01-01"
}
}
При серверном создании возможна ситуация, когда:
Решение заключается в:
Пример защиты:
let creating = false;
create: function(input, callback) {
if (creating) return callback(false);
creating = true;
fetch("/api/create", {
method: "POST",
body: JSON.stringify({ name: input })
})
.then(r => r.json())
.then(data => {
creating = false;
callback({ value: data.id, text: data.name });
})
.catch(() => {
creating = false;
callback(false);
});
}
Часто сервер возвращает уже существующую запись вместо создания новой. Это позволяет поддерживать консистентность данных:
if (existing) {
return res.json({
id: existing.id,
name: existing.name,
created: false
});
}
Клиент может игнорировать флаг created, но он полезен
для аналитики и UI-логики.
Серверная часть обязана фильтровать ввод:
Пример:
const normalized = name
.trim()
.toLowerCase()
.replace(/\s+/g, " ");
Создание новых элементов часто требует контекста пользователя:
fetch("/api/tags/create", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ name: input })
});
На сервере это позволяет:
Серверная обработка создания в связке с Tom Select формирует следующую архитектурную цепочку:
create функции;