Массовая инициализация элементов

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

Базовый подход к выборке элементов

Массовая инициализация начинается с получения коллекции DOM-элементов. Обычно используются методы querySelectorAll, либо более узкие селекторы по классам или атрибутам:

const selects = document.querySelectorAll('.js-choices');

Результатом является NodeList, содержащий все элементы <select>, к которым требуется применить функциональность Choices.js.

Дальнейшая логика строится вокруг обхода этой коллекции и создания экземпляра библиотеки для каждого элемента.

Стандартная инициализация через перебор

Наиболее прямолинейный способ — использование метода forEach:

selects.forEach((select) => {
  new Choices(select);
});

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

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

Инициализация с общей конфигурацией

В реальных проектах редко используется дефолтная конфигурация. Чаще требуется единый набор настроек:

const commonConfig = {
  searchEnabled: true,
  itemSelectText: '',
  shouldSort: false,
  removeItemButton: true
};

selects.forEach((select) => {
  new Choices(select, commonConfig);
});

Такой подход обеспечивает централизованное управление поведением всех селектов. Изменение одного параметра автоматически распространяется на всю группу элементов.

Динамическая модификация конфигурации

При массовой инициализации часто возникает необходимость изменять поведение отдельных элементов на основе их атрибутов. Для этого используется чтение data-* свойств:

selects.forEach((select) => {
  const isSearchDisabled = select.dataset.search === 'false';

  new Choices(select, {
    searchEnabled: !isSearchDisabled,
    shouldSort: select.dataset.sort === 'true'
  });
});

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

Группировка по типам элементов

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

const searchSelects = document.querySelectorAll('.js-choices-search');
const simpleSelects = document.querySelectorAll('.js-choices-simple');

Далее каждая группа инициализируется отдельно:

searchSelects.forEach((el) => {
  new Choices(el, {
    searchEnabled: true
  });
});

simpleSelects.forEach((el) => {
  new Choices(el, {
    searchEnabled: false
  });
});

Такой подход снижает сложность конфигурации и улучшает читаемость кода.

Использование функции-инициализатора

При увеличении количества элементов целесообразно выносить логику в отдельную функцию:

function initChoices(elements, config = {}) {
  elements.forEach((el) => new Choices(el, config));
}

initChoices(document.querySelectorAll('.js-choices'), {
  shouldSort: false
});

Это позволяет повторно использовать механизм инициализации в разных частях приложения.

Предотвращение повторной инициализации

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

selects.forEach((select) => {
  if (select.dataset.choicesInitialized) return;

  new Choices(select);
  select.dataset.choicesInitialized = 'true';
});

Маркер в dataset используется как простой механизм отслеживания состояния инициализации.

Хранение экземпляров

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

const instances = [];

selects.forEach((select) => {
  const instance = new Choices(select);
  instances.push(instance);
});

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

Уничтожение экземпляров в массе

При переходе между экранами или обновлении интерфейса требуется корректная очистка:

instances.forEach((instance) => {
  instance.destroy();
});

Этот этап предотвращает утечки памяти и дублирование DOM-структур.

Динамическая инициализация после загрузки контента

В приложениях с AJAX или SPA-архитектурой новые элементы могут появляться после первичной загрузки страницы:

function initNewChoices(root = document) {
  const newSelects = root.querySelectorAll('.js-choices');

  newSelects.forEach((select) => {
    if (!select.dataset.choicesInitialized) {
      new Choices(select);
      select.dataset.choicesInitialized = 'true';
    }
  });
}

Функция принимает корневой элемент, что позволяет ограничить область поиска и ускорить обработку.

Делегирование и наблюдение за DOM

Для полностью динамических интерфейсов применяется MutationObserver, отслеживающий появление новых элементов:

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((node) => {
      if (node.nodeType !== 1) return;

      if (node.matches('.js-choices')) {
        new Choices(node);
      }

      const nested = node.querySelectorAll?.('.js-choices');
      nested?.forEach((el) => new Choices(el));
    });
  });
});

observer.observe(document.body, { childList: true, subtree: true });

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

Оптимизация массовой инициализации

При работе с большим количеством элементов важно учитывать производительность. Основные принципы:

  • минимизация повторных DOM-запросов
  • использование локальных выборок внутри контейнеров
  • избежание избыточных пересозданий экземпляров
  • хранение состояния инициализации в WeakMap или dataset

Пример с WeakMap:

const choicesMap = new WeakMap();

selects.forEach((select) => {
  const instance = new Choices(select);
  choicesMap.set(select, instance);
});

Это позволяет безопасно связывать DOM-элементы с их экземплярами без риска утечек памяти.

Обработка ошибок при массовой инициализации

При большом количестве элементов возможны ситуации, когда часть DOM некорректна или уже изменена:

selects.forEach((select) => {
  try {
    new Choices(select);
  } catch (e) {
    console.warn('Ошибка инициализации элемента', select, e);
  }
});

Изоляция ошибок предотвращает остановку процесса инициализации всей группы.

Комбинирование с ленивой инициализацией

В интерфейсах с вкладками или скрытыми блоками часто применяется отложенная инициализация:

function initVisibleChoices() {
  const visibleSelects = document.querySelectorAll('.js-choices:visible');

  visibleSelects.forEach((select) => {
    if (!select.dataset.choicesInitialized) {
      new Choices(select);
      select.dataset.choicesInitialized = 'true';
    }
  });
}

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

Централизованное управление массовыми экземплярами

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

const ChoicesManager = {
  instances: [],

  init(selector, config) {
    const elements = document.querySelectorAll(selector);

    elements.forEach((el) => {
      const instance = new Choices(el, config);
      this.instances.push(instance);
    });
  },

  destroyAll() {
    this.instances.forEach((i) => i.destroy());
    this.instances = [];
  }
};

Такой подход упрощает контроль жизненного цикла всех селектов в приложении.