Шаблон для dropdown

Choices.js предоставляет систему шаблонов, позволяющую полностью переопределять отображение элементов выпадающего списка (dropdown), выбранных значений и групп. Механизм основан на наборе функций, возвращающих HTML-строки, которые библиотека вставляет в DOM без дополнительной логики рендера. Основная цель — дать полный контроль над визуальной структурой и поведением элементов без необходимости форка или модификации ядра.


Внутренне Choices.js разделяет визуализацию на несколько независимых слоёв:

  • choice — элемент внутри выпадающего списка
  • item — выбранный элемент в поле ввода
  • group — заголовок группы элементов
  • loading — состояние загрузки данных
  • noResults / noChoices — служебные состояния интерфейса

Каждый слой формируется через функцию-шаблон, возвращающую HTML-строку. Эти функции передаются через параметр callbackOnCreateTemplates или через объект templates (в зависимости от версии библиотеки).


Базовая структура templates

Основной объект шаблонов задаётся при инициализации:

const instance = new Choices('#select', {
  templates: {
    item: (classNames, data) => {},
    choice: (classNames, data) => {},
    group: (classNames, data) => {},
    loading: (classNames) => {},
    noResults: (classNames) => {},
    noChoices: (classNames) => {}
  }
});

Каждая функция получает:

  • classNames — объект с CSS-классами библиотеки
  • data — объект текущего элемента (если применимо)

Шаблон элемента dropdown (choice)

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

Структура данных data:

  • id — идентификатор
  • value — значение
  • label — текст
  • selected — выбран ли элемент
  • disabled — недоступен ли
  • customProperties — дополнительные данные

Пример базового переопределения:

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

Особенности поведения choice

  • Элемент должен содержать атрибут data-choice
  • Обязательно присутствие data-id и data-value
  • Атрибут data-choice-selectable определяет кликабельность
  • Отключённые элементы должны содержать aria-disabled

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


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

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

templates: {
  item: (classNames, data) => {
    return `
      <div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}"
           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-item">
          ×
        </button>
      </div>
    `;
  }
}

Логика item

  • data-item обязателен для обработки удаления
  • data-button="remove-item" связывает кнопку с внутренним обработчиком
  • highlighted используется при навигации клавиатурой

Шаблоны групп (group)

Группы используются при grouped options, когда данные структурированы:

{
  label: 'Fruits',
  id: 1,
  disabled: false,
  choices: [...]
}

Переопределение:

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

Важные детали

  • data.children уже содержит сгенерированные choice-элементы
  • Группы не требуют ручной итерации
  • Стилизация группы влияет на структуру вложенности dropdown

Служебные шаблоны состояния

noResults

Отображается при отсутствии совпадений:

templates: {
  noResults: (classNames) => {
    return `
      <div class="${classNames.noResults}">
        Ничего не найдено
      </div>
    `;
  }
}

noChoices

Используется, когда список пуст:

noChoices: (classNames) => {
  return `
    <div class="${classNames.noChoices}">
      Данные отсутствуют
    </div>
  `;
}

loading

Состояние загрузки при async-режиме:

loading: (classNames) => {
  return `
    <div class="${classNames.item} ${classNames.loading}">
      Загрузка...
    </div>
  `;
}

Работа с customProperties

customProperties позволяет расширять данные элементов без изменения API:

{
  value: 'paris',
  label: 'Paris',
  customProperties: {
    country: 'France',
    population: 2148000
  }
}

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

choice: (classNames, data) => {
  return `
    <div class="${classNames.item}" data-choice data-id="${data.id}">
      <div>${data.label}</div>
      <small>${data.customProperties.country}</small>
    </div>
  `;
}

HTML-санитизация и безопасность

Choices.js не выполняет автоматическую очистку HTML внутри шаблонов. Любые вставки:

  • data.label
  • data.value
  • customProperties

могут содержать потенциально опасный HTML.

Типовая стратегия защиты:

const escapeHtml = (str) => {
  return String(str)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#039;');
};

Использование:

label: escapeHtml(data.label)

Динамические состояния внутри dropdown

Шаблоны часто комбинируются с состояниями:

  • active
  • highlighted
  • selected
  • disabled

Пример комбинированной логики:

choice: (classNames, data) => {
  const classes = [
    classNames.item,
    classNames.itemChoice,
    data.selected ? classNames.selectedState : '',
    data.disabled ? classNames.disabledState : ''
  ].join(' ');

  return `
    <div class="${classes}"
         data-choice
         data-id="${data.id}"
         data-value="${data.value}">
      ${data.label}
    </div>
  `;
}

Переиспользование шаблонов через фабрики

Для сложных интерфейсов шаблоны выносятся в фабрики:

const createChoiceTemplate = (iconMap) => {
  return (classNames, data) => {
    return `
      <div class="${classNames.item}" data-choice data-id="${data.id}">
        <span class="icon">${iconMap[data.value] || ''}</span>
        <span>${data.label}</span>
      </div>
    `;
  };
};

Использование:

templates: {
  choice: createChoiceTemplate({
    apple: '?',
    banana: '?'
  })
}

Интеграция с async-данными

При загрузке данных через API шаблоны часто комбинируются с состоянием loading и noResults.

Пример сценария:

new Choices('#select', {
  searchEnabled: true,
  shouldSort: false,
  templates: {
    loading: (classNames) => `
      <div class="${classNames.loading}">
        Загрузка данных...
      </div>
    `,
    noResults: (classNames) => `
      <div class="${classNames.noResults}">
        Совпадения отсутствуют
      </div>
    `
  }
});

Влияние шаблонов на производительность

Использование сложных шаблонов влияет на:

  • частоту перерисовки dropdown
  • размер DOM-дерева
  • скорость поиска

Критические факторы:

  • избегание тяжёлых вычислений внутри template-функций
  • минимизация DOM-структуры
  • отсутствие повторных вычислений внутри render loop

Оптимизированный подход:

  • подготовка данных заранее
  • кэширование HTML-фрагментов
  • использование простых строковых шаблонов

Ограничения системы шаблонов

Механизм шаблонов не поддерживает:

  • виртуальный DOM
  • компонентную модель
  • реактивное обновление
  • diffing элементов

Каждое обновление списка приводит к полной перегенерации HTML-строк для отображаемой части dropdown, что требует аккуратного проектирования сложных UI-расширений.