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

Библиотека Choices.js построена вокруг событийной модели, в которой каждый значимый пользовательский или программный шаг в работе селекта отражается через события DOM-элемента. Основной элемент, через который осуществляется подписка, — исходный <select> или <input>, переданный в экземпляр Choices.

const element = document.querySelector('#example');
const choices = new Choices(element, {
  searchEnabled: true
});

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


Основные события, используемые для логирования

Choices.js генерирует несколько ключевых событий, которые покрывают почти все пользовательские действия:

addItem

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

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

event.detail обычно содержит:

  • value — значение
  • label — отображаемый текст
  • id — внутренний идентификатор
  • groupValue — если используется группировка

removeItem

Фиксирует удаление выбранного элемента.

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

change

Срабатывает при любом изменении состояния выбора.

element.addEventListener('change', (event) => {
  console.log('Состояние изменено:', event.detail.value);
});

choice

Отражает выбор элемента из выпадающего списка.

element.addEventListener('choice', (event) => {
  console.log('Выбор из списка:', event.detail);
});

Фиксирует ввод поискового запроса.

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

showDropdown / hideDropdown

Используются для отслеживания открытия и закрытия интерфейса.

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

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

Структурированное логирование событий

Простое использование console.log быстро становится недостаточным. При построении аналитики или отладки вводится структурированный формат событий.

function logEvent(type, payload) {
  const log = {
    type,
    payload,
    timestamp: new Date().toISOString(),
    source: 'choices.js'
  };

  console.log(JSON.stringify(log));
}

Применение:

element.addEventListener('addItem', (e) => {
  logEvent('addItem', e.detail);
});

Такой подход позволяет:

  • агрегировать события
  • отправлять данные в аналитические системы
  • фильтровать логи по типам

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

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

class ChoicesLogger {
  constructor(instance, name) {
    this.instance = instance;
    this.name = name;

    this.bindEvents();
  }

  bindEvents() {
    const el = this.instance.passedElement.element;

    el.addEventListener('addItem', (e) => this.log('addItem', e.detail));
    el.addEventListener('removeItem', (e) => this.log('removeItem', e.detail));
    el.addEventListener('search', (e) => this.log('search', e.detail));
    el.addEventListener('choice', (e) => this.log('choice', e.detail));
  }

  log(eventType, data) {
    const record = {
      source: this.name,
      eventType,
      data,
      time: Date.now()
    };

    console.log(record);
  }
}

Использование:

const choices = new Choices('#select');

new ChoicesLogger(choices, 'country-selector');

Логирование поисковых событий и оптимизация потока

Событие search генерируется при каждом вводе символа. Это создаёт риск избыточного логирования.

Для уменьшения шума применяется буферизация через debounce:

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Применение к поиску:

const logSearch = debounce((e) => {
  console.log('Поиск:', e.detail.value);
}, 300);

element.addEventListener('search', logSearch);

Такой подход:

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

Логирование динамических данных (AJAX / async choices)

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

const choices = new Choices('#ajax', {
  shouldSort: false,
  searchEnabled: true
});

Логирование запросов:

element.addEventListener('search', async (e) => {
  const query = e.detail.value;

  console.log('Запрос к серверу:', query);

  const response = await fetch(`/api/search?q=${query}`);
  const data = await response.json();

  console.log('Ответ сервера:', {
    query,
    resultsCount: data.length
  });
});

Расширенный вариант фиксирует:

  • время запроса
  • задержку ответа
  • количество возвращённых элементов

Контекстное логирование состояния выбора

Choices.js позволяет отслеживать текущее состояние выбранных значений через событие change. Это полезно для построения истории действий.

element.addEventListener('change', (e) => {
  const state = {
    value: e.detail.value,
    activeItems: choices.getValue(true)
  };

  console.log('Текущее состояние:', state);
});

Такой подход позволяет:

  • отслеживать полный снимок состояния
  • фиксировать цепочки изменений
  • восстанавливать поведение пользователя

Обработка ошибок и нестандартных ситуаций

Хотя Choices.js не генерирует большое количество ошибок на уровне событий, нестандартные состояния можно фиксировать вручную.

try {
  choices.setValue([{ value: 'test', label: 'Test' }]);
} catch (e) {
  console.log('Ошибка установки значения:', {
    message: e.message,
    stack: e.stack
  });
}

Дополнительно возможно отслеживание некорректных пользовательских действий через проверку данных в событиях:

element.addEventListener('addItem', (e) => {
  if (!e.detail.value) {
    console.warn('Пустое значение добавления');
  }
});

Режимы логирования: разработка и production

Логирование в Choices.js часто разделяется на два режима:

Разработка

  • подробные события
  • вывод всех параметров
  • включён search logging
const DEBUG = true;

function debugLog(...args) {
  if (DEBUG) console.log(...args);
}

Production

  • минимальный набор событий
  • отправка в аналитику
  • отсутствие console.log
function logToService(event) {
  navigator.sendBeacon('/log', JSON.stringify(event));
}

Консолидация событий в единый поток

При сложных интерфейсах Choices.js может быть частью общей системы форм. Тогда события агрегируются:

const eventStream = [];

function pushEvent(type, data) {
  eventStream.push({
    type,
    data,
    time: performance.now()
  });
}

Применение:

element.addEventListener('addItem', (e) => {
  pushEvent('addItem', e.detail);
});

element.addEventListener('removeItem', (e) => {
  pushEvent('removeItem', e.detail);
});

Это позволяет:

  • строить пользовательские сессии
  • анализировать последовательности действий
  • воспроизводить поведение интерфейса

Интеграция с внешними системами аналитики

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

function analyticsAdapter(eventType, data) {
  window.analytics.push({
    event: eventType,
    properties: data
  });
}

Подключение:

element.addEventListener('addItem', (e) => {
  analyticsAdapter('choices_add_item', e.detail);
});

Такая схема обеспечивает:

  • единый формат событий
  • совместимость с аналитическими платформами
  • возможность расширения без изменения логики Choices.js