Система шаблонов в Choices.js

Система шаблонов в 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
}

Такая унификация данных обеспечивает предсказуемость рендера и упрощает кастомизацию.


Механизм генерации HTML

Каждый шаблон возвращает строку, которая затем вставляется в 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-*, которые используются для последующей обработки событий внутри библиотеки.


КлассNames как часть системы шаблонов

Каждый шаблон получает объект classNames, содержащий стандартизированные CSS-классы. Это позволяет отделить стили от логики формирования HTML.

Пример структуры:

{
  item: 'choices__item',
  itemSelectable: 'choices__item--selectable',
  itemDisabled: 'choices__item--disabled',
  placeholder: 'choices__placeholder',
  list: 'choices__list'
}

Использование classNames гарантирует единообразие структуры, даже при полной кастомизации шаблонов.


Переопределение шаблона choice

Наиболее часто изменяемый шаблон — 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 и визуализация выбранных значений

Шаблон 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 отвечает за поле ввода, которое используется как триггер фильтрации списка.

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

          • groupHeading
          • choice
        • choice

      • item

Такая композиция позволяет изменять отдельные уровни интерфейса без разрушения общей структуры.


Безопасность и ограничения шаблонов

Система шаблонов не использует полноценный JSX или виртуальный DOM, поэтому вся ответственность за корректность HTML ложится на разработчика кастомизации.

Основные ограничения:

  • отсутствие автоматического экранирования HTML в пользовательских данных
  • необходимость ручного контроля XSS-рисков
  • строгая зависимость от структуры data-* атрибутов
  • невозможность частичного патчинга шаблонов без полного переопределения функции

Практика расширения системы шаблонов

При расширении интерфейса через шаблоны обычно применяются следующие подходы:

  • добавление дополнительных span или div для метаданных
  • использование customProperties для передачи дополнительных данных
  • условный рендеринг элементов внутри шаблона
  • интеграция с иконками через внешние библиотеки
  • внедрение индикаторов состояния (loading, error, active)

Пример использования 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

Система шаблонов выполняет роль слоя представления, отделённого от логики выбора, фильтрации и управления состоянием. Благодаря этому Choices.js остаётся гибкой библиотекой, пригодной для интеграции в различные UI-фреймворки и дизайн-системы без изменения ядра.