Обзор системы событий

Система событий в библиотеке Tom Select представляет собой механизм реагирования на действия пользователя и изменения состояния компонента. Практически каждая операция внутри элемента выбора сопровождается генерацией событий: ввод текста, открытие выпадающего списка, загрузка данных, выбор элемента, удаление значения, потеря фокуса и многие другие.

События позволяют:

  • отслеживать поведение пользователя;
  • изменять интерфейс в реальном времени;
  • синхронизировать компонент с сервером;
  • интегрировать Tom Select с другими библиотеками;
  • внедрять собственную бизнес-логику;
  • расширять стандартное поведение компонента.

Архитектура событий в Tom Select построена по принципу pub/sub — объект генерирует события, а внешний код подписывается на них через обработчики.


Метод on()

Основной механизм подписки на события — метод on.

Базовый синтаксис:

const select = new TomSelect('#users');

select.on('change', function(value) {
    console.log('Новое значение:', value);
});

Первый аргумент — имя события.

Второй аргумент — callback-функция, вызываемая при наступлении события.


Список основных событий

Событие initialize

Срабатывает после полной инициализации компонента.

new TomSelect('#users', {
    onInitialize() {
        console.log('Tom Select инициализирован');
    }
});

Альтернативный вариант:

const select = new TomSelect('#users');

select.on('initialize', () => {
    console.log('Компонент готов');
});

Типичные сценарии применения

  • подключение дополнительных модулей;
  • начальная загрузка данных;
  • модификация DOM после создания компонента;
  • запуск аналитики.

Событие change

Одно из наиболее используемых событий.

Вызывается при изменении выбранного значения.

select.on('change', (value) => {
    console.log(value);
});

Для режима multiple:

<select id="tags" multiple>
select.on('change', (values) => {
    console.log(values);
});

Особенности

Событие вызывается:

  • при выборе элемента;
  • при удалении элемента;
  • при программном изменении через API.

Получение текущего значения

В обработчиках часто используется метод getValue().

select.on('change', () => {
    console.log(select.getValue());
});

Для одиночного select:

"user_1"

Для multiple:

["user_1", "user_2"]

Событие item_add

Срабатывает при добавлении нового элемента.

select.on('item_add', (value, item) => {
    console.log('Добавлен:', value);
});

Аргументы

value

Значение выбранного элемента.

item

DOM-элемент выбранного item.


Работа с DOM внутри события

select.on('item_add', (value, item) => {
    item.classList.add('selected-animation');
});

Событие item_remove

Вызывается после удаления элемента.

select.on('item_remove', (value) => {
    console.log('Удалён:', value);
});

Событие clear

Срабатывает после очистки всех выбранных значений.

select.on('clear', () => {
    console.log('Все значения удалены');
});

Часто используется совместно с кнопками сброса формы.


События открытия и закрытия dropdown

select.on('dropdown_open', () => {
    console.log('Список открыт');
});
select.on('dropdown_close', () => {
    console.log('Список закрыт');
});

Управление состоянием интерфейса

Затемнение фона при открытии

select.on('dropdown_open', () => {
    document.body.classList.add('overlay');
});

select.on('dropdown_close', () => {
    document.body.classList.remove('overlay');
});

Событие focus

Вызывается при получении фокуса.

select.on('focus', () => {
    console.log('Фокус установлен');
});

Событие blur

Срабатывает при потере фокуса.

select.on('blur', () => {
    console.log('Фокус потерян');
});

Валидация через события

Событие blur удобно использовать для проверки данных.

select.on('blur', () => {

    const value = select.getValue();

    if (!value.length) {
        select.wrapper.classList.add('error');
    }

});

Событие type

Срабатывает при вводе текста пользователем.

select.on('type', (str) => {
    console.log('Поиск:', str);
});

Динамический поиск

select.on('type', (query) => {

    if (query.length < 3) {
        return;
    }

    console.log('Отправка AJAX запроса');

});

Событие load

Вызывается после загрузки удалённых данных.

select.on('load', (data) => {
    console.log('Получено записей:', data.length);
});

Комбинация load и AJAX

new TomSelect('#users', {

    load(query, callback) {

        fetch(`/api/users?q=${query}`)
            .then(r => r.json())
            .then(data => callback(data));

    }

});
select.on('load', (data) => {
    console.log(data);
});

Событие option_add

Срабатывает при добавлении новой option.

select.on('option_add', (value, data) => {
    console.log(value);
    console.log(data);
});

Событие option_remove

select.on('option_remove', (value) => {
    console.log('Опция удалена');
});

Событие option_clear

Вызывается после очистки списка опций.

select.on('option_clear', () => {
    console.log('Список options очищен');
});

Событие optgroup_add

Срабатывает при добавлении группы.

select.on('optgroup_add', (id, data) => {
    console.log(id);
});

Событие destroy

Вызывается перед уничтожением экземпляра.

select.on('destroy', () => {
    console.log('Экземпляр уничтожен');
});

Очистка ресурсов

Событие destroy критически важно при работе с SPA-приложениями.

select.on('destroy', () => {

    window.removeEventListener('resize', resizeHandler);

    clearInterval(timer);

});

Метод off()

Удаляет обработчики событий.

function onChange(value) {
    console.log(value);
}

select.on('change', onChange);

select.off('change', onChange);

Удаление всех обработчиков

select.off('change');

Метод trigger()

Позволяет вручную инициировать событие.

