В Choices.js визуальное представление элементов списка и выбранных значений полностью отделено от логики данных. Это достигается через систему шаблонов, позволяющую переопределять разметку для каждого типа сущности: вариантов выбора, выбранных элементов, групп, сообщений состояния и элементов поиска.
Базовая архитектура библиотеки строится вокруг генерации DOM-структур через функции-шаблоны, которые возвращают HTML-элементы или строки разметки. Это дает полный контроль над отображением без необходимости форкать библиотеку или переписывать внутреннюю логику.
Choices.js использует набор шаблонов, каждый из которых отвечает за конкретный элемент интерфейса:
Каждый шаблон может быть переопределен через конфигурацию
callbackOnCreateTemplates.
Ключевой точкой расширения визуального слоя является функция:
const choices = new Choices(element, {
callbackOnCreateTemplates: function (template) {
return {
item: (classNames, data) => {
return template(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : ''}"
data-item
data-id="${data.id}"
data-value="${data.value}">
<span class="custom-item-text">${data.label}</span>
</div>
`);
},
choice: (classNames, data) => {
return template(`
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : ''}"
data-choice
data-id="${data.id}"
data-value="${data.value}">
<span class="custom-choice-text">${data.label}</span>
</div>
`);
}
};
}
});
Функция получает объект template, который преобразует
строку HTML в DOM-элемент с необходимой внутренней обработкой
библиотеки.
Элемент выбора — это базовая единица списка. Он отображается в выпадающем меню и представляет один возможный вариант.
Каждый choice содержит:
id — внутренний идентификаторvalue — значение, отправляемое при выбореlabel — отображаемый текстdisabled — состояние недоступностиselected — выбран ли элементactive — доступен ли для выбора после фильтрацииchoice: (classNames, data) => {
const status = data.disabled ? 'disabled' : 'active';
return template(`
<div class="${classNames.item} ${classNames.itemChoice} status-${status}"
role="option"
data-choice
data-id="${data.id}"
data-value="${data.value}">
<div class="choice-main">
<strong class="choice-label">${data.label}</strong>
</div>
${data.customProperties?.description
? `<div class="choice-description">${data.customProperties.description}</div>`
: ''}
</div>
`);
}
Choices.js позволяет добавлять произвольные данные в
customProperties, что расширяет шаблон:
choices.setChoices([
{
value: 'js',
label: 'JavaScript',
customProperties: {
description: 'Язык программирования для веба'
}
}
]);
Элемент item отображается внутри поля выбора после того,
как опция была выбрана.
removeItemButton
включен)item: (classNames, data) => {
return template(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : ''}"
data-item
data-id="${data.id}"
data-value="${data.value}">
<span class="item-label">${data.label}</span>
<button type="button"
class="item-remove"
data-button="remove">
×
</button>
</div>
`);
}
data.highlighted используется при навигации с
клавиатурыdata.active может влиять на возможность удаленияdata-button="remove" для
интеграции с логикой библиотекиГруппы позволяют структурировать список опций.
label — название группыid — идентификаторdisabled — блокировка всей группыgroup: (classNames, data) => {
return template(`
<div class="${classNames.group}"
data-group
data-id="${data.id}">
<div class="group-header">
<span class="group-title">${data.label}</span>
</div>
<div class="group-children">
${data.children}
</div>
</div>
`);
}
Choices.js уже формирует вложенные children, поэтому
шаблон группы отвечает только за оболочку. Важно сохранять контейнер для
дочерних элементов, иначе список не будет отображаться корректно.
Контейнер определяет общую структуру компонента, включая input, список и состояния.
containerOuter: (classNames, data) => {
return template(`
<div class="${classNames.containerOuter}"
data-type="${data.type}">
${data.input}
${data.dropdown}
</div>
`);
}
dropdown: (classNames) => {
return template(`
<div class="${classNames.list}"
aria-expanded="false">
<div class="${classNames.listInner}">
<!-- choices -->
</div>
</div>
`);
}
Notice используется для отображения системных сообщений:
notice: (classNames, message) => {
return template(`
<div class="${classNames.item} notice"
role="alert">
<span class="notice-text">${message}</span>
</div>
`);
}
Choices.js автоматически передает сообщения:
No results foundLoading...Press to selectИх можно локализовать или полностью заменить.
Функция template внутри
callbackOnCreateTemplates выполняет несколько задач:
Важно использовать именно template, а не
innerHTML, так как библиотека ожидает специфическую
структуру элементов.
Шаблоны могут зависеть от состояния данных:
choice: (classNames, data) => {
const isVIP = data.customProperties?.vip;
return template(`
<div class="${classNames.item} ${isVIP ? 'vip' : ''}"
data-choice
data-value="${data.value}">
${isVIP ? '<span class="badge">VIP</span>' : ''}
${data.label}
</div>
`);
}
Такая модель позволяет:
Несмотря на гибкость, существуют архитектурные ограничения:
data-choice,
data-item)Часто шаблоны используются совместно для создания единого UI:
choice формирует карточки в спискеitem повторяет визуальный стиль выбранных
элементовgroup добавляет логическую структуруnotice унифицирует состояние интерфейсаЕдинообразие достигается через общие CSS-классы и согласованную структуру DOM.
Шаблоны тесно связаны с классами из classNames, которые
предоставляет библиотека:
itemitemChoiceitemDisabledhighlightedStatelistgroupЭти классы рекомендуется не заменять вручную, а расширять, сохраняя внутреннюю логику поведения.
Шаблонная система позволяет реализовать:
Пример расширенного choice:
choice: (classNames, data) => {
return template(`
<div class="${classNames.item} custom-card"
data-choice
data-value="${data.value}">
<img src="${data.customProperties?.icon}" class="icon" />
<div class="content">
<div class="title">${data.label}</div>
<div class="subtitle">${data.customProperties?.subtitle}</div>
</div>
</div>
`);
}