Работа с группировкой опций в Choices.js опирается на механизм optgroup-подобной структуры данных и систему шаблонов (templates), отвечающих за визуализацию как самих элементов, так и заголовков групп. Шаблон для групп является ключевым элементом кастомизации, позволяющим полностью контролировать внешний вид разделителей и логики отображения сгруппированных наборов значений.
Группы в Choices.js формируются через вложенную структуру данных, где каждая группа содержит мета-информацию и массив опций:
label — отображаемое имя группыid — уникальный идентификатор (используется
внутренне)disabled — блокировка всей группыchoices — массив элементов внутри группыПример структуры:
const groupedData = [
{
label: 'Фронтенд',
id: 1,
disabled: false,
choices: [
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'svelte', label: 'Svelte' }
]
},
{
label: 'Бэкенд',
id: 2,
choices: [
{ value: 'node', label: 'Node.js' },
{ value: 'django', label: 'Django' },
{ value: 'go', label: 'Go' }
]
}
];
Такая структура является основой для построения группированного списка, где каждая группа становится отдельным визуальным блоком.
После инициализации библиотека преобразует входные данные в нормализованную структуру:
choice и
groupКаждая группа передается в шаблон рендеринга отдельно, что позволяет управлять отображением на уровне UI без изменения исходных данных.
Шаблон группы — это функция, определяющая, как будет отображаться
заголовок группы в выпадающем списке. Он задается через конфигурацию
templates.group.
Базовый синтаксис:
const choices = new Choices('#select', {
choices: groupedData,
templates: {
group: (classNames) => {
return (data) => {
return `
<div class="${classNames.group}">
<span class="${classNames.groupHeading}">
${data.label}
</span>
</div>
`;
};
}
}
});
Функция шаблона получает:
classNames — объект с системными CSS-классамиdata — объект группы (label, id, disabled,
choices)В некоторых конфигурациях также доступны дополнительные поля:
activeItemsdisabledparent контекстШаблон группы отвечает исключительно за:
Логика выбора элементов и фильтрации не должна находиться внутри шаблона.
Часто требуется расширить стандартный заголовок группы дополнительными элементами: счетчиками, иконками или индикаторами состояния.
Пример расширенного шаблона:
templates: {
group: (classNames) => {
return (group) => {
const count = group.choices ? group.choices.length : 0;
return `
<div class="${classNames.group}">
<div class="group-header">
<span class="${classNames.groupHeading}">
${group.label}
</span>
<span class="group-count">
${count}
</span>
</div>
</div>
`;
};
}
}
В данном случае добавляется вычисляемый элемент — количество опций внутри группы, что позволяет визуально оценивать объем категории.
Шаблон может учитывать состояние группы:
Пример обработки состояния:
templates: {
group: (classNames) => {
return (group) => {
if (!group.choices || group.choices.length === 0) {
return '';
}
const isDisabled = group.disabled;
return `
<div class="${classNames.group} ${isDisabled ? 'is-disabled' : ''}">
<span class="${classNames.groupHeading}">
${group.label}
</span>
</div>
`;
};
}
}
Пустая строка в качестве возврата фактически исключает группу из DOM, что влияет на финальное отображение списка.
Группа не существует изолированно — она тесно связана с шаблоном
элементов (choice template). При рендеринге происходит
последовательность:
Таким образом, group template влияет только на первый уровень структуры, не затрагивая дочерние элементы.
Объект classNames является центральным механизмом
унификации стилей:
group — контейнер группыgroupHeading — заголовокchoice — элемент выбораitem — выбранный элементПример использования:
templates: {
group: (classNames) => (group) => `
<div class="${classNames.group}">
<span class="${classNames.groupHeading}">
${group.label}
</span>
</div>
`
}
Преимущество такого подхода — независимость от конкретных CSS-имен, заданных библиотекой по умолчанию.
Шаблоны могут быть изменены при инициализации, но не всегда поддерживают горячее обновление без пересоздания экземпляра.
Пример смены логики отображения:
const instance = new Choices('#select', {
choices: groupedData,
templates: {
group: (classNames) => (group) => `
<div class="${classNames.group}">
<strong>${group.label}</strong>
</div>
`
}
});
Любая модификация шаблона требует учета того, что DOM пересоздается при перерендере списка.
При активной фильтрации поиском группы могут:
Шаблон группы не управляет фильтрацией, но может визуально отражать её результат:
templates: {
group: (classNames) => (group) => {
const hasVisibleChoices = group.choices?.some(c => !c.disabled);
return `
<div class="${classNames.group} ${hasVisibleChoices ? '' : 'is-empty'}">
<span class="${classNames.groupHeading}">
${group.label}
</span>
</div>
`;
}
}
При проектировании кастомного group template необходимо учитывать ограничения:
Шаблон остается чистой функцией отображения без состояния.
Group template выполняет функцию визуального уровня абстракции между:
Он обеспечивает:
В результате шаблон групп становится точкой расширения, через которую реализуются сложные UI-паттерны в списках выбора.