Библиотека Choices.js предназначена для замены стандартных
<select> и <input> на более
функциональные компоненты с поиском, множественным выбором, тегами и
кастомным поведением. При использовании на реальных интерфейсах часто
возникает необходимость создавать несколько независимых экземпляров на
одной странице, каждый из которых имеет собственную конфигурацию, набор
данных и поведение.
Ключевой принцип работы с множественными экземплярами заключается в том, что каждый DOM-элемент инициализируется отдельным объектом Choices, не пересекающимся по состояниям с другими инстансами.
Типичный сценарий — несколько <select> элементов
на странице:
<sel ect class="js-choice" id="country">
<option value="kz">Kazakhstan</option>
<option value="ru">Russia</option>
</select>
<select class="js-choice" id="city">
<option value="karaganda">Karaganda</option>
<option value="almaty">Almaty</option>
</select>
Инициализация всех экземпляров:
import Choices fr om 'choices.js';
const elements = document.querySelectorAll('.js-choice');
const instances = [];
elements.forEach((el) => {
const instance = new Choices(el, {
searchEnabled: true,
shouldSort: false
});
instances.push(instance);
});
Каждый вызов new Choices() создаёт изолированный
экземпляр со своим состоянием, DOM-обвязкой и обработчиками событий.
Choices.js не использует глобальное состояние для хранения данных, поэтому:
Это позволяет безопасно использовать библиотеку в сложных интерфейсах: фильтры, формы, панели администрирования.
Часто требуется различное поведение для разных селектов. Например, один с поиском, другой без него:
document.querySelectorAll('.js-choice').forEach((el) => {
const type = el.dataset.type;
const config = {
shouldSort: false
};
if (type === 'searchable') {
config.searchEnabled = true;
}
if (type === 'tags') {
config.removeItemButton = true;
config.maxItemCount = 5;
}
new Choices(el, config);
});
HTML:
<select class="js-choice" data-type="searchable"></select>
<select class="js-choice" data-type="tags" multiple></select>
Использование data-* атрибутов позволяет масштабировать
конфигурацию без усложнения JavaScript-кода.
При увеличении количества полей удобно выносить логику создания экземпляров в отдельную функцию:
function createChoice(element) {
const baseConfig = {
shouldSort: false,
searchEnabled: true
};
if (element.classList.contains('is-tags')) {
baseConfig.removeItemButton = true;
baseConfig.maxItemCount = 10;
}
if (element.dataset.ajax === 'true') {
baseConfig.searchEnabled = true;
}
return new Choices(element, baseConfig);
}
const instances = Array.fr om(
document.querySelectorAll('.js-choice')
).map(createChoice);
Такой подход упрощает поддержку и централизует правила конфигурации.
В SPA или интерфейсах с динамическим DOM элементы могут появляться после загрузки страницы.
function initNewSelect(container) {
const el = container.querySelector('select.js-choice');
if (!el) return;
return new Choices(el, {
searchEnabled: true
});
}
При добавлении новых блоков:
const container = document.createElement('div');
container.innerHTML = `
<select class="js-choice">
<option value="1">One</option>
</select>
`;
document.body.appendChild(container);
initNewSelect(container);
При множественных инстансах часто требуется централизованное управление:
const instances = new Map();
document.querySelectorAll('.js-choice').forEach((el) => {
const instance = new Choices(el, {});
instances.set(el, instance);
});
Использование Map позволяет:
При удалении элементов из DOM необходимо корректно освобождать ресурсы:
function removeSelect(el) {
const instance = instances.get(el);
if (instance) {
instance.destroy();
instances.delete(el);
}
el.remove();
}
Метод destroy():
Иногда требуется обновить конфигурацию без перезагрузки страницы:
function reinit(el, newConfig) {
const oldInstance = instances.get(el);
if (oldInstance) {
oldInstance.destroy();
}
const newInstance = new Choices(el, newConfig);
instances.set(el, newInstance);
}
При множественных экземплярах удобно использовать базовую конфигурацию:
const baseConfig = {
shouldSort: false,
searchEnabled: true,
itemSelectText: ''
};
function createInstance(el, override = {}) {
return new Choices(el, {
...baseConfig,
...override
});
}
Это уменьшает дублирование и упрощает поддержку единых правил UI.
Каждый экземпляр имеет собственные события, например
addItem:
const instance = new Choices(el, {});
el.addEventListener('addItem', (event) => {
console.log('Добавлено значение:', event.detail.value);
});
При множественных экземплярах важно не использовать глобальные обработчики без фильтрации источника:
document.addEventListener('addItem', (e) => {
if (!e.target.classList.contains('js-choice')) return;
console.log('Сработало для нужного select');
});
В формах с повторяющимися блоками (например, динамические строки таблицы) каждый select должен инициализироваться отдельно:
function initForm(form) {
form.querySelectorAll('select.js-choice').forEach((el) => {
new Choices(el, {
removeItemButton: true
});
});
}
Это предотвращает:
При загрузке данных с сервера каждый экземпляр может иметь собственный endpoint:
document.querySelectorAll('.js-choice').forEach((el) => {
const url = el.dataset.url;
const instance = new Choices(el, {
searchEnabled: true
});
el.addEventListener('search', async (e) => {
const query = e.detail.value;
const response = await fetch(`${url}?q=${query}`);
const data = await response.json();
instance.setChoices(data, 'value', 'label', true);
});
});
При десятках и сотнях инстансов важны:
Пример ленивой инициализации:
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
new Choices(entry.target, {
searchEnabled: true
});
observer.unobserve(entry.target);
});
});
document.querySelectorAll('.js-choice').forEach((el) => {
observer.observe(el);
});
destroy()При масштабных интерфейсах применяется структура:
Пример регистра:
class ChoiceRegistry {
constructor() {
this.map = new Map();
}
init(el, config) {
const instance = new Choices(el, config);
this.map.set(el, instance);
return instance;
}
destroy(el) {
const instance = this.map.get(el);
if (instance) instance.destroy();
this.map.delete(el);
}
}
const registry = new ChoiceRegistry();
При множественных экземплярах Choices.js на странице ключевыми принципами являются: