Фильтры таблиц

Фильтрация таблиц на основе выбранных значений — один из наиболее распространённых сценариев взаимодействия интерфейса с данными. Комбинация табличного представления и многофункционального селектора позволяет строить гибкие системы отбора, в которых Tom Select выступает слоем управления критериями, а таблица — визуализацией результата.

Основная идея заключается в том, что Tom Select формирует набор активных фильтров, которые затем применяются к массиву данных таблицы либо на стороне клиента, либо через серверный запрос. Такой подход разделяет ответственность: компонент выбора отвечает за состояние фильтров, а таблица — за отображение уже отфильтрованного результата.


Модель взаимодействия селектора и таблицы

Связь между Tom Select и таблицей строится через единый источник данных и механизм реактивного обновления.

Типичная архитектура включает:

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

Tom Select в этой схеме управляет состоянием фильтров, генерируя события изменения значения.

const tableData = [
  { id: 1, name: "Alpha", status: "active", category: "A" },
  { id: 2, name: "Beta", status: "archived", category: "B" },
  { id: 3, name: "Gamma", status: "active", category: "A" }
];

let filters = {
  status: [],
  category: []
};

Инициализация Tom Select как фильтрационного слоя

Для каждого поля фильтрации создаётся отдельный экземпляр Tom Select. Значения синхронизируются с объектом состояния фильтров.

const statusSelect = new TomSelect("#status-filter", {
  maxItems: null,
  onChange(values) {
    filters.status = values;
    applyFilters();
  }
});

const categorySelect = new TomSelect("#category-filter", {
  maxItems: null,
  onChange(values) {
    filters.category = values;
    applyFilters();
  }
});

Каждое изменение состояния селектора приводит к пересчёту набора строк таблицы.


Применение локальной фильтрации

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

function applyFilters() {
  let result = tableData;

  if (filters.status.length > 0) {
    result = result.filter(row =>
      filters.status.includes(row.status)
    );
  }

  if (filters.category.length > 0) {
    result = result.filter(row =>
      filters.category.includes(row.category)
    );
  }

  renderTable(result);
}

Функция renderTable отвечает за обновление DOM-таблицы и перерисовку строк на основе отфильтрованного набора.


Рендеринг таблицы после фильтрации

Отрисовка таблицы может выполняться через прямое управление DOM или через шаблонную функцию.

function renderTable(data) {
  const tbody = document.querySelector("tbody");
  tbody.innerHTML = "";

  data.forEach(row => {
    const tr = document.createElement("tr");

    tr.innerHTML = `
      <td>${row.id}</td>
      <td>${row.name}</td>
      <td>${row.status}</td>
      <td>${row.category}</td>
    `;

    tbody.appendChild(tr);
  });
}

При изменении фильтров пересобирается только тело таблицы, без изменения структуры.


Множественные независимые фильтры

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

Расширение модели состояния:

let filters = {
  status: [],
  category: [],
  tags: []
};

Добавляется дополнительный селектор:

const tagsSelect = new TomSelect("#tags-filter", {
  maxItems: null,
  onChange(values) {
    filters.tags = values;
    applyFilters();
  }
});

Фильтрация по массивным полям требует дополнительной логики сопоставления:

if (filters.tags.length > 0) {
  result = result.filter(row =>
    filters.tags.some(tag => row.tags.includes(tag))
  );
}

Кастомные функции фильтрации

Tom Select позволяет задавать собственную логику поиска внутри выпадающего списка, но фильтрация таблицы может иметь отдельные правила, не совпадающие с поисковым алгоритмом селектора.

Пример кастомной логики сравнения:

function matchRule(row, filters) {
  return (
    (filters.status.length === 0 || filters.status.includes(row.status)) &&
    (filters.category.length === 0 || filters.category.includes(row.category)) &&
    (filters.tags.length === 0 ||
      filters.tags.some(tag => row.tags.includes(tag)))
  );
}

Использование унифицированного предиката упрощает масштабирование системы фильтрации.


Асинхронная фильтрация и серверный режим

При больших объёмах данных локальная фильтрация становится неэффективной. В таких случаях Tom Select используется как генератор параметров для серверного запроса.

async function applyFilters() {
  const query = new URLSearchParams();

  filters.status.forEach(v => query.append("status", v));
  filters.category.forEach(v => query.append("category", v));

  const response = await fetch(`/api/table?${query.toString()}`);
  const data = await response.json();

  renderTable(data);
}

Tom Select при этом остаётся источником состояния, но не участвует в обработке данных.


Асинхронная загрузка опций фильтра

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

const categorySelect = new TomSelect("#category-filter", {
  load: function(query, callback) {
    fetch(`/api/categories?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => callback(data))
      .catch(() => callback());
  }
});

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


Синхронизация состояния фильтров с URL

Состояние фильтров может быть сериализовано в адресную строку для сохранения представления таблицы.

function syncToUrl() {
  const params = new URLSearchParams();

  filters.status.forEach(v => params.append("status", v));
  filters.category.forEach(v => params.append("category", v));

  history.replaceState(null, "", "?" + params.toString());
}

Обратная синхронизация:

function readFromUrl() {
  const params = new URLSearchParams(location.search);

  filters.status = params.getAll("status");
  filters.category = params.getAll("category");

  statusSelect.setValue(filters.status);
  categorySelect.setValue(filters.category);
}

Производительность при большом объёме данных

При росте количества строк таблицы ключевым фактором становится стоимость фильтрации и перерисовки DOM.

Оптимизационные подходы включают:

  • кэширование результатов фильтрации;
  • использование debounce на события изменения Tom Select;
  • виртуализацию таблицы;
  • разделение фильтрации и рендера.

Пример debounce:

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

const applyFiltersDebounced = debounce(applyFilters, 150);

Комбинированные сценарии фильтрации

В сложных интерфейсах фильтры часто работают совместно с поиском, сортировкой и пагинацией. Tom Select в таких системах формирует только часть параметров запроса.

Типовая цепочка обработки данных:

  • сбор состояния фильтров;
  • применение поискового запроса;
  • сортировка результата;
  • разбиение на страницы;
  • отображение текущего сегмента.

Такая модель позволяет сохранять предсказуемость поведения интерфейса при изменении любого из параметров.


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

Фильтры могут зависеть друг от друга. Например, выбор категории влияет на доступные статусы. Tom Select в этом случае используется как каскадный контроллер.

categorySelect.on("change", async (values) => {
  const res = await fetch(`/api/statuses?category=${values.join(",")}`);
  const statuses = await res.json();

  statusSelect.clearOptions();
  statusSelect.addOptions(statuses);
});

Такая связка позволяет формировать адаптивные фильтрационные панели, где доступные значения изменяются в зависимости от текущего контекста данных.