В Slim Select обработка событий строится вокруг конфигурационного
объекта events, который передаётся при инициализации
экземпляра. Каждое событие представляет собой отдельное свойство,
значением которого является функция-обработчик. Типичный набор включает
afterChange, beforeOpen,
afterOpen, beforeClose,
afterClose, error.
const select = new SlimSelect({
select: '#example',
events: {
afterChange: (newVal) => {
console.log('Изменение значения:', newVal)
}
}
})
Такой подход задаёт важное ограничение: каждый ключ события хранит только одну функцию. При повторном назначении обработчика происходит перезапись предыдущего значения. В результате отсутствует нативная поддержка множественной подписки на одно и то же событие.
При расширении логики интерфейса часто возникает необходимость разделить обработку одного события на несколько независимых функций. Например, изменение значения может одновременно:
Прямое объявление нескольких обработчиков невозможно:
events: {
afterChange: handlerA,
afterChange: handlerB // перезапишет handlerA
}
Фактически остаётся только последний назначенный обработчик.
Базовый способ организации множественной подписки заключается в создании агрегирующей функции, которая вызывает набор обработчиков.
const afterChangeHandlers = []
function addAfterChange(handler) {
afterChangeHandlers.push(handler)
}
function removeAfterChange(handler) {
const index = afterChangeHandlers.indexOf(handler)
if (index !== -1) afterChangeHandlers.splice(index, 1)
}
const select = new SlimSelect({
select: '#example',
events: {
afterChange: (value) => {
afterChangeHandlers.forEach(fn => fn(value))
}
}
})
Такой подход превращает одиночный callback Slim Select в мини-реализацию pub-sub механизма. Слой абстракции позволяет добавлять и удалять подписки динамически.
При увеличении количества событий целесообразно использовать централизованный менеджер подписок.
class EventBus {
constructor() {
this.events = {}
}
subscribe(event, handler) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(handler)
}
unsubscribe(event, handler) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(fn => fn !== handler)
}
emit(event, payload) {
if (!this.events[event]) return
this.events[event].forEach(fn => fn(payload))
}
}
const bus = new EventBus()
Интеграция с Slim Select осуществляется через проксирование событий:
const select = new SlimSelect({
select: '#example',
events: {
afterChange: (value) => bus.emit('afterChange', value),
beforeOpen: () => bus.emit('beforeOpen'),
afterClose: () => bus.emit('afterClose')
}
})
Теперь любая логика подключается независимо:
bus.subscribe('afterChange', (value) => {
console.log('Лог 1:', value)
})
bus.subscribe('afterChange', (value) => {
console.log('Лог 2:', value)
})
При множественной подписке порядок вызова становится значимым аспектом. В реализации через массив порядок соответствует очередности регистрации.
Дополнительно вводится возможность приоритизации:
subscribe(event, handler, priority = 0) {
if (!this.events[event]) this.events[event] = []
this.events[event].push({ handler, priority })
this.events[event].sort((a, b) => b.priority - a.priority)
}
Исполнение:
emit(event, payload) {
if (!this.events[event]) return
this.events[event].forEach(item => item.handler(payload))
}
Такой механизм полезен при смешении критической логики (валидация, блокировка UI) и второстепенных операций (логирование, аналитика).
При динамическом создании интерфейсов Slim Select (например, в модальных окнах или SPA-компонентах) критично управлять отпиской обработчиков.
Проблема возникает при сохранении ссылок на функции, которые больше не используются:
bus.subscribe('afterChange', handler)
// компонент уничтожен, но handler остаётся в памяти
Решение заключается в строгом хранении ссылок:
const handler = (value) => {
console.log(value)
}
bus.subscribe('afterChange', handler)
// позже
bus.unsubscribe('afterChange', handler)
В более сложных сценариях применяется токен подписки:
subscribe(event, handler) {
const id = Symbol()
if (!this.events[event]) this.events[event] = []
this.events[event].push({ id, handler })
return id
}
unsubscribe(event, id) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(h => h.id !== id)
}
Slim Select предоставляет события жизненного цикла, которые удобно использовать как точки расширения системы подписок:
beforeOpen — подготовка интерфейсаafterOpen — активация UI-логикиafterChange — реакция на изменение значенияafterClose — финализация состоянияПример распределения логики:
bus.subscribe('afterOpen', () => {
console.log('UI активирован')
})
bus.subscribe('afterChange', (value) => {
validate(value)
sync(value)
track(value)
})
Каждый обработчик выполняет строго ограниченную задачу, что снижает связность кода.
Для унификации можно создавать фабричный слой, связывающий Slim Select и event-bus:
function createSlimSelectWithBus(config) {
const bus = new EventBus()
const select = new SlimSelect({
...config,
events: {
afterChange: (v) => bus.emit('afterChange', v),
afterOpen: () => bus.emit('afterOpen'),
afterClose: () => bus.emit('afterClose')
}
})
return {
select,
on: bus.subscribe.bind(bus),
off: bus.unsubscribe.bind(bus),
emit: bus.emit.bind(bus)
}
}
Использование:
const ss = createSlimSelectWithBus({
select: '#example'
})
ss.on('afterChange', (v) => updateUI(v))
ss.on('afterChange', (v) => save(v))
Множественная подписка становится инструментом архитектурного разделения ответственности. Один и тот же event Slim Select превращается в точку интеграции нескольких независимых слоёв:
bus.subscribe('afterChange', updateView)
bus.subscribe('afterChange', persistToStore)
bus.subscribe('afterChange', sendAnalytics)
bus.subscribe('afterChange', validateBusinessRules)
Каждая функция существует изолированно и не зависит от порядка других, если не задана явная приоритизация.
Использование множественной подписки поверх Slim Select добавляет абстракцию, которая требует контроля:
При этом достигается расширение базовой модели событий Slim Select до полноценной событийной шины, позволяющей масштабировать взаимодействие компонентов без модификации исходного экземпляра библиотеки.