Библиотеки Choices.js и Tom Select решают схожую задачу: расширение
возможностей <select> и <input> с
поддержкой поиска, тегов, асинхронной загрузки и кастомного отображения
элементов. Однако архитектура, API и модель расширения у них
различаются.
Наиболее частые причины перехода на Tom Select:
Tom Select особенно хорошо подходит для сложных административных интерфейсов, CRM-систем, каталогов и приложений с динамической загрузкой данных.
Choices.js предоставляет относительно высокоуровневый API. Основной акцент сделан на:
Типичная инициализация:
const choices = new Choices('#tags', {
removeItemButton: true,
searchEnabled: true
});
Tom Select построен иначе:
Пример:
const select = new TomSelect('#tags', {
plugins: ['remove_button'],
create: true
});
<link rel="stylesheet" href="choices.min.css">
<script src="choices.min.js"></script>
<link rel="stylesheet" href="tom-select.css">
<script src="tom-select.complete.min.js"></script>
Полная сборка complete включает встроенные плагины.
const choices = new Choices('#country', {
searchEnabled: true,
itemSelectText: ''
});
const select = new TomSelect('#country', {
create: false
});
В Tom Select многие функции активны по умолчанию, поэтому часть параметров просто исчезает.
<select id="skills" multiple>
<option value="js">JavaScript</option>
<option value="php">PHP</option>
</select>
new Choices('#skills', {
removeItemButton: true
});
new TomSelect('#skills', {
plugins: ['remove_button']
});
В Tom Select удаление элементов реализовано через плагин.
new Choices('#tags', {
addItems: true,
addItemFilter: value => value.length > 2
});
new TomSelect('#tags', {
create: input => {
if (input.length < 3) {
return false;
}
return {
value: input,
text: input
};
}
});
В Tom Select создание опций может возвращать полноценный объект.
choices.setChoices([
{ value: '1', label: 'Red' },
{ value: '2', label: 'Green' }
], 'value', 'label', true);
new TomSelect('#colors', {
options: [
{ value: '1', text: 'Red' },
{ value: '2', text: 'Green' }
]
});
choices.setChoices(newData, 'value', 'label', false);
select.addOption({
value: '3',
text: 'Blue'
});
select.refreshOptions(false);
В Tom Select обновление выпадающего списка обычно выполняется вручную.
const values = choices.getValue(true);
const values = select.getValue();
Для multiple возвращается массив, для одиночного выбора
— строка.
choices.removeActiveItems();
select.clear();
choices.destroy();
select.destroy();
API здесь практически идентичен.
fetch('/api/tags')
.then(response => response.json())
.then(data => {
choices.setChoices(data, 'value', 'label', true);
});
new TomSelect('#tags', {
valueField: 'id',
labelField: 'name',
searchField: 'name',
load(query, callback) {
fetch(`/api/tags?q=${query}`)
.then(response => response.json())
.then(data => callback(data))
.catch(() => callback());
}
});
Tom Select предоставляет встроенный механизм асинхронной загрузки
через load().
{
value: 'js',
label: 'JavaScript'
}
{
value: 'js',
text: 'JavaScript'
}
Одно из самых распространённых изменений при миграции — замена
label на text.
Tom Select позволяет использовать любые имена свойств.
new TomSelect('#users', {
valueField: 'id',
labelField: 'username',
searchField: ['username', 'email']
});
Это особенно полезно при интеграции с REST API.
element.addEventListener('addItem', event => {
console.log(event.detail.value);
});
select.on('item_add', value => {
console.log(value);
});
| Choices.js | Tom Select |
|---|---|
| addItem | item_add |
| removeItem | item_remove |
| search | type |
| showDropdown | dropdown_open |
| hideDropdown | dropdown_close |
| highlightItem | item_select |
new Choices('#cities', {
searchEnabled: true,
searchChoices: true
});
new TomSelect('#cities', {
searchField: ['text']
});
Если searchField пустой, поиск отключается:
searchField: []
Choices.js ограничивает возможности кастомизации.
Tom Select предоставляет полноценную систему шаблонов.
new TomSelect('#users', {
render: {
option(data, escape) {
return `
<div class="user-option">
<strong>${escape(data.name)}</strong>
<span>${escape(data.email)}</span>
</div>
`;
},
item(data, escape) {
return `
<div class="selected-user">
${escape(data.name)}
</div>
`;
}
}
});
Choices.js в большинстве случаев самостоятельно ограничивает опасный HTML.
В Tom Select ответственность за безопасность лежит на разработчике.
Правильный вариант:
escape(data.name)
Опасный вариант:
${data.name}
Без экранирования возможны XSS-атаки.
new Choices('#tags', {
duplicateItemsAllowed: false
});
new TomSelect('#tags', {
create: true,
persist: false
});
Для предотвращения дубликатов:
createFilter(value) {
return !this.options[value];
}
Choices.js практически не имеет полноценной плагинной архитектуры.
Tom Select активно использует плагины.
Пример:
new TomSelect('#tags', {
plugins: {
remove_button: {
title: 'Удалить'
},
restore_on_backspace: {}
}
});
| Плагин | Назначение |
|---|---|
| remove_button | Кнопка удаления |
| clear_button | Полная очистка |
| checkbox_options | Чекбоксы |
| drag_drop | Drag & Drop |
| dropdown_header | Заголовок списка |
| no_backspace_delete | Блокировка удаления |
Choices.js использует собственные классы:
.choices {}
.choices__inner {}
.choices__list {}
Tom Select использует другую структуру:
.ts-wrapper {}
.ts-control {}
.ts-dropdown {}
.option {}
.item {}
Наиболее частые проблемы:
.choices__inner {
border-radius: 8px;
}
.ts-control {
border-radius: 8px;
}
new Choices('#country', {
placeholder: true,
placeholderValue: 'Выберите страну'
});
<select placeholder="Выберите страну">
new TomSelect('#country');
Tom Select обычно использует атрибут HTML.
choices.disable();
choices.enable();
select.disable();
select.enable();
choices.showDropdown();
choices.hideDropdown();
select.open();
select.close();
choices.clearChoices();
select.clearOptions();
choices.removeActiveItems();
select.clear();
Tom Select обычно показывает лучшую производительность при:
Однако кастомные шаблоны могут существенно увеличить нагрузку на DOM.
const choices = new Choices('#users', {
removeItemButton: true,
searchEnabled: true,
duplicateItemsAllowed: false
});
fetch('/api/users')
.then(r => r.json())
.then(users => {
choices.setChoices(
users,
'id',
'name',
true
);
});
const select = new TomSelect('#users', {
valueField: 'id',
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button'],
create: false,
load(query, callback) {
fetch(`/api/users?q=${query}`)
.then(r => r.json())
.then(users => callback(users))
.catch(() => callback());
},
onItemAdd(value) {
console.log('Selected:', value);
}
});
Choices.js скрывает значительную часть внутренней логики.
Tom Select предоставляет доступ к:
Пример:
console.log(select.options);
console.log(select.items);
console.log(select.control);
Tom Select создавался как современное развитие Selectize.js.
Поэтому многие проекты мигрируют по цепочке:
Selectize.js → Tom Select
Choices.js → Tom Select
API Selectize.js во многом сохраняется.
label вместо textОшибка:
{
value: 1,
label: 'Admin'
}
Правильно:
{
value: 1,
text: 'Admin'
}
escape()Ошибка:
return `<div>${data.name}</div>`;
Безопасный вариант:
return `<div>${escape(data.name)}</div>`;
refreshOptions()После добавления данных dropdown может не обновиться.
select.addOption(option);
select.refreshOptions(false);
Стили Choices.js и Tom Select нельзя подключать одновременно.
Проблемы:
Ошибка:
load(query) {
fetch(...);
}
Правильно:
load(query, callback) {
fetch(...)
.then(r => r.json())
.then(data => callback(data))
.catch(() => callback());
}
При большом проекте часто используется стратегия поэтапной замены.
Старые компоненты продолжают работать на Choices.js.
Новые формы создаются на Tom Select.
Создаётся слой адаптации:
function createSelect(element, options) {
return new TomSelect(element, options);
}
Удаляется Choices.js и его CSS.
Иногда создают совместимый API:
class ChoicesAdapter {
constructor(selector, options) {
this.instance = new TomSelect(selector, {
plugins: options.removeItemButton
? ['remove_button']
: []
});
}
getValue() {
return this.instance.getValue();
}
destroy() {
this.instance.destroy();
}
}
Это позволяет постепенно переписывать кодовую базу.
Tom Select проще адаптировать к компонентному подходу благодаря:
Однако важно корректно уничтожать экземпляры.
useEffect(() => {
const select = new TomSelect(ref.current);
return () => {
select.destroy();
};
}, []);
При десятках тысяч элементов рекомендуется:
render;Плохой вариант:
option(data) {
return `
<div>
<img src="${data.avatar}">
<div>
<strong>${data.name}</strong>
<p>${data.description}</p>
</div>
</div>
`;
}
Оптимизированный вариант:
option(data, escape) {
return `
<div class="user">
${escape(data.name)}
</div>
`;
}
После миграции обычно тестируют: