Кастомизация отображения выбранных значений

Механизм отображения выбранных значений в Tom Select основан на системе рендеринга, которая разделяет визуализацию выпадающего списка и выбранных элементов внутри control-зоны. Именно слой выбранных значений чаще всего требует кастомизации, поскольку именно он формирует интерфейс «чипов», тегов, аватаров, бейджей и сложных структур данных.


Архитектура отображения выбранных элементов

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

  • render.item — элемент внутри dropdown списка
  • render.option — визуализация опции в списке
  • render.item (или render.selection в некоторых конфигурациях) — отображение выбранного значения
  • render.optgroup_header — заголовки групп

Для кастомизации выбранных значений ключевым является именно render.item, поскольку он отвечает за то, как элемент превращается в «чип» внутри control-блока.


Базовая кастомизация выбранного элемента

Стандартная конфигурация может быть переопределена через объект render:

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

Здесь:

  • data — объект выбранной опции
  • escape — функция для безопасного экранирования HTML
  • возвращаемая строка — HTML-разметка, вставляемая в control

Формирование сложных «чипов»

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

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

new TomSelect("#users", {
  render: {
    item: function(data, escape) {
      return `
        <div class="user-chip">
          <img class="avatar" src="${escape(data.avatar)}" />
          <span class="name">${escape(data.name)}</span>
          <span class="role">${escape(data.role)}</span>
        </div>
      `;
    }
  }
});

Такой подход превращает стандартный input в визуальный компонент уровня UI-фреймворка.


Разделение отображения dropdown и выбранных значений

Важный аспект архитектуры заключается в том, что render.option и render.item не обязаны совпадать.

new TomSelect("#products", {
  render: {
    option: function(data, escape) {
      return `<div class="option">${escape(data.title)} — ${escape(data.price)}</div>`;
    },

    item: function(data, escape) {
      return `<div class="selected-product">${escape(data.title)}</div>`;
    }
  }
});

Такое разделение позволяет:

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

Управление структурой «чипов»

Отображение выбранных элементов часто требует изменения поведения контейнера control.

Ограничение количества выбранных элементов

new TomSelect("#tags", {
  maxItems: 3
});

При превышении лимита библиотека автоматически перестраивает control-зону.


Кастомные классы через render

Для глубокой интеграции с CSS-системами используется добавление классов внутри шаблонов:

new TomSelect("#select", {
  render: {
    item: function(data, escape) {
      const typeClass = data.type ? `type-${data.type}` : "";

      return `
        <div class="ts-chip ${typeClass}">
          ${escape(data.text)}
        </div>
      `;
    }
  }
});

Это позволяет:

  • подключать дизайн-системы
  • использовать utility CSS (например, BEM или Tailwind-подобные классы)
  • стилизовать элементы по данным

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

Каждый выбранный элемент хранит полный объект данных, включая кастомные поля.

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

{
  value: "42",
  text: "John Doe",
  role: "admin",
  avatar: "/img/john.png"
}

Отображение:

new TomSelect("#users", {
  render: {
    item: function(data, escape) {
      return `
        <div class="chip">
          <img src="${escape(data.avatar)}" />
          <span>${escape(data.text)}</span>
        </div>
      `;
    }
  }
});

Безопасность при кастомном рендеринге

Использование HTML в render.item требует строгого экранирования всех динамических данных.

Функция escape обязательна для:

  • data.text
  • пользовательских строк
  • любых внешних значений

Игнорирование экранирования приводит к XSS-уязвимостям, особенно при загрузке данных с API.


Изменение поведения удаления выбранных элементов

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

.ts-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border-radius: 12px;
}

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

new TomSelect("#select", {
  plugins: {
    remove_button: null
  }
});

Динамическое обновление отображения

Изменение выбранных значений программно приводит к повторному вызову рендеринга:

const control = new TomSelect("#select");

control.addItem("value1");
control.addItem("value2");

control.updateItem("value1", {
  text: "Updated label",
  avatar: "/new.png"
});

После обновления UI автоматически перестраивает соответствующий чип.


Кастомизация контейнера выбранных значений

Control-зона может быть дополнительно модифицирована через CSS-классы:

  • .ts-control — основной контейнер
  • .ts-wrapper — общий wrapper
  • .item — выбранный элемент

Пример:

.ts-control {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

Это позволяет реализовать:

  • многострочные списки выбранных значений
  • адаптивные чипы
  • гибкую верстку

Различие одиночного и множественного выбора

В режиме single selection выбранный элемент отображается внутри input-поля, а не как список чипов.

new TomSelect("#single", {
  maxItems: 1,
  render: {
    item: function(data, escape) {
      return `<div class="single-value">${escape(data.text)}</div>`;
    }
  }
});

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

  • control содержит только один визуальный элемент
  • поведение ближе к autocomplete

Использование пользовательских контейнеров

Иногда требуется изменить структуру control-зоны полностью:

new TomSelect("#select", {
  render: {
    item: function(data, escape) {
      return `
        <span class="custom-tag">
          <strong>${escape(data.text)}</strong>
        </span>
      `;
    }
  }
});

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


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

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

new TomSelect("#icons", {
  render: {
    item: function(data, escape) {
      return `
        <div class="icon-item">
          <i class="${escape(data.icon)}"></i>
          <span>${escape(data.text)}</span>
        </div>
      `;
    }
  }
});

Такой подход используется в:

  • системах ролей
  • списках категорий
  • интерфейсах с визуальными метками

Управление состоянием через data attributes

Для связи UI и логики можно добавлять data-атрибуты:

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

Это позволяет:

  • отслеживать элементы через DOM
  • интегрировать сторонние скрипты
  • строить сложные события поверх UI

Поведение при переполнении control-зоны

Когда выбранных элементов становится много, применяется стратегия overflow:

  • перенос на новую строку
  • скрытие части элементов
  • сокращение отображения (через кастомную логику)

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

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

Комбинирование render с событиями

Отображение часто синхронизируется с событиями:

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

select.on("item_add", function(value, item) {
  item.classList.add("added-animation");
});

Это позволяет добавлять анимации появления выбранных элементов.


Гибридные шаблоны отображения

Сложные интерфейсы часто комбинируют несколько источников данных:

render: {
  item: function(data, escape) {
    const badge = data.isNew ? `<span class="badge">NEW</span>` : "";

    return `
      <div class="chip">
        ${escape(data.text)}
        ${badge}
      </div>
    `;
  }
}

Такие конструкции превращают control-зону в полноценный UI-компонент с логикой отображения состояния.