Шаблон для групп

Работа с группировкой опций в Choices.js опирается на механизм optgroup-подобной структуры данных и систему шаблонов (templates), отвечающих за визуализацию как самих элементов, так и заголовков групп. Шаблон для групп является ключевым элементом кастомизации, позволяющим полностью контролировать внешний вид разделителей и логики отображения сгруппированных наборов значений.

Группы в Choices.js формируются через вложенную структуру данных, где каждая группа содержит мета-информацию и массив опций:

  • label — отображаемое имя группы
  • id — уникальный идентификатор (используется внутренне)
  • disabled — блокировка всей группы
  • choices — массив элементов внутри группы

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

const groupedData = [
  {
    label: 'Фронтенд',
    id: 1,
    disabled: false,
    choices: [
      { value: 'react', label: 'React' },
      { value: 'vue', label: 'Vue' },
      { value: 'svelte', label: 'Svelte' }
    ]
  },
  {
    label: 'Бэкенд',
    id: 2,
    choices: [
      { value: 'node', label: 'Node.js' },
      { value: 'django', label: 'Django' },
      { value: 'go', label: 'Go' }
    ]
  }
];

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

Внутреннее представление групп

После инициализации библиотека преобразует входные данные в нормализованную структуру:

  • группы становятся контейнерами (group nodes)
  • элементы внутри групп получают контекст принадлежности
  • добавляется связь между choice и group

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

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

Шаблон группы — это функция, определяющая, как будет отображаться заголовок группы в выпадающем списке. Он задается через конфигурацию templates.group.

Базовый синтаксис:

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

Контекст параметров шаблона

Функция шаблона получает:

  • classNames — объект с системными CSS-классами
  • data — объект группы (label, id, disabled, choices)

В некоторых конфигурациях также доступны дополнительные поля:

  • activeItems
  • disabled
  • parent контекст

Разделение ответственности шаблона

Шаблон группы отвечает исключительно за:

  • визуализацию заголовка группы
  • структуру DOM-элемента группы
  • добавление кастомных классов
  • включение или отключение интерактивности

Логика выбора элементов и фильтрации не должна находиться внутри шаблона.

Кастомизация заголовка группы

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

Пример расширенного шаблона:

templates: {
  group: (classNames) => {
    return (group) => {
      const count = group.choices ? group.choices.length : 0;

      return `
        <div class="${classNames.group}">
          <div class="group-header">
            <span class="${classNames.groupHeading}">
              ${group.label}
            </span>
            <span class="group-count">
              ${count}
            </span>
          </div>
        </div>
      `;
    };
  }
}

В данном случае добавляется вычисляемый элемент — количество опций внутри группы, что позволяет визуально оценивать объем категории.

Условное отображение групп

Шаблон может учитывать состояние группы:

  • отключенные группы
  • пустые группы
  • группы, скрытые фильтрацией поиска

Пример обработки состояния:

templates: {
  group: (classNames) => {
    return (group) => {
      if (!group.choices || group.choices.length === 0) {
        return '';
      }

      const isDisabled = group.disabled;

      return `
        <div class="${classNames.group} ${isDisabled ? 'is-disabled' : ''}">
          <span class="${classNames.groupHeading}">
            ${group.label}
          </span>
        </div>
      `;
    };
  }
}

Пустая строка в качестве возврата фактически исключает группу из DOM, что влияет на финальное отображение списка.

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

Группа не существует изолированно — она тесно связана с шаблоном элементов (choice template). При рендеринге происходит последовательность:

  1. создается контейнер группы
  2. рендерится заголовок группы через group template
  3. рендерятся элементы через choice template
  4. группа вставляется в общий список

Таким образом, group template влияет только на первый уровень структуры, не затрагивая дочерние элементы.

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

Объект classNames является центральным механизмом унификации стилей:

  • group — контейнер группы
  • groupHeading — заголовок
  • choice — элемент выбора
  • item — выбранный элемент

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

templates: {
  group: (classNames) => (group) => `
    <div class="${classNames.group}">
      <span class="${classNames.groupHeading}">
        ${group.label}
      </span>
    </div>
  `
}

Преимущество такого подхода — независимость от конкретных CSS-имен, заданных библиотекой по умолчанию.

Динамическое изменение шаблона

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

Пример смены логики отображения:

const instance = new Choices('#select', {
  choices: groupedData,
  templates: {
    group: (classNames) => (group) => `
      <div class="${classNames.group}">
        <strong>${group.label}</strong>
      </div>
    `
  }
});

Любая модификация шаблона требует учета того, что DOM пересоздается при перерендере списка.

Комбинация групп и фильтрации

При активной фильтрации поиском группы могут:

  • сохраняться, если хотя бы один элемент совпадает
  • скрываться полностью, если совпадений нет
  • отображаться частично (часть элементов скрыта)

Шаблон группы не управляет фильтрацией, но может визуально отражать её результат:

templates: {
  group: (classNames) => (group) => {
    const hasVisibleChoices = group.choices?.some(c => !c.disabled);

    return `
      <div class="${classNames.group} ${hasVisibleChoices ? '' : 'is-empty'}">
        <span class="${classNames.groupHeading}">
          ${group.label}
        </span>
      </div>
    `;
  }
}

Ограничения шаблона группы

При проектировании кастомного group template необходимо учитывать ограничения:

  • отсутствие доступа к DOM API внутри шаблона
  • отсутствие прямого управления событиями
  • невозможность изменять структуру дочерних элементов
  • работа только в рамках рендера строки/DOM-фрагмента

Шаблон остается чистой функцией отображения без состояния.

Роль группового шаблона в архитектуре Choices.js

Group template выполняет функцию визуального уровня абстракции между:

  • сырыми данными (choices data)
  • внутренней моделью библиотеки
  • итоговым DOM

Он обеспечивает:

  • разделение логики и представления
  • возможность стилизации без изменения данных
  • поддержку сложных интерфейсных сценариев (категории, фильтры, метки)

В результате шаблон групп становится точкой расширения, через которую реализуются сложные UI-паттерны в списках выбора.