Использование функций в шаблонах

В Choices.js система шаблонов строится вокруг функций, возвращающих строку HTML. Каждый шаблон в конфигурации templates может быть задан как функция, получающая контекст элемента и возвращающая разметку, которая будет вставлена в DOM. Такой подход позволяет полностью контролировать отображение списка, выбранных элементов и групп, не ограничиваясь стандартным представлением.

Ключевая особенность заключается в том, что шаблоны не являются статическими строками — они вычисляются динамически при каждом рендере, что открывает доступ к состоянию компонента и данным конкретного элемента.

Общая структура шаблонов

Конфигурация templates передаётся при инициализации экземпляра:

const choices = new Choices('#select', {
  templates: {
    item: function (classNames, data) {
      return `<div class="${classNames.item}">${data.label}</div>`;
    }
  }
});

Каждая функция получает два основных аргумента:

  • classNames — объект с предопределёнными CSS-классами Choices.js
  • data — объект данных текущего элемента

В зависимости от типа шаблона могут добавляться дополнительные параметры.


Контекст данных внутри шаблонов

Объект data является центральным элементом при построении шаблонов. Он содержит всю информацию об опции или выбранном элементе:

  • value — значение элемента
  • label — отображаемый текст
  • id — уникальный идентификатор
  • disabled — флаг недоступности
  • selected — состояние выбора
  • active — активный элемент в списке
  • customProperties — пользовательские данные, переданные в опцию

Пример использования расширенного контекста:

{
  value: 'ru',
  label: 'Русский',
  selected: true,
  disabled: false,
  customProperties: {
    flag: '??',
    region: 'EU'
  }
}

Функциональные шаблоны элементов списка

Наиболее часто переопределяется шаблон choice, отвечающий за отображение элементов выпадающего списка.

const choices = new Choices('#select', {
  templates: {
    choice: function (classNames, data) {
      return `
        <div class="${classNames.item} ${classNames.itemChoice}" 
             data-choice 
             data-id="${data.id}" 
             data-value="${data.value}" 
             ${data.disabled ? 'aria-disabled="true"' : ''}>
          <span class="label">${data.label}</span>
        </div>
      `;
    }
  }
});

Функциональный подход позволяет:

  • добавлять кастомные атрибуты data-*
  • управлять доступностью через ARIA
  • изменять структуру элемента без изменения логики компонента

Шаблон выбранных элементов (item)

Шаблон item используется для отображения выбранных значений в поле ввода.

templates: {
  item: function (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>
    `;
  }
}

Здесь важно учитывать состояние элемента:

  • выбранный элемент может содержать кнопку удаления
  • можно визуально выделять активные или изменённые значения
  • допускается вложенная структура HTML

Использование условий внутри шаблонов

Функции позволяют внедрять условную логику прямо в генерацию HTML. Это один из ключевых механизмов кастомизации.

item: function (classNames, data) {
  let icon = '';

  if (data.customProperties && data.customProperties.icon) {
    icon = `<span class="icon">${data.customProperties.icon}</span>`;
  }

  return `
    <div class="${classNames.item}">
      ${icon}
      <span>${data.label}</span>
    </div>
  `;
}

Такой подход применяется для:

  • отображения иконок
  • визуальных маркеров состояния
  • различий между типами данных

Шаблоны групп и заголовков

Choices.js поддерживает группировку элементов, и для этого используется шаблон group.

templates: {
  group: function (classNames, data) {
    return `
      <div class="${classNames.group}">
        <div class="${classNames.groupHeading}">
          ${data.label}
        </div>
        <div class="${classNames.groupList}">
        </div>
      </div>
    `;
  }
}

Контекст data в этом случае содержит:

  • label — название группы
  • disabled — состояние группы
  • id — идентификатор

Функциональный шаблон позволяет полностью менять структуру группировки, включая добавление дополнительных элементов, счетчиков или меток.


Использование customProperties в шаблонах

customProperties является расширяемым полем, которое позволяет передавать произвольные данные в шаблон.

{
  value: 'js',
  label: 'JavaScript',
  customProperties: {
    level: 'advanced',
    color: '#f7df1e'
  }
}

Пример использования:

choice: function (classNames, data) {
  return `
    <div class="${classNames.item}" style="border-left: 3px solid ${data.customProperties.color}">
      ${data.label}
      <small>${data.customProperties.level}</small>
    </div>
  `;
}

Это позволяет внедрять:

  • цветовую маркировку
  • уровни сложности
  • дополнительные метаданные без изменения логики Choices.js

Защита от XSS при использовании функций шаблонов

Поскольку шаблоны возвращают HTML-строки, важно учитывать безопасность входных данных. Значения label и value могут быть потенциально небезопасными, если они поступают из внешних источников.

Рекомендуется использовать экранирование:

function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

Применение:

item: function (classNames, data) {
  return `
    <div class="${classNames.item}">
      ${escapeHtml(data.label)}
    </div>
  `;
}

Динамическое поведение шаблонов

Функции шаблонов вызываются при каждом изменении состояния компонента. Это позволяет учитывать:

  • выбранность элемента (selected)
  • активное наведение (active)
  • состояние фокуса
  • фильтрацию результатов

Пример изменения внешнего вида при активности:

choice: function (classNames, data) {
  const activeClass = data.active ? classNames.highlightedState : '';

  return `
    <div class="${classNames.item} ${activeClass}">
      ${data.label}
    </div>
  `;
}

Работа с классами через classNames

Объект classNames централизует все CSS-классы библиотеки. Это позволяет не хардкодить строки классов и сохранять совместимость с внутренней структурой Choices.js.

Пример использования:

templates: {
  item: function (classNames, data) {
    return `<div class="${classNames.item} ${classNames.itemSelectable}">
      ${data.label}
    </div>`;
  }
}

Основные преимущества:

  • согласованность с библиотекой
  • упрощение поддержки
  • возможность адаптации темы через CSS

Разделение логики и представления в шаблонах

Функциональные шаблоны позволяют выносить сложную логику отображения прямо в конфигурацию компонента. При этом сохраняется разделение:

  • Choices.js управляет состоянием
  • шаблоны управляют представлением

Типовой паттерн:

function renderLabel(data) {
  return data.disabled
    ? `<s>${data.label}</s>`
    : data.label;
}

templates: {
  choice: function (classNames, data) {
    return `
      <div class="${classNames.item}">
        ${renderLabel(data)}
      </div>
    `;
  }
}

Ограничения функциональных шаблонов

Несмотря на гибкость, существует ряд ограничений:

  • возвращаемая строка должна быть валидным HTML
  • отсутствует прямой доступ к DOM-узлам внутри шаблона
  • нет встроенного реактивного обновления, кроме перерендеринга
  • чрезмерная сложность функций ухудшает производительность

Оптимизация достигается за счёт:

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

Практика структурирования сложных шаблонов

При усложнении интерфейса шаблоны целесообразно разбивать на функции:

function renderIcon(icon) {
  return icon ? `<span class="icon">${icon}</span>` : '';
}

function renderMeta(meta) {
  return meta ? `<small>${meta}</small>` : '';
}

templates: {
  choice: function (classNames, data) {
    return `
      <div class="${classNames.item}">
        ${renderIcon(data.customProperties?.icon)}
        ${data.label}
        ${renderMeta(data.customProperties?.meta)}
      </div>
    `;
  }
}

Такой подход улучшает читаемость и упрощает сопровождение логики отображения.