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

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

В основе лежит механизм регистрации обработчиков и их последующего вызова при наступлении определённых условий. Каждый экземпляр Choices инкапсулирует набор внутренних событий, связанных с:

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

События генерируются в строго определённые моменты выполнения внутренних методов. Это позволяет точно отслеживать изменения состояния без опроса (polling) или прямого доступа к внутренним структурам данных.

Каждое событие передаёт объект контекста, содержащий актуальное состояние экземпляра, а также дополнительные данные, специфичные для конкретного события.

Подписка на события

Choices.js предоставляет метод passedElement.addEventListener через DOM-обёртку, но основной механизм подписки реализован через экземпляр объекта и его внутренний event emitter.

Типовая модель подписки выглядит следующим образом:

const element = document.querySelector('#select');
const choices = new Choices(element);

choices.passedElement.element.addEventListener(
  'addItem',
  (event) => {
    console.log('Добавлен элемент:', event.detail);
  },
  false
);

Внутри объекта event.detail содержится структура, включающая:

  • value — значение выбранного элемента;
  • label — отображаемый текст;
  • id — внутренний идентификатор;
  • customProperties — дополнительные данные, если они были заданы;
  • choice — ссылка на объект выбора.

Такой формат позволяет унифицировать обработку данных вне зависимости от источника добавления элемента — пользовательский ввод, программное добавление или выбор из списка.

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

Инициализация

Событие init возникает после полной инициализации экземпляра. В этот момент уже построена DOM-структура, загружены опции и применены начальные настройки.

choices.passedElement.element.addEventListener('init', (event) => {
  console.log('Компонент готов');
});

Инициализационное событие полезно для привязки дополнительной логики, зависящей от наличия готового интерфейса.

Открытие и закрытие списка

События showDropdown и hideDropdown сигнализируют об изменении состояния выпадающего списка.

choices.passedElement.element.addEventListener('showDropdown', () => {
  console.log('Список открыт');
});

choices.passedElement.element.addEventListener('hideDropdown', () => {
  console.log('Список закрыт');
});

Эти события часто используются для управления стилями, аналитикой пользовательского поведения или синхронизацией с другими компонентами интерфейса.

События выбора и изменения данных

Добавление элемента

Событие addItem является одним из ключевых. Оно вызывается при добавлении нового значения в список выбранных элементов.

choices.passedElement.element.addEventListener('addItem', (event) => {
  const { value, label } = event.detail;
  console.log(`Добавлено: ${label} (${value})`);
});

Важно учитывать, что событие срабатывает независимо от способа добавления: пользовательский клик, ввод с клавиатуры или программный вызов метода setChoiceByValue.

Удаление элемента

Событие removeItem вызывается при удалении значения из выбранных элементов.

choices.passedElement.element.addEventListener('removeItem', (event) => {
  console.log('Удалён элемент:', event.detail.value);
});

Удаление может происходить как через интерфейс (иконка удаления), так и программно через API. Это событие обеспечивает согласованную реакцию системы на любое изменение набора выбранных значений.

Изменение набора значений

Событие change является агрегирующим и вызывается при любом изменении состояния выбора. Оно не всегда предоставляет детальную информацию о причине изменения, но отражает итоговое состояние.

choices.passedElement.element.addEventListener('change', (event) => {
  console.log('Текущее значение:', event.target.value);
});

Это событие ближе к стандартному DOM change, но в контексте Choices.js оно учитывает внутреннюю модель данных библиотеки.

События поиска и фильтрации

Встроенная система поиска генерирует событие search, позволяющее отслеживать ввод пользователя в поле фильтрации.

choices.passedElement.element.addEventListener('search', (event) => {
  console.log('Поисковый запрос:', event.detail.value);
});

Данное событие важно для реализации серверной фильтрации, когда результаты подгружаются динамически. В этом случае каждый ввод пользователя может инициировать запрос к API.

Особенность обработки заключается в том, что событие может вызываться многократно в процессе ввода, поэтому часто требуется реализация дебаунса на уровне обработчика.

События добавления и удаления опций

Choices.js различает выбранные элементы и доступные опции. При динамическом изменении списка опций используются события:

  • addChoice
  • removeChoice

Пример:

choices.passedElement.element.addEventListener('addChoice', (event) => {
  console.log('Добавлена опция:', event.detail.choice);
});

Эти события полезны при построении динамических списков, где данные обновляются из внешних источников.

Системные события состояния

Фокус и взаимодействие

Компонент генерирует события, связанные с фокусом и взаимодействием:

  • focus
  • blur
choices.passedElement.element.addEventListener('focus', () => {
  console.log('Компонент в фокусе');
});

Эти события соответствуют стандартному поведению DOM, но отражают состояние именно кастомного элемента Choices.js, а не нативного select.

Очистка выбора

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

choices.passedElement.element.addEventListener('clear', () => {
  console.log('Выбор очищен');
});

Оно часто используется в формах с возможностью сброса фильтров или параметров поиска.

Структура объекта события

Каждое событие в Choices.js содержит унифицированный объект event, который расширяет стандартный DOM Event. В зависимости от типа события он может включать:

  • detail — специфические данные события;
  • target — связанный DOM-элемент;
  • timeStamp — время срабатывания;
  • внутренние ссылки на экземпляр Choices.

Пример структуры:

{
  detail: {
    value: "option_1",
    label: "Option 1",
    id: 3
  },
  target: HTMLSelectElement,
  timeStamp: 1690000000000
}

Такое разделение позволяет отделять данные бизнес-логики от системных параметров события.

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

Внутренняя последовательность событий в Choices.js строго определена. Например, при добавлении нового элемента последовательность может выглядеть следующим образом:

  1. Проверка валидности значения;
  2. Обновление внутреннего состояния;
  3. Обновление DOM;
  4. Генерация addItem;
  5. Генерация change.

Это важно при построении сложной логики, зависящей от точного момента обновления данных. Ошибки часто возникают при попытке модифицировать состояние внутри обработчиков без учёта порядка выполнения.

Программная генерация событий

Choices.js не предназначает внешнюю генерацию своих внутренних событий напрямую, однако изменение состояния через API автоматически приводит к их вызову. Например:

choices.setValue([{ value: '1', label: 'Item 1' }]);

В результате будут сгенерированы соответствующие события добавления и изменения состояния. Это обеспечивает единообразие между пользовательскими и программными действиями.

Особенности работы в режиме multiple

При включённом режиме множественного выбора события addItem и removeItem могут срабатывать многократно в рамках одной операции. Это требует учёта батчинга изменений, особенно при массовом обновлении данных.

В таких сценариях важно ориентироваться не на каждое отдельное событие, а на итоговое состояние, получаемое через change.

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

Каждый экземпляр Choices.js изолирован, однако внешняя логика может синхронизировать несколько компонентов через их события. Это позволяет строить связанные селекты, где изменение одного поля влияет на содержимое другого.

Пример архитектурного подхода:

  • первый select генерирует change;
  • обработчик выполняет фильтрацию данных;
  • второй select обновляет список через setChoices;
  • генерируются собственные события обновления.

Такая схема позволяет строить каскадные зависимости без модификации внутренней логики библиотеки.