Dropdown Footer

Dropdown Footer представляет собой нижнюю часть выпадающего списка, которая рендерится под всеми опциями и остаётся фиксированной в пределах контейнера dropdown. В архитектуре Tom Select эта область используется как расширяемый слот интерфейса, позволяющий добавлять управляющие элементы, вспомогательную информацию или интерактивные действия, связанные с выбором значений.

Footer выполняет функцию дополнительного UI-слоя, который не участвует в механике выбора напрямую, но влияет на пользовательский сценарий взаимодействия со списком:

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

В отличие от header, который обычно отвечает за ввод и фильтрацию, footer ориентирован на завершение логики взаимодействия и вспомогательные действия.

Внутренняя структура dropdown в Tom Select строится из трёх основных зон:

  • header (верхняя часть: поиск, фильтры)
  • options (основной список элементов)
  • footer (нижняя фиксированная зона)

Footer рендерится после списка опций, но до закрывающего контейнера dropdown. Это важно для понимания порядка обновления DOM и поведения при виртуализации списка.

Ключевая особенность: footer не пересоздаётся при каждом ререндере options, если он вынесен в отдельный рендер-функционал плагина или кастомного renderer-а.


В Tom Select footer чаще всего реализуется через переопределение render.dropdown_footer.

new TomSelect("#select", {
  render: {
    dropdown_footer: function (data, escape) {
      return `
        <div class="ts-dropdown-footer">
          <button type="button" class="ts-action-btn">
            Добавить новый элемент
          </button>
        </div>
      `;
    }
  }
});

Особенности реализации:

  • функция вызывается при построении dropdown;
  • возвращает HTML-строку;
  • escape используется для безопасного вывода динамического текста;
  • footer вставляется строго после списка опций.

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

new TomSelect("#select", {
  load: function (query, callback) {
    fetch(`/api/items?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => callback(data))
      .catch(() => callback());
  },

  render: {
    dropdown_footer: function (data, escape) {
      const count = data.options ? Object.keys(data.options).length : 0;

      return `
        <div class="ts-dropdown-footer">
          <span class="ts-info">
            Найдено: ${escape(count)}
          </span>
        </div>
      `;
    }
  }
});

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


Одно из ключевых применений footer — добавление кнопок управления списком.

new TomSelect("#select", {
  persist: false,

  render: {
    dropdown_footer: function () {
      return `
        <div class="ts-dropdown-footer">
          <button type="button" class="add-item-btn">
            + Создать новый элемент
          </button>
        </div>
      `;
    }
  },

  onInitialize: function () {
    this.dropdown_content.addEventListener("click", (e) => {
      if (e.target.classList.contains("add-item-btn")) {
        const value = prompt("Введите значение");
        if (value) {
          this.addOption({ value, text: value });
          this.addItem(value);
        }
      }
    });
  }
});

Особенность такого подхода заключается в том, что footer остаётся частью dropdown DOM, поэтому делегирование событий предпочтительнее прямых обработчиков.


Footer часто используется для отображения системных состояний:

  • загрузка данных;
  • отсутствие результатов;
  • превышение лимита;
  • ошибки API.
new TomSelect("#select", {
  render: {
    dropdown_footer: function (data) {
      if (this.loading) {
        return `<div class="ts-dropdown-footer">Загрузка...</div>`;
      }

      if (!data.options || Object.keys(data.options).length === 0) {
        return `<div class="ts-dropdown-footer">Нет данных</div>`;
      }

      return `<div class="ts-dropdown-footer">Готово к выбору</div>`;
    }
  }
});

Здесь footer становится реактивным индикатором состояния компонента, хотя сам по себе не является reactive-системой — обновление происходит через перерисовку dropdown.


CSS-уровень играет ключевую роль в визуальной интеграции footer с остальной частью dropdown.

.ts-dropdown-footer {
  padding: 8px 10px;
  border-top: 1px solid #e5e5e5;
  background: #fafafa;
  font-size: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.ts-dropdown-footer .ts-action-btn {
  background: none;
  border: none;
  color: #2b7cff;
  cursor: pointer;
}

.ts-dropdown-footer .ts-action-btn:hover {
  text-decoration: underline;
}

Важные моменты:

  • footer визуально отделяется границей;
  • фон отличается от списка options;
  • кнопки не должны конфликтовать со стилями option элементов;
  • высота footer должна быть предсказуемой, чтобы не сдвигать scroll unexpectedly.

Dropdown в Tom Select часто ограничен по высоте, поэтому footer становится частью scroll-контейнера.

Это приводит к важному поведению:

  • footer прокручивается вместе с options;
  • при закреплении footer требуется кастомная структура DOM;
  • sticky-поведение достигается через CSS.

Пример закрепления footer:

.ts-dropdown {
  display: flex;
  flex-direction: column;
}

.ts-dropdown-content {
  overflow-y: auto;
  flex: 1;
}

.ts-dropdown-footer {
  position: sticky;
  bottom: 0;
}

Такой подход превращает footer в фиксированную панель действий внутри dropdown.


Плагины Tom Select могут модифицировать поведение footer:

  • плагин dropdown_input может перемещать input выше footer;
  • virtual_scroll влияет на необходимость фиксации footer;
  • кастомные плагины могут добавлять свои footer-слои.

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

  1. header plugins
  2. options renderer
  3. footer renderer

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


В сложных интерфейсах footer может разделяться на несколько зон логически, хотя физически остаётся одним блоком:

render: {
  dropdown_footer: function () {
    return `
      <div class="ts-dropdown-footer">
        <div class="footer-stats">Результаты обновлены</div>
        <div class="footer-actions">
          <button class="reload-btn">Обновить</button>
          <button class="clear-btn">Очистить</button>
        </div>
      </div>
    `;
  }
}

Такой подход позволяет:

  • разделять информационные и управляющие элементы;
  • упрощать масштабирование UI;
  • поддерживать сложные сценарии взаимодействия без изменения core логики компонента.

При изменении состояния компонента происходит пересборка dropdown:

  • изменение поиска → перерисовка footer;
  • изменение selection → возможное обновление текста footer;
  • очистка → reset footer state.

Внутренне это связано с повторным вызовом render-функций, что делает footer зависимым от текущего контекста TomSelectInstance.


Ограничения и особенности реализации

Footer в Tom Select имеет ряд ограничений:

  • отсутствует отдельный lifecycle hook только для footer;
  • нет встроенной реактивности без перерендера dropdown;
  • события нужно делегировать вручную;
  • сложная логика внутри footer может усложнить поддержку.

При этом архитектура остаётся предсказуемой: footer всегда является частью DOM dropdown и не отделяется в отдельный слой компонента.