Поиск по нескольким полям

В Tom Select механизм поиска изначально рассчитан на работу не только с одиночным текстовым полем, но и с набором атрибутов объекта. Ключевым параметром выступает searchField, который принимает массив строк с именами полей данных, по которым выполняется индексирование и последующий поиск.

new TomSelect("#select", {
  valueField: "id",
  labelField: "title",
  searchField: ["title", "description", "tags"],
  options: [
    { id: 1, title: "JavaScript", description: "Язык программирования", tags: "js,frontend" },
    { id: 2, title: "TypeScript", description: "Надстройка над JS", tags: "ts,typing" }
  ]
});

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


Поведение поиска при нескольких полях

При использовании массива searchField Tom Select объединяет значения всех указанных свойств объекта в единый поисковый индекс. Однако логика сравнения не является простым конкатенированием строк — каждое поле участвует в вычислении релевантности отдельно.

Если совпадение найдено в нескольких полях одного объекта, итоговый score увеличивается. Это создаёт естественное ранжирование:

  • совпадение в title даёт максимальный вес
  • совпадение в description — средний
  • совпадение в tags — минимальный, если не переопределено

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


Приоритетизация полей через кастомный score

Для тонкого контроля над многопольным поиском используется функция score. Она позволяет переопределить вклад каждого поля в итоговую релевантность.

new TomSelect("#select", {
  valueField: "id",
  labelField: "title",
  searchField: ["title", "description", "tags"],

  score: function(search) {
    return function(item) {
      let score = 0;

      if (item.title.toLowerCase().includes(search.toLowerCase())) {
        score += 10;
      }

      if (item.description.toLowerCase().includes(search.toLowerCase())) {
        score += 5;
      }

      if (item.tags.toLowerCase().includes(search.toLowerCase())) {
        score += 2;
      }

      return score;
    };
  }
});

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


Поиск по вложенным структурам данных

Во многих прикладных сценариях данные не являются плоскими. Объекты могут содержать вложенные структуры:

{
  id: 1,
  title: "React",
  meta: {
    description: "UI библиотека",
    keywords: ["frontend", "ui", "components"]
  }
}

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

Подход с предобработкой

function normalizeOptions(data) {
  return data.map(item => ({
    ...item,
    search_blob: [
      item.title,
      item.meta?.description,
      (item.meta?.keywords || []).join(" ")
    ].join(" ")
  }));
}

Далее поиск строится по одному агрегированному полю:

new TomSelect("#select", {
  valueField: "id",
  labelField: "title",
  searchField: ["search_blob"],
  options: normalizeOptions(data)
});

Такой подход снижает гибкость, но повышает предсказуемость и производительность индексации.


Влияние регистра, диакритики и нормализации строк

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

Для унификации данных часто применяется нормализация Unicode:

function normalizeString(str) {
  return str
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "");
}

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


Расширенная логика фильтрации через filter-option

Параметр filter позволяет полностью переопределить логику включения элемента в результаты поиска.

new TomSelect("#select", {
  valueField: "id",
  labelField: "title",
  searchField: ["title", "description"],

  filter: function(item, search) {
    const s = search.toLowerCase();

    return (
      item.title.toLowerCase().includes(s) ||
      item.description.toLowerCase().includes(s)
    );
  }
});

В этом режиме searchField фактически перестаёт играть роль основного механизма и становится вспомогательной структурой для UI-подсказок, тогда как фильтрация полностью контролируется разработчиком.


Комбинирование нескольких стратегий поиска

На практике часто применяется гибридная модель:

  • searchField отвечает за индексирование
  • score управляет ранжированием
  • filter определяет допустимость результата
new TomSelect("#select", {
  valueField: "id",
  labelField: "title",
  searchField: ["title", "description", "tags"],

  score: function(search) {
    return item => {
      let score = 0;

      if (item.title.includes(search)) score += 20;
      if (item.description.includes(search)) score += 10;
      if (item.tags.includes(search)) score += 5;

      return score;
    };
  },

  filter: function(item, search) {
    return item.title.length > 0;
  }
});

Такой подход позволяет разделить ответственность между этапами обработки данных: фильтрация исключает лишнее, score сортирует, searchField формирует базовый индекс.


Поиск с весами полей через нормализацию score

Более формализованный подход заключается в использовании коэффициентов веса:

const weights = {
  title: 5,
  description: 3,
  tags: 1
};

new TomSelect("#select", {
  searchField: ["title", "description", "tags"],

  score: function(search) {
    const s = search.toLowerCase();

    return item => {
      let score = 0;

      if (item.title.toLowerCase().includes(s)) {
        score += weights.title;
      }

      if (item.description.toLowerCase().includes(s)) {
        score += weights.description;
      }

      if (item.tags.toLowerCase().includes(s)) {
        score += weights.tags;
      }

      return score;
    };
  }
});

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


Производительность при большом количестве полей

Рост числа searchField напрямую влияет на стоимость построения индекса и время обработки запроса. Особенно это заметно при тысячах опций.

Ключевые факторы влияния:

  • количество полей в каждом объекте
  • длина строковых значений
  • глубина нормализации данных
  • наличие кастомного score

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

  • предварительное объединение полей в один индексируемый текст
  • ограничение числа searchField до 2–4 ключевых атрибутов
  • отказ от тяжёлых вычислений внутри score
  • кэширование нормализованных строк

Поиск по тегам и массивам значений

Когда поле представляет собой массив, его необходимо преобразовать в строку или использовать специализированную логику сравнения:

{
  id: 1,
  title: "Vue",
  tags: ["frontend", "framework", "reactive"]
}

Решение:

searchField: ["title", "tags_blob"],

options: data.map(item => ({
  ...item,
  tags_blob: item.tags.join(" ")
}))

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


Контекстное влияние полей на релевантность

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

При расширении числа полей важно учитывать:

  • увеличение шумового сигнала при слабоструктурированных данных
  • необходимость балансировки весов
  • риск переоценки второстепенных атрибутов

Рациональная структура данных обычно предполагает наличие одного главного поля (например, title) и нескольких вспомогательных (description, tags), где влияние вторых строго ограничено.