Кастомизация отображения элементов

В библиотеке Tom Select отображение элементов строится на системе рендер-функций (render functions), которые позволяют полностью контролировать HTML-разметку как выбранных значений, так и выпадающего списка. Центральная идея заключается в том, что каждая часть интерфейса не фиксирована, а формируется через набор шаблонов, переопределяемых разработчиком.

Основной объект кастомизации — параметр render, принимающий набор функций:

  • option — отображение элемента в списке
  • item — отображение выбранного элемента
  • option_create — отображение создаваемого элемента
  • optgroup_header — заголовки групп
  • no_results — состояние отсутствия результатов
  • loading — индикатор загрузки

Каждая функция получает два аргумента: data (данные элемента) и escape (функция экранирования HTML).

new TomSelect("#select", {
  render: {
    option: function(data, escape) {
      return `<div>${escape(data.text)}</div>`;
    },
    item: function(data, escape) {
      return `<span>${escape(data.text)}</span>`;
    }
  }
});

Такой подход позволяет отделить данные от представления и полностью контролировать DOM-структуру.


Структура объекта данных и влияние на рендеринг

Каждый элемент, передаваемый в Tom Select, представляет собой объект, содержащий минимум поле text. Однако реальная гибкость достигается за счёт расширения структуры:

  • value — значение элемента
  • text — отображаемый текст
  • пользовательские поля (image, description, badge, meta)

Эти дополнительные поля напрямую используются внутри render-функций.

{
  value: "js",
  text: "JavaScript",
  image: "/icons/js.png",
  description: "Язык программирования",
  badge: "pop"
}

Использование расширенных данных позволяет создавать сложные UI-компоненты прямо внутри выпадающего списка.


Кастомизация списка опций (option)

Функция option отвечает за отображение элементов внутри dropdown. Это ключевая точка кастомизации, так как именно здесь формируется визуальная структура списка.

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

render: {
  option: function(data, escape) {
    return `
      <div class="option">
        <img src="${escape(data.image)}" class="option-avatar">
        <div class="option-body">
          <div class="option-title">${escape(data.text)}</div>
          <div class="option-desc">${escape(data.description)}</div>
        </div>
      </div>
    `;
  }
}

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


Кастомизация выбранных элементов (item)

Отображение выбранного элемента часто требует более компактной и визуально упрощённой формы. Функция item отвечает за то, как выбранные значения отображаются внутри инпута.

render: {
  item: function(data, escape) {
    return `
      <div class="selected-item">
        <span class="label">${escape(data.text)}</span>
        <span class="badge">${escape(data.badge)}</span>
      </div>
    `;
  }
}

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


Различие между option и item

Несмотря на схожесть, option и item имеют принципиальные различия:

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

В сложных интерфейсах часто применяется принцип «богатый option — упрощённый item».


Группировка и кастомизация optgroup

При использовании группировки данных Tom Select предоставляет optgroup_header. Он отвечает за визуальное оформление заголовков групп.

render: {
  optgroup_header: function(data, escape) {
    return `
      <div class="optgroup-header">
        ${escape(data.label)}
      </div>
    `;
  }
}

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


Состояния no_results и loading

Кастомизация состояний интерфейса позволяет улучшить UX при динамическом поиске.

no_results используется, когда поиск не дал совпадений:

render: {
  no_results: function(data, escape) {
    return `<div class="no-results">Ничего не найдено</div>`;
  }
}

loading отображается при асинхронной загрузке данных:

render: {
  loading: function(data, escape) {
    return `<div class="loading-state">Загрузка...</div>`;
  }
}

Эти состояния особенно важны при использовании remote data и AJAX-запросов.


Использование escape и защита от XSS

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

Неправильный подход:

return `<div>${data.text}</div>`;

Правильный подход:

return `<div>${escape(data.text)}</div>`;

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


Добавление интерактивных элементов внутри render

Render-функции допускают включение интерактивных элементов, включая кнопки и индикаторы. Однако следует учитывать, что Tom Select повторно рендерит элементы при обновлении состояния.

Пример добавления кнопки удаления:

render: {
  item: function(data, escape) {
    return `
      <div class="tag-item">
        <span>${escape(data.text)}</span>
        <button type="button" class="remove">×</button>
      </div>
    `;
  }
}

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


Управление классами и структурой DOM

Хотя Tom Select не навязывает строгую структуру HTML, рекомендуется придерживаться согласованной системы классов:

  • единый префикс для всех элементов (ts-, select-, option-)
  • разделение визуальных блоков (header, body, meta)
  • минимизация вложенности

Это упрощает поддержку CSS и предотвращает конфликты при интеграции в крупные проекты.


Адаптация под темы и дизайн-системы

Кастомизация render-функций часто используется для интеграции с дизайн-системами. В таких случаях структура HTML должна соответствовать компонентной модели:

  • avatar → пользовательские аватары
  • badge → статусы
  • meta → дополнительная информация
  • icon → тип элемента

Пример:

render: {
  option: function(data, escape) {
    return `
      <div class="ds-option">
        <div class="ds-icon">${escape(data.icon)}</div>
        <div class="ds-content">
          <div class="ds-title">${escape(data.text)}</div>
          <div class="ds-meta">${escape(data.meta)}</div>
        </div>
      </div>
    `;
  }
}

Ограничения и производительность рендеринга

При сложной кастомизации важно учитывать влияние на производительность:

  • большое количество DOM-узлов замедляет рендеринг
  • сложные шаблоны увеличивают время обновления списка
  • изображения без lazy-loading могут перегружать интерфейс

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

  • минимизации HTML внутри render
  • кеширования вычисляемых значений
  • использования CSS вместо лишней разметки
  • отказа от тяжёлых операций внутри render-функций

Переиспользование шаблонов рендеринга

Для крупных проектов целесообразно выносить render-функции в отдельные модули. Это позволяет поддерживать единый стиль отображения во всех инстансах Tom Select.

export const renderTemplates = {
  option: (data, escape) => `...`,
  item: (data, escape) => `...`
};

И затем использовать их повторно:

new TomSelect("#select", {
  render: renderTemplates
});

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