Работа с несколькими селектами в интерфейсе часто требует единообразной настройки поведения, внешнего вида и источников данных. Библиотека 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';
}
});
}
Функция принимает корневой элемент, что позволяет ограничить область поиска и ускорить обработку.
Для полностью динамических интерфейсов применяется
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 });
Такой механизм обеспечивает автоматическую инициализацию без ручного вызова функций.
При работе с большим количеством элементов важно учитывать производительность. Основные принципы:
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 = [];
}
};
Такой подход упрощает контроль жизненного цикла всех селектов в приложении.