Checkbox Options в Tom Select реализуются через механизм кастомного
рендеринга опций и расширения поведения multi-select режима, при котором
каждый элемент списка отображается с интерактивным чекбоксом. Этот режим
не является отдельной встроенной сущностью ядра, а формируется через
комбинацию настроек plugins, кастомных шаблонов
render и управления состоянием выбранных значений.
Внутренняя модель выбора в библиотеке основана на двух слоях состояния:
Checkbox-поведение возникает, когда UI layer перестраивает каждую опцию как элемент, содержащий визуальный индикатор состояния выбора.
Ключевым фактором становится не изменение модели данных, а расширение представления каждой опции.
Checkbox-режим всегда опирается на multi-select поведение:
new TomSelect("#select", {
maxItems: null,
plugins: ['remove_button']
});
Однако сам факт multi-select не создаёт чекбоксы. Он лишь позволяет хранить массив значений, что необходимо для синхронизации состояния чекбокса и выбранных элементов.
Основной механизм реализации чекбоксов — переопределение
render.option.
new TomSelect("#select", {
plugins: ['remove_button'],
render: {
option: function(data, escape) {
const selected = this.items.includes(data.value);
return `
<div class="ts-option">
<label class="ts-checkbox">
<input type="checkbox" ${selected ? 'checked' : ''} disabled />
<span class="ts-label">${escape(data.text)}</span>
</label>
</div>
`;
}
}
});
Здесь чекбокс не является управляемым input-элементом в классическом смысле. Он служит визуальным индикатором, а управление выбором остаётся за внутренней логикой селекта.
Важный аспект — синхронизация UI и состояния this.items.
При изменении выбора библиотека пересоздаёт список отображаемых опций,
что приводит к обновлению состояния чекбоксов.
Механизм работает следующим образом:
addItem или
removeItemitemsrefreshOptionsrender.option заново вычисляет
checkedТаким образом чекбокс не хранит состояние самостоятельно, а полностью зависит от модели данных.
Если чекбокс внутри option остаётся интерактивным, возникает конфликт событий: клик по input может не совпадать с логикой выбора Tom Select. Поэтому используется один из двух подходов:
<input type="checkbox" disabled />
Такой подход полностью делегирует управление библиотеке и исключает рассинхронизацию событий.
render: {
option: function(data, escape) {
return `
<div class="ts-option" data-value="${data.value}">
<input type="checkbox" />
<span>${escape(data.text)}</span>
</div>
`;
}
},
onDropdownOpen: function() {
this.dropdown.addEventListener('click', (e) => {
const option = e.target.closest('.ts-option');
if (!option) return;
this.setActiveOption(option);
this.onOptionSelect(option, e);
});
}
Этот вариант сложнее, но позволяет эмулировать поведение нативного списка с чекбоксами.
Визуальная часть checkbox-mode обычно строится поверх стандартных CSS-селекторов:
.ts-option {
display: flex;
align-items: center;
padding: 6px 10px;
}
.ts-checkbox {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.ts-checkbox input[type="checkbox"] {
pointer-events: none;
}
Ключевой момент — отключение pointer-events у input, чтобы клик всегда обрабатывался контейнером опции.
Tom Select использует систему плагинов, где checkbox-логика может быть оформлена как расширение.
Пример кастомного плагина:
TomSelect.define('checkbox_options', function(options) {
const self = this;
self.hook('render', 'option', function(data, html) {
const checked = self.items.includes(data.value);
return `
<div class="ts-option">
<input type="checkbox" ${checked ? 'checked' : ''} disabled>
<span>${data.text}</span>
</div>
`;
});
});
И подключение:
new TomSelect("#select", {
plugins: ['checkbox_options']
});
Такой подход позволяет вынести визуальную логику из конфигурации и использовать её повторно.
При использовании load или динамической подгрузки данных
чекбоксы должны корректно отражать текущее состояние выбора.
new TomSelect("#select", {
valueField: "id",
labelField: "title",
searchField: "title",
load: function(query, callback) {
fetch(`/api/items?q=${query}`)
.then(res => res.json())
.then(data => callback(data));
},
render: {
option: function(data, escape) {
const checked = this.items.includes(data.id);
return `
<div>
<input type="checkbox" ${checked ? 'checked' : ''} disabled />
<span>${escape(data.title)}</span>
</div>
`;
}
}
});
Здесь важно, что при каждой загрузке данных render-метод получает
актуальное состояние items, что гарантирует корректное
отображение чекбоксов даже при асинхронном обновлении списка.
Checkbox UI тесно связан с механизмом удаления выбранных элементов
через remove_button plugin. При удалении:
itemsrefreshItems()Это делает состояние полностью реактивным без необходимости ручного контроля DOM.
При большом количестве опций повторный рендер чекбоксов может стать узким местом. Для оптимизации применяются следующие подходы:
this.items в локальную переменную внутри
renderrender.optionrender.item отдельно от
render.optionrender: {
option: function(data, escape) {
const isSelected = this.items.indexOf(data.value) !== -1;
return `<div class="opt">
<input type="checkbox" ${isSelected ? 'checked' : ''} disabled>
<span>${escape(data.text)}</span>
</div>`;
}
}
Checkbox UI не изменяет стандартную клавиатурную модель:
ArrowUp / ArrowDown — смена активной опцииEnter — выбор/снятие выбораSpace — альтернативный триггер выбораЧекбоксы при этом не получают фокус, чтобы не ломать единый event flow внутри компонента.
Несмотря на визуальную простоту, подход имеет ограничения:
Эти ограничения связаны с тем, что checkbox является только визуальным слоем поверх внутренней модели выбора, а не частью DOM-стейта компонента.
В checkbox-режиме выбор элементов соответствует множественной конечной множественной структуре:
Каждое действие пользователя выполняет операцию симметрической разности:
UI чекбокса отображает принадлежность элемента множеству B, но не управляет им напрямую.