В Tom Select создание новых значений реализуется через встроенный
механизм create, позволяющий пользователю добавлять
элементы, отсутствующие в текущем списке опций. При активации этого
режима введённая строка преобразуется в объект опции и одновременно
попадает в список выбранных значений (items) и в набор
доступных опций (options), если это предусмотрено
конфигурацией.
Базовая конфигурация создания:
new TomSelect('#select', {
create: true
});
При таком режиме любое уникальное значение, не найденное среди существующих опций, становится новым элементом. Внутренне библиотека формирует объект вида:
{
value: "новое_значение",
text: "новое_значение"
}
и добавляет его в коллекцию опций.
Ключевой момент заключается в том, что созданный элемент не является «временным» — он сразу становится частью состояния компонента, а значит участвует в фильтрации, отображении и сериализации.
Жизненный цикл созданного элемента в Tom Select проходит несколько стадий:
createFiltercreate callback или
встроенный механизмoptionsitemsitem_add и changeВ момент создания Tom Select синхронизирует внутренние структуры:
this.options — полный набор доступных опцийthis.items — выбранные значенияЛюбое несоответствие между этими слоями приводит к рассинхронизации отображения и состояния.
Создание элементов управляется несколькими параметрами:
Определяет возможность добавления новых элементов.
create: true
Также допускается функция:
create: function(input) {
return {
value: input.toLowerCase(),
text: input
};
}
Функция позволяет нормализовать данные до попадания в модель.
Определяет допустимость создания элемента на основе введённого значения.
createFilter: function(input) {
return input.length > 2;
}
Фильтр часто используется для предотвращения создания пустых или некорректных значений.
Автоматическое создание элемента при потере фокуса:
createOnBlur: true
При включении значение из input может быть преобразовано в элемент без явного подтверждения.
Внутренняя модель Tom Select разделяет данные на два слоя:
Созданный элемент одновременно попадает в оба слоя, что важно учитывать при интеграции с внешним источником данных.
Проблема возникает при серверной синхронизации: backend часто хранит только справочник сущностей, тогда как клиент может генерировать новые значения локально.
Создание элементов почти всегда требует интеграции с backend. Основная задача — связать локально созданный элемент с серверной сущностью.
Типовой сценарий:
const ts = new TomSelect('#select', {
create: function(input) {
return {
value: input,
text: input,
temp: true
};
},
onItemAdd: function(value) {
const option = this.options[value];
if (option && option.temp) {
fetch('/api/tags', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: option.text })
})
.then(res => res.json())
.then(data => {
this.updateOption(value, {
value: data.id,
text: data.name
});
this.removeItem(value);
this.addItem(data.id);
});
}
}
});
Оптимистическая синхронизация предполагает немедленное добавление элемента в UI до подтверждения сервером.
Преимущества:
Недостатки:
Пример:
onItemAdd: function(value) {
const option = this.options[value];
if (!option || !option.temp) return;
const tempId = value;
fetch('/api/tags', {
method: 'POST',
body: JSON.stringify({ name: option.text })
})
.then(res => res.json())
.then(data => {
this.updateOption(tempId, {
value: data.id,
text: data.name
});
this.refreshItems();
})
.catch(() => {
this.removeItem(tempId);
});
}
Одной из ключевых проблем является дублирование созданных элементов. Оно возникает при отсутствии строгой нормализации входных данных.
Типовые стратегии:
create: function(input) {
const normalized = input.trim().toLowerCase();
if (this.options[normalized]) return false;
return {
value: normalized,
text: input.trim()
};
}
createFilter: function(input) {
const exists = Object.values(this.options)
.some(opt => opt.text.toLowerCase() === input.toLowerCase());
return !exists;
}
После создания элемент может изменяться: сервер может возвращать исправленные данные, либо требуется обновление отображаемого текста.
Tom Select не имеет прямого «patch»-API, поэтому используется комбинация операций:
updateOptionremoveItemaddItemthis.updateOption(oldValue, {
value: newValue,
text: newText
});
this.removeItem(oldValue);
this.addItem(newValue);
Важно учитывать, что изменение value требует пересборки
items, так как идентификатор используется как ключ.
При инициализации компонента часто требуется восстановить созданные элементы, сохранённые на сервере.
Типовой сценарий:
const initialItems = [
{ id: 1, name: 'JavaScript' },
{ id: 2, name: 'Frontend' }
];
new TomSelect('#select', {
options: initialItems.reduce((acc, item) => {
acc[item.id] = {
value: item.id,
text: item.name
};
return acc;
}, {}),
items: initialItems.map(i => i.id)
});
Если среди сохранённых значений есть пользовательские элементы, не
входящие в справочник, их необходимо предварительно добавить в
options, иначе Tom Select не сможет корректно отобразить их
в списке выбранных элементов.
При использовании remote data (например, через load),
создание элементов требует учёта гонок состояний.
Проблема возникает, когда:
Решение заключается в принудительном сохранении локально созданных значений:
load: function(query, callback) {
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => {
const options = {};
data.forEach(item => {
options[item.id] = {
value: item.id,
text: item.name
};
});
Object.assign(this.options, options);
callback(data);
});
}
Одним из критичных аспектов синхронизации является различие между временными и серверными идентификаторами.
Типовая модель:
tmp_12342При обновлении необходимо гарантировать:
itemsoptionsИгнорирование этого приводит к ситуации, когда визуально элемент существует, но логически недоступен для операций поиска или удаления.
При сбоях синхронизации возможны три стратегии:
Пример мягкого отката:
.catch(() => {
this.updateOption(tempId, {
value: tempId,
text: option.text + ' (не сохранено)'
});
});
При использовании сложных объектов вместо строк требуется явное управление сериализацией:
create: function(input) {
return {
value: crypto.randomUUID(),
text: input,
raw: {
label: input,
createdAt: Date.now()
}
};
}
Такая структура позволяет сохранять метаданные, но требует внешнего
контроля при отправке на сервер, так как Tom Select оперирует только
value и text.
Создание элемента вызывает последовательность событий:
option_additem_addchangeПри синхронизации важно различать их назначение:
option_add — изменение справочникаitem_add — изменение выбранных значенийchange — итоговое состояниеНеправильная реакция на option_add часто приводит к
повторной отправке данных на сервер и циклическим запросам.
При повторной инициализации компонента с уже созданными значениями необходимо избегать повторного создания дубликатов.
Ключевая стратегия — предварительная нормализация
options:
itemsНесоблюдение порядка приводит к конфликтам состояния и потере пользовательских данных.