Плагинная архитектура строится вокруг расширения ядра без модификации исходного поведения компонента. Внутри экземпляра селекта события выступают центральным механизмом синхронизации состояния между ядром и расширениями. Каждый плагин получает доступ к контексту инстанса и может как подписываться на события, так и инициировать их, формируя цепочки реактивного поведения.
События в плагинах не отделены от основного API. Плагин работает в том же пространстве имён и использует те же методы, что и основной экземпляр. Это означает, что корректная работа с событиями требует понимания жизненного цикла компонента и порядка инициализации плагинов.
Внутри экземпляра используется событийная модель вида:
this.on(eventName, handler)this.off(eventName, handler)this.trigger(eventName, payload)Плагин получает доступ к этим методам через контекст
this, который передаётся при инициализации.
TomSelect.define('examplePlugin', function(options) {
this.on('initialize', () => {
console.log('Инициализация завершена');
});
});
Ключевой момент заключается в том, что подписка должна происходить до наступления события. Поэтому большинство плагинов используют события инициализации как точку входа.
Понимание порядка событий критично для корректной интеграции:
initializeНа каждом этапе доступны разные события. Например:
initialize — безопасная точка для первичной
настройкиload — завершение загрузки данныхitem_add — добавление элементаitem_remove — удаление элементаchange — изменение значенияПлагин, который модифицирует поведение выбора, почти всегда начинает
работу с initialize, чтобы гарантировать наличие
DOM-структуры и внутренних коллекций.
Подписка выполняется через this.on, но важно учитывать
контекст выполнения обработчика. Внутри callback this может
отличаться от инстанса, поэтому часто используется замыкание.
TomSelect.define('logPlugin', function() {
const self = this;
self.on('item_add', function(value) {
console.log('Добавлен элемент:', value);
self.trigger('custom_log_event', value);
});
});
Здесь происходит сразу два действия:
item_addcustom_log_eventТаким образом плагин не только потребляет события, но и расширяет событийную модель.
Плагины могут расширять интерфейс событий, создавая новые сигналы, которые могут использовать другие плагины или внешний код.
this.trigger('plugin_state_change', {
active: true,
source: 'examplePlugin'
});
Важно соблюдать соглашения:
Такая дисциплина обеспечивает совместимость нескольких плагинов одновременно.
Некоторые события допускают вмешательство в поток выполнения. Например, в обработчиках можно изменять состояние или предотвращать стандартное поведение через контрольные флаги, если они предусмотрены логикой ядра.
Типичный паттерн:
this.on('item_add', (value) => {
if (value === 'forbidden') {
this.removeItem(value);
}
});
Такой подход позволяет реализовывать:
Когда подключено несколько расширений, они фактически образуют цепочку подписчиков на один и тот же event bus. Порядок подключения может влиять на результат.
TomSelect.define('pluginA', function() {
this.on('change', (value) => {
this.trigger('pluginA_processed', value);
});
});
TomSelect.define('pluginB', function() {
this.on('pluginA_processed', (value) => {
console.log('Получено из pluginA:', value);
});
});
В этом примере pluginB зависит от того, что
pluginA уже инициировал собственное событие. Это создаёт
неявную зависимость между модулями.
При проектировании плагинов необходимо учитывать несколько типичных проблем:
1. Зацикливание событий
this.on('change', (value) => {
this.trigger('change', value);
});
Такой код создаёт бесконечный цикл, поскольку обработчик сам вызывает событие, на которое подписан.
2. Утечки памяти
Подписки, которые не снимаются при уничтожении инстанса, продолжают существовать:
this.on('item_add', handler);
Если плагин динамически отключается, необходимо гарантировать очистку:
this.off('item_add', handler);
3. Непредсказуемый порядок выполнения
При множественных подписчиках порядок вызова может зависеть от внутренней реализации и порядка регистрации.
thisКонтекст внутри обработчиков событий часто теряется, особенно при использовании стрелочных функций.
this.on('item_add', (value) => {
// this остаётся внешним, что обычно удобно
});
или
this.on('item_add', function(value) {
// this может указывать на event context
});
Рекомендуется явно фиксировать ссылку на инстанс:
const self = this;
Это делает код предсказуемым при сложной композиции плагинов.
Внутри Tom Select присутствует разделение между:
Плагины должны ориентироваться в первую очередь на внутренние события, поскольку DOM может изменяться между версиями и режимами работы.
Пример:
this.on('dropdown_open', () => {
console.log('Открыт список');
});
DOM-обработчики используются только в случаях, когда требуется низкоуровневое вмешательство в интерфейс.
Иногда требуется изменить стандартную логику события без полной замены ядра. Это достигается через комбинацию перехвата и повторной генерации.
this.on('item_remove', (value) => {
const modified = value.toUpperCase();
this.trigger('item_removed_normalized', modified);
});
Такой подход позволяет:
События могут сопровождаться асинхронной логикой, особенно если плагин взаимодействует с сервером или внешними источниками.
this.on('type', async (query) => {
const results = await fetchData(query);
this.trigger('custom_results', results);
});
Важно учитывать, что порядок событий при асинхронных обработчиках становится недетерминированным. Поэтому нельзя полагаться на последовательность выполнения без дополнительной синхронизации.
Сложные расширения строятся как набор независимых модулей, взаимодействующих исключительно через события. Это снижает связанность и повышает переиспользуемость.
Паттерн:
Связь между ними осуществляется через trigger и
on, без прямых вызовов методов друг друга.
При разработке крупных плагинов полезно группировать события по смыслу:
item_* — работа с элементамиdropdown_* — интерфейс спискаload_* — загрузка данныхplugin_* — события расширенийТакая структура облегчает сопровождение и снижает риск конфликтов имён.
Корректная работа с событиями в плагинах опирается на несколько принципов:
При соблюдении этих условий событийная модель остаётся стабильной даже при большом количестве подключённых расширений.