Функции рендеринга

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


Архитектура render-слоя

Объект render передаётся в конфигурации при инициализации и содержит набор функций, каждая из которых возвращает HTML-строку.

Ключевая идея заключается в разделении логики данных и представления:

  • данные приходят в виде объектов {value, text, ...}
  • рендер-функции преобразуют их в HTML
  • Tom Select вставляет результат в DOM без дополнительной обработки

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

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

Функция escape критически важна — она предотвращает XSS-уязвимости при вставке пользовательских данных.


Основные функции рендеринга

option

option отвечает за отображение элемента в выпадающем списке.

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

Особенности:

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

item

item формирует отображение выбранного элемента внутри input-зоны.

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

Разделение option и item позволяет создавать разные представления одного и того же объекта в зависимости от контекста.


option_create

option_create используется при включённой возможности добавления новых элементов пользователем (create: true).

render: {
  option_create: function(data, escape) {
    return `
      <div class="create">
        Добавить: <strong>${escape(data.input)}</strong>
      </div>
    `;
  }
}

Особенности:

  • data.input содержит строку, введённую пользователем
  • отображается только при отсутствии совпадений
  • тесно связан с логикой создания новых значений

optgroup_header

Группировка опций реализуется через optgroup_header.

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

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

{
  optgroups: [
    { value: "fruits", label: "Фрукты" }
  ],
  options: [
    { value: "apple", text: "Яблоко", optgroup: "fruits" }
  ]
}

no_results

no_results отображает состояние отсутствия совпадений при поиске.

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

Это состояние активируется после фильтрации списка.


loading

loading используется при асинхронной подгрузке данных (load функция).

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

Особенности:

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

Контекст данных в render-функциях

Каждая функция получает объект data, структура которого зависит от типа элемента:

  • option / item: {value, text, ...customFields}
  • option_create: {input}
  • optgroup_header: {label}
  • loading / no_results: служебные объекты

Дополнительные поля можно свободно добавлять при инициализации:

options: [
  { value: "1", text: "Apple", color: "red" }
]

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

render: {
  option: function(data, escape) {
    return `
      <div>
        <span>${escape(data.text)}</span>
        <small>${escape(data.color)}</small>
      </div>
    `;
  }
}

Функция escape и безопасность

Каждая render-функция получает второй аргумент escape.

Его задача — экранирование HTML-символов:

  • <&lt;
  • >&gt;
  • &&amp;

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

escape(data.text)

Игнорирование escape приводит к потенциальной возможности внедрения HTML/JS-кода в DOM, особенно при загрузке данных с сервера или пользовательского ввода.


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

Tom Select позволяет заменить весь набор рендер-функций:

new TomSelect("#select", {
  render: {
    option: function(data, escape) {
      return `<div>${escape(data.text)}</div>`;
    },
    item: function(data, escape) {
      return `<span>${escape(data.text)}</span>`;
    },
    no_results: function() {
      return `<div>Нет данных</div>`;
    }
  }
});

При отсутствии конкретного render-метода используется стандартная реализация библиотеки.


Условный рендеринг внутри функций

Render-функции допускают любую логику ветвления:

render: {
  option: function(data, escape) {
    if (data.disabled) {
      return `<div class="disabled">${escape(data.text)}</div>`;
    }
    return `<div>${escape(data.text)}</div>`;
  }
}

Таким образом можно:

  • скрывать элементы
  • менять структуру в зависимости от состояния
  • добавлять визуальные маркеры

Производительность render-слоя

Render-функции вызываются часто, особенно при:

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

Оптимизационные практики:

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

Интеграция с кастомными данными

Tom Select не ограничивает структуру данных. Render-слой может использовать любые поля:

{
  value: "js",
  text: "JavaScript",
  icon: "?",
  description: "Язык программирования"
}
render: {
  option: function(data, escape) {
    return `
      <div class="option">
        <span class="icon">${data.icon}</span>
        <div class="content">
          <div class="title">${escape(data.text)}</div>
          <div class="desc">${escape(data.description)}</div>
        </div>
      </div>
    `;
  }
}

Взаимодействие render с состояниями компонента

Render-слой тесно связан с внутренними состояниями:

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

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


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

При сложных интерфейсах render-функции часто выносятся в отдельные шаблоны:

function renderOption(data, escape) {
  return `
    <div class="option">
      ${escape(data.text)}
    </div>
  `;
}

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

Это упрощает поддержку и повторное использование логики представления.


Расширенные сценарии кастомизации

Render-слой может использоваться для:

  • добавления изображений
  • отображения статусов (online/offline)
  • построения карточек вместо строк
  • внедрения интерактивных элементов внутри dropdown

Пример карточного варианта:

render: {
  option: function(data, escape) {
    return `
      <div class="card">
        <img src="${data.avatar}" />
        <div>
          <strong>${escape(data.text)}</strong>
        </div>
      </div>
    `;
  }
}