select.trigger('change', 'admin');

Пользовательские события

Tom Select поддерживает кастомные события.

select.on('user_loaded', (user) => {
    console.log(user);
});
select.trigger('user_loaded', {
    id: 10,
    name: 'Alex'
});

Передача нескольких параметров

select.trigger(
    'custom_event',
    'users',
    15,
    true
);
select.on(
    'custom_event',
    (table, id, active) => {

        console.log(table);
        console.log(id);
        console.log(active);

    }
);

События и конфигурационные callbacks

Tom Select поддерживает два подхода:

Callback внутри настроек

new TomSelect('#users', {

    onChange(value) {
        console.log(value);
    }

});

Подписка через on

select.on('change', handler);

Разница между подходами

Конфигурационные callbacks

Подходят для:

  • базовой настройки;
  • компактной конфигурации;
  • локальной логики.

on()

Подходит для:

  • модульной архитектуры;
  • динамического подключения;
  • плагинов;
  • масштабируемых приложений.

Контекст this в событиях

Во многих callbacks this указывает на экземпляр Tom Select.

new TomSelect('#users', {

    onChange(value) {

        console.log(this);
        console.log(this.getValue());

    }

});

Проблемы стрелочных функций

Стрелочные функции не создают собственный this.

new TomSelect('#users', {

    onChange: (value) => {

        console.log(this);

    }

});

Внутри this уже не будет экземпляром Tom Select.


Порядок вызова событий

При выборе элемента может происходить следующая последовательность:

  1. dropdown_open
  2. type
  3. item_add
  4. change
  5. dropdown_close

Понимание порядка событий важно при сложной логике интерфейса.


Асинхронные обработчики

Обработчики могут быть асинхронными.

select.on('change', async (value) => {

    const response = await fetch('/save', {
        method: 'POST',
        body: JSON.stringify({ value })
    });

    console.log(await response.json());

});

Обработка ошибок внутри событий

select.on('change', async (value) => {

    try {

        await saveUser(value);

    } catch(error) {

        console.error(error);

    }

});

Централизованная система логирования

const events = [
    'change',
    'item_add',
    'item_remove',
    'dropdown_open',
    'dropdown_close'
];

events.forEach(event => {

    select.on(event, (...args) => {

        console.log(event, args);

    });

});

Интеграция с аналитикой

select.on('item_add', (value) => {

    analytics.track('select_item', {
        value
    });

});

Работа с несколькими экземплярами

document.querySelectorAll('.select').forEach(element => {

    const instance = new TomSelect(element);

    instance.on('change', (value) => {

        console.log(element.id, value);

    });

});

Предотвращение циклических событий

При программном изменении значения возможно повторное срабатывание change.

let updating = false;

select.on('change', (value) => {

    if (updating) {
        return;
    }

    updating = true;

    select.setValue(value);

    updating = false;

});

Отладка системы событий

Логирование всех событий

const originalTrigger = select.trigger;

select.trigger = function() {

    console.log(arguments);

    return originalTrigger.apply(this, arguments);

};

Проверка активных обработчиков

Внутренне Tom Select хранит события в объекте:

console.log(select._events);

Структура:

{
    change: [Function],
    item_add: [Function]
}

Практическая архитектура событий

Выделение отдельного слоя событий

class UserSelectEvents {

    constructor(select) {

        this.select = select;

        this.register();

    }

    register() {

        this.select.on(
            'change',
            this.onChange.bind(this)
        );

        this.select.on(
            'item_add',
            this.onAdd.bind(this)
        );

    }

    onChange(value) {

        console.log(value);

    }

    onAdd(value) {

        console.log(value);

    }

}

Декларативное подключение событий

const handlers = {

    change(value) {
        console.log(value);
    },

    item_add(value) {
        console.log(value);
    }

};

Object.entries(handlers).forEach(([event, handler]) => {

    select.on(event, handler);

});

Производительность системы событий

При большом количестве обработчиков возможно ухудшение производительности.

Особенно это касается:

  • type;
  • load;
  • change.

Оптимизация обработчиков

Debounce

function debounce(fn, delay) {

    let timer;

    return function(...args) {

        clearTimeout(timer);

        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);

    };

}
select.on('type', debounce((query) => {

    console.log(query);

}, 300));

Изоляция бизнес-логики

Нежелательно размещать сложную логику прямо внутри callbacks.

Плохой вариант:

select.on('change', async (value) => {

    // 200 строк логики

});

Лучший вариант:

select.on('change', handleUserChange);
async function handleUserChange(value) {

    await syncUser(value);

    updateUI(value);

}

Взаимодействие событий с плагинами

Многие плагины Tom Select генерируют собственные события.

Например:

  • drag & drop плагины;
  • virtual scroll;
  • remove_button;
  • checkbox options.

Поэтому при использовании расширений важно изучать документацию конкретного плагина.


Особенности жизненного цикла событий

Жизненный цикл обычно выглядит следующим образом:

  1. Создание экземпляра.
  2. Инициализация.
  3. Подключение событий.
  4. Взаимодействие пользователя.
  5. Изменение состояния.
  6. Генерация событий.
  7. Очистка.
  8. Уничтожение экземпляра.

Понимание жизненного цикла особенно важно при интеграции Tom Select в:

  • React;
  • Vue;
  • Angular;
  • SPA-приложения;
  • административные панели;
  • сложные формы с динамической генерацией компонентов.