Система шаблонов в Choices.js построена вокруг идеи полного контроля над тем, как визуально и семантически отображаются элементы интерфейса: выбранные значения, элементы списка, группы, плейсхолдеры и служебные состояния. В основе лежит набор функций-шаблонов, которые возвращают DOM-строки или HTML-структуры, используемые при построении интерфейса компонента.
Каждый шаблон представляет собой чистую функцию, принимающую объект данных и возвращающую строку разметки. Это позволяет отделить данные от представления и переопределять визуальную часть без изменения логики работы библиотеки.
Внутри Choices.js шаблоны определяются через конфигурационный объект
callbackOnCreateTemplates. При инициализации экземпляра
библиотеки он получает набор функций, переопределяющих стандартное
поведение рендера.
const choices = new Choices(element, {
callbackOnCreateTemplates: (template) => ({
item: (classNames, data) => {
return template(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : ''}"
data-item
data-id="${data.id}"
data-value="${data.value}">
${data.label}
</div>
`);
}
})
});
Ключевой объект template — это утилита, предоставляемая
библиотекой для безопасного формирования HTML. Она оборачивает строку и
может выполнять внутреннюю нормализацию разметки.
Choices.js использует набор предопределённых шаблонов, каждый из которых отвечает за отдельную часть интерфейса:
containerOuter — внешний контейнер компонентаcontainerInner — внутренний контейнер, содержащий input
и списокitem — выбранный элемент (tag)choice — элемент выпадающего спискаgroup — группа элементовgroupHeading — заголовок группыinput — поле вводаdropdown — контейнер выпадающего спискаnotice — системные сообщения (например, “нет
результатов”)Каждый из этих шаблонов может быть переопределён независимо, что позволяет точечно модифицировать интерфейс без вмешательства в остальные части.
Каждый шаблон получает строго определённую структуру данных.
Например, для choice:
{
id: number,
value: string,
label: string,
selected: boolean,
disabled: boolean,
groupId: number | null,
customProperties: object
}
Для item (выбранного элемента):
{
id: number,
value: string,
label: string,
active: boolean,
highlighted: boolean,
disabled: boolean
}
Для групп:
{
id: number,
value: string,
active: boolean,
disabled: boolean,
choices: array
}
Такая унификация данных обеспечивает предсказуемость рендера и упрощает кастомизацию.
Каждый шаблон возвращает строку, которая затем вставляется в DOM через внутренние механизмы Choices.js. Библиотека не использует виртуальный DOM, а работает через прямую генерацию HTML.
Пример шаблона элемента списка:
choice: (classNames, data) => {
return `
<div class="${classNames.itemChoice} ${data.selected ? classNames.selectedState : ''}"
data-select-text="Press to select"
data-choice
data-id="${data.id}"
data-value="${data.value}"
${data.disabled ? 'aria-disabled="true"' : ''}>
${data.label}
</div>
`;
}
Здесь важную роль играет согласованность атрибутов
data-*, которые используются для последующей обработки
событий внутри библиотеки.
Каждый шаблон получает объект classNames, содержащий
стандартизированные CSS-классы. Это позволяет отделить стили от логики
формирования HTML.
Пример структуры:
{
item: 'choices__item',
itemSelectable: 'choices__item--selectable',
itemDisabled: 'choices__item--disabled',
placeholder: 'choices__placeholder',
list: 'choices__list'
}
Использование classNames гарантирует единообразие
структуры, даже при полной кастомизации шаблонов.
Наиболее часто изменяемый шаблон — choice. Он отвечает
за визуализацию элементов списка.
Пример расширенного кастомного шаблона:
choices.setChoices(
[
{ value: '1', label: 'JavaScript', selected: false },
{ value: '2', label: 'TypeScript', selected: false }
],
'value',
'label',
false
);
И кастомный рендер:
const choices = new Choices(element, {
callbackOnCreateTemplates: (template) => ({
choice: (classNames, data) => {
const icon = data.value === '1' ? '⚡' : '?';
return template(`
<div class="${classNames.itemChoice}"
data-choice
data-id="${data.id}"
data-value="${data.value}">
<span class="icon">${icon}</span>
<span class="label">${data.label}</span>
</div>
`);
}
})
});
Такой подход позволяет внедрять произвольные визуальные элементы: иконки, бейджи, статусные индикаторы.
Шаблон item отвечает за отображение выбранных значений в
инпуте (в виде тегов в multi-select режиме).
item: (classNames, data) => {
return `
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : ''}"
data-item
data-id="${data.id}"
data-value="${data.value}">
<span class="value">${data.label}</span>
<button type="button" class="remove-button" data-button="remove-item">
×
</button>
</div>
`;
}
Ключевая особенность — наличие управляющих элементов внутри шаблона, которые библиотека автоматически связывает с обработчиками событий.
Choices.js использует отдельный шаблон notice для
отображения системных сообщений:
Пример:
notice: (classNames, data) => {
return `
<div class="${classNames.item} ${classNames.notice}"
data-notice>
${data.message}
</div>
`;
}
Объект data в данном случае содержит поле
message, которое формируется внутренней логикой поиска и
фильтрации.
Шаблон input отвечает за поле ввода, которое используется как триггер фильтрации списка.
input: (classNames, data) => {
return `
<input type="search"
class="${classNames.input}"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
role="textbox"
aria-autocomplete="list"
placeholder="${data.placeholder || ''}">
`;
}
Значение placeholder также может быть переопределено
через настройки или динамически изменяться при взаимодействии с
компонентом.
Шаблоны Choices.js не существуют изолированно — они формируют иерархическую структуру:
containerOuter
containerInner
input
list
group
groupHeadingchoicechoice
item
Такая композиция позволяет изменять отдельные уровни интерфейса без разрушения общей структуры.
Система шаблонов не использует полноценный JSX или виртуальный DOM, поэтому вся ответственность за корректность HTML ложится на разработчика кастомизации.
Основные ограничения:
data-* атрибутовПри расширении интерфейса через шаблоны обычно применяются следующие подходы:
span или div для
метаданныхcustomProperties для передачи
дополнительных данныхПример использования customProperties:
choice: (classNames, data) => {
const type = data.customProperties?.type;
return `
<div class="${classNames.itemChoice}"
data-choice>
<span>${data.label}</span>
${type ? `<em class="type">${type}</em>` : ''}
</div>
`;
}
Система шаблонов выполняет роль слоя представления, отделённого от логики выбора, фильтрации и управления состоянием. Благодаря этому Choices.js остаётся гибкой библиотекой, пригодной для интеграции в различные UI-фреймворки и дизайн-системы без изменения ядра.