Архитектура плагинов в Tom Select построена вокруг идеи минимального ядра и расширяемого поведения через подключаемые модули. Ядро библиотеки отвечает только за базовые операции: управление списком опций, выбранными значениями, вводом пользователя и синхронизацией состояния с DOM. Любая дополнительная логика — поиск с автодополнением, создание новых элементов, кастомные рендеры, работа с удалёнными источниками — реализуется через плагины.
Плагин в этой архитектуре представляет собой функцию, которая
получает экземпляр TomSelect и модифицирует его поведение:
добавляет методы, перехватывает события, расширяет конфигурацию или
заменяет части логики.
Ключевая идея:
плагин не наследует класс, а модифицирует экземпляр через композицию
Подключение плагинов происходит на этапе инициализации экземпляра или через явное добавление в рантайме.
Типичная форма регистрации:
import TomSelect from 'tom-select'
import pluginRemoveButton from 'tom-select/dist/js/plugins/remove_button'
new TomSelect('#select', {
plugins: [pluginRemoveButton]
})
Плагин может быть представлен как:
(instance) => voidinitБазовый контракт:
function plugin(instance) {
// модификация instance
}
После вызова функции плагина экземпляр уже содержит изменённое поведение.
При инициализации TomSelect выполняется цикл загрузки
плагинов:
Упрощённая модель:
class TomSelect {
constructor(el, options) {
this.options = options
this.setup()
;(options.plugins || []).forEach(plugin => {
plugin(this)
})
}
}
Плагины не изолированы друг от друга и работают с одним и тем же объектом, что формирует модель цепочки модификаций.
Плагин в архитектуре Tom Select не имеет строгого жизненного цикла, но фактически проходит несколько стадий взаимодействия:
Плагин получает уже созданный экземпляр и может:
После инициализации плагин реагирует на:
Некоторые плагины предоставляют метод destroy или
аналогичный механизм очистки:
function plugin(instance) {
function cleanup() {
// удаление обработчиков
}
instance.on('destroy', cleanup)
}
Основной механизм интеграции плагинов — это событийная
система. Экземпляр TomSelect реализует
publish/subscribe модель.
Типичные события:
initializechangeitem_additem_removedropdown_opendropdown_closetypeloadПлагин может подписываться на события:
function plugin(instance) {
instance.on('item_add', (value) => {
console.log('Добавлен элемент:', value)
})
}
Также поддерживается отмена поведения через возвращаемые значения:
instance.on('before_add_item', (value) => {
if (value === 'forbidden') {
return false
}
})
Это создаёт модель перехвата и модификации потока событий, где плагины могут вмешиваться в стандартную логику.
Плагины часто расширяют экземпляр новыми методами:
function plugin(instance) {
instance.clearAll = function () {
Object.keys(instance.items).forEach(item => {
instance.removeItem(item)
})
}
}
Такой подход создаёт гибридную модель:
Также плагины могут добавлять внутренние свойства:
instance.settings.myPluginEnabled = true
instance.state.myPluginCache = {}
Более сложный механизм — monkey patching методов экземпляра.
function plugin(instance) {
const originalAddItem = instance.addItem
instance.addItem = function (value, silent) {
if (value === 'blocked') return
return originalAddItem.call(this, value, silent)
}
}
Такая техника позволяет:
Риск архитектуры — возможные конфликты при нескольких плагинах, перезаписывающих один и тот же метод.
Плагины в Tom Select не имеют формальной изоляции, поэтому их взаимодействие определяется порядком подключения.
new TomSelect('#select', {
plugins: [pluginA, pluginB, pluginC]
})
Порядок имеет значение:
pluginA может изменить методpluginB будет работать уже с изменённой версиейpluginC увидит итоговое состояниеЭто создаёт модель цепочки трансформаций экземпляра.
Tom Select хранит состояние в нескольких слоях:
this.items — выбранные элементыthis.options — доступные опцииthis.settings — конфигурацияthis.control — DOM-контейнерыПлагины часто используют эти структуры напрямую.
Пример изменения состояния:
function plugin(instance) {
instance.on('change', () => {
instance.state.lastChange = Date.now()
})
}
Архитектурная особенность: состояние не инкапсулировано строго, что упрощает расширение, но снижает защиту от побочных эффектов.
function validatePlugin(instance) {
const originalAddItem = instance.addItem
instance.addItem = function (value, silent) {
if (!/^[a-z]+$/.test(value)) {
instance.trigger('invalid', value)
return
}
return originalAddItem.call(this, value, silent)
}
instance.on('invalid', (value) => {
console.warn('Недопустимое значение:', value)
})
}
Здесь сочетаются три механизма:
function loggerPlugin(instance) {
instance.on('item_add', (value) => {
console.log('[ADD]', value)
})
instance.on('item_remove', (value) => {
console.log('[REMOVE]', value)
})
}
В этом случае плагин не вмешивается в логику, а работает как наблюдатель.
Некоторые плагины могут добавляться после инициализации:
const select = new TomSelect('#select')
plugin(select)
Такой подход требует аккуратности, так как:
Основная проблема архитектуры — конфликты при:
Типичные стратегии:
Архитектура Tom Select опирается на несколько ключевых принципов:
Эти принципы создают гибкую, но не строго изолированную систему расширений, где поведение формируется наложением нескольких слоёв логики поверх ядра.