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

По умолчанию библиотека Choices.js выполняет поиск по тексту отображаемого элемента (label). Такой механизм подходит для простых списков, однако в реальных интерфейсах часто требуется фильтрация сразу по нескольким свойствам:

  • названию;
  • коду;
  • категории;
  • описанию;
  • идентификатору;
  • тегам;
  • дополнительным метаданным.

Особенно важен многопольный поиск при работе:

  • с большими справочниками;
  • административными панелями;
  • CRM-системами;
  • каталогами товаров;
  • списками пользователей;
  • сложными выпадающими меню.

Базовый механизм поиска

Внутри Choices.js поиск работает через параметр searchFields.

Пример стандартной настройки:

const choices = new Choices('#users', {
  searchEnabled: true,
  searchFields: ['label', 'value']
});

Массив searchFields определяет, по каким свойствам объекта выполняется поиск.


Структура объекта элемента

Каждый элемент списка может содержать множество полей:

const users = [
  {
    value: 'u1',
    label: 'Александр Петров',
    email: 'alex@example.com',
    department: 'Разработка',
    role: 'Frontend'
  },
  {
    value: 'u2',
    label: 'Мария Иванова',
    email: 'maria@example.com',
    department: 'Маркетинг',
    role: 'SEO'
  }
];

Поиск одновременно по label и value

Самый распространённый вариант:

const choices = new Choices('#users', {
  searchEnabled: true,
  searchFields: ['label', 'value']
});

Теперь строка поиска будет проверять:

  • отображаемый текст;
  • внутреннее значение элемента.

Добавление пользовательских полей

Choices.js поддерживает поиск по произвольным свойствам объекта.

Подготовка данных

const users = [
  {
    value: '1',
    label: 'Иван Сидоров',
    customProperties: {
      email: 'ivan@example.com',
      city: 'Москва',
      department: 'Backend'
    }
  },
  {
    value: '2',
    label: 'Анна Козлова',
    customProperties: {
      email: 'anna@example.com',
      city: 'Санкт-Петербург',
      department: 'UI/UX'
    }
  }
];

Поиск по customProperties

const choices = new Choices('#users', {
  choices: users,
  searchEnabled: true,
  searchFields: [
    'label',
    'value',
    'customProperties.email',
    'customProperties.city',
    'customProperties.department'
  ]
});

Теперь поиск работает по:

  • имени;
  • идентификатору;
  • email;
  • городу;
  • отделу.

Полный пример многопольного поиска

HTML

<select id="employees"></select>

JavaScript

const employees = [
  {
    value: 'emp-001',
    label: 'Андрей Волков',
    customProperties: {
      email: 'volkov@example.com',
      department: 'Backend',
      position: 'Senior Developer',
      city: 'Алматы'
    }
  },
  {
    value: 'emp-002',
    label: 'Ольга Смирнова',
    customProperties: {
      email: 'smirnova@example.com',
      department: 'Design',
      position: 'UI Designer',
      city: 'Астана'
    }
  },
  {
    value: 'emp-003',
    label: 'Максим Орлов',
    customProperties: {
      email: 'orlov@example.com',
      department: 'DevOps',
      position: 'System Engineer',
      city: 'Караганда'
    }
  }
];

const choices = new Choices('#employees', {
  choices: employees,
  searchEnabled: true,
  searchFields: [
    'label',
    'value',
    'customProperties.email',
    'customProperties.department',
    'customProperties.position',
    'customProperties.city'
  ]
});

Что происходит при вводе

Если пользователь вводит:

backend

будут найдены элементы, у которых:

customProperties.department === 'Backend'

Если вводится:

караганда

найдётся запись:

city: 'Караганда'

Если вводится:

smirnova@example.com

поиск выполнится по email.


Поиск по скрытым данным

Часто требуется искать не только по отображаемому тексту, но и по служебной информации.

Пример:

{
  value: 'prd-001',
  label: 'Ноутбук Lenovo',
  customProperties: {
    sku: 'LEN-15-8842',
    barcode: '220000113344',
    vendor: 'Lenovo'
  }
}

Настройка:

searchFields: [
  'label',
  'customProperties.sku',
  'customProperties.barcode',
  'customProperties.vendor'
]

Теперь пользователь может искать:

  • по SKU;
  • по штрихкоду;
  • по производителю;
  • по названию.

Поиск по тегам

Исходные данные

const products = [
  {
    value: '1',
    label: 'iPhone 15',
    customProperties: {
      tags: 'apple smartphone ios'
    }
  },
  {
    value: '2',
    label: 'Galaxy S24',
    customProperties: {
      tags: 'samsung android smartphone'
    }
  }
];

Настройка поиска

const choices = new Choices('#products', {
  choices: products,
  searchEnabled: true,
  searchFields: [
    'label',
    'customProperties.tags'
  ]
});

Теперь поиск по слову:

android

вернёт Samsung.


Использование массива тегов

Choices.js лучше работает со строками, поэтому массив рекомендуется преобразовывать заранее.

Нежелательный вариант

tags: ['apple', 'smartphone', 'ios']

Предпочтительный вариант

tags: 'apple smartphone ios'

Нормализация данных перед поиском

Для повышения качества поиска данные часто подготавливаются заранее.

Пример

const products = apiData.map(item => ({
  value: item.id,
  label: item.name,
  customProperties: {
    searchText: `
      ${item.name}
      ${item.category}
      ${item.vendor}
      ${item.tags.join(' ')}
      ${item.article}
    `.toLowerCase()
  }
}));

Поиск по единому агрегированному полю

Иногда вместо множества searchFields удобнее создать одно поле:

searchFields: ['customProperties.searchText']

Это особенно эффективно:

  • при больших объёмах данных;
  • при сложной фильтрации;
  • при динамической загрузке;
  • при интеграции с API.

Пример агрегированного поиска

const users = [
  {
    value: '1',
    label: 'Алексей',
    customProperties: {
      searchText: 'алексей frontend react typescript москва'
    }
  },
  {
    value: '2',
    label: 'Елена',
    customProperties: {
      searchText: 'елена design figma ui ux минск'
    }
  }
];

const choices = new Choices('#users', {
  choices: users,
  searchEnabled: true,
  searchFields: ['customProperties.searchText']
});

Производительность многопольного поиска

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

Основные причины:

  • большое количество searchFields;
  • длинные строки;
  • частые обновления DOM;
  • сложные пользовательские шаблоны.

Оптимизация поиска

1. Минимизация количества searchFields

Плохо:

searchFields: [
  'label',
  'value',
  'customProperties.email',
  'customProperties.city',
  'customProperties.phone',
  'customProperties.position',
  'customProperties.department',
  'customProperties.description'
]

Лучше:

searchFields: ['customProperties.searchText']

2. Предварительная нормализация

Хорошая практика:

searchText: text.toLowerCase()

3. Ограничение результатов

const choices = new Choices('#users', {
  searchResultLimit: 20
});

4. Использование AJAX-поиска

Для очень больших наборов данных лучше выполнять поиск на сервере.


Комбинация с асинхронной загрузкой

Пример

const choices = new Choices('#users', {
  searchEnabled: true
});

document.querySelector('#users')
  .addEventListener('search', async (event) => {

    const query = event.detail.value;

    const response = await fetch(`/api/users?q=${query}`);

    const data = await response.json();

    choices.clearChoices();

    choices.setChoices(
      data,
      'value',
      'label',
      true
    );
  });

Локализация поиска

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

Пример

searchText: `
  ${item.name}
  ${item.city}
`.toLowerCase()

Поиск без учёта регистра

Choices.js автоматически выполняет case-insensitive поиск, однако единая нормализация данных уменьшает вероятность ошибок.


Поиск по числовым данным

Числовые значения рекомендуется хранить как строки.

Нежелательно

id: 10025

Лучше

id: '10025'

Поиск по артикулу

{
  value: '1',
  label: 'Монитор ASUS',
  customProperties: {
    article: 'AS-9981-KZ'
  }
}

Настройка:

searchFields: [
  'label',
  'customProperties.article'
]

Использование fuseOptions

Choices.js использует Fuse.js для нечёткого поиска.

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

const choices = new Choices('#products', {
  searchEnabled: true,
  searchFields: [
    'label',
    'customProperties.searchText'
  ],
  fuseOptions: {
    threshold: 0.3
  }
});

Влияние threshold

Низкое значение

threshold: 0.1
  • поиск более строгий;
  • меньше совпадений;
  • выше точность.

Высокое значение

threshold: 0.6
  • поиск более гибкий;
  • допускаются опечатки;
  • больше результатов.

Поиск с поддержкой опечаток

const choices = new Choices('#cities', {
  searchEnabled: true,
  searchFields: [
    'label',
    'customProperties.searchText'
  ],
  fuseOptions: {
    threshold: 0.4,
    distance: 100
  }
});

Использование includeScore

fuseOptions: {
  includeScore: true
}

Позволяет получать оценку релевантности совпадения.


Ограничение глубины поиска

При очень сложных объектах рекомендуется избегать глубоко вложенных структур:

Плохо:

customProperties.meta.department.name

Лучше:

customProperties.department

Типичные ошибки

Ошибка: поле отсутствует

searchFields: ['customProperties.phone']

Если phone отсутствует у части элементов, возможны проблемы с качеством поиска.


Ошибка: поиск по массиву

tags: ['frontend', 'react']

Предпочтительнее:

tags: 'frontend react'

Ошибка: слишком много полей

searchFields: [
  'a',
  'b',
  'c',
  'd',
  'e',
  'f',
  'g',
  'h'
]

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


Архитектура поискового поля

Наиболее стабильная схема:

customProperties: {
  searchText: 'единая поисковая строка'
}

Преимущества:

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

Интеграция с серверным API

Сервер может сразу возвращать готовое поисковое поле.

Пример ответа API

[
  {
    "value": "1",
    "label": "Александр",
    "customProperties": {
      "searchText": "александр backend nodejs postgres алматы"
    }
  }
]

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

Распространённая схема:

  1. локальный поиск по уже загруженным данным;
  2. серверный поиск при отсутствии результатов;
  3. обновление списка через setChoices().

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

Пример

customProperties: {
  searchText: `
    notebook ноутбук laptop
    computer компьютер pc
  `
}

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

  • на русском;
  • на английском;
  • по синонимам.

Индексация сокращений

Пример

customProperties: {
  searchText: 'frontend frontend developer fe'
}

Теперь поиск:

fe

тоже вернёт результат.


Использование searchChoices

Полное отключение поиска:

searchChoices: false

При многопольной фильтрации параметр должен быть включён:

searchChoices: true

Поиск внутри select multiple

Многопольный поиск особенно полезен для:

<select multiple></select>

Поскольку пользователь может искать элементы по:

  • имени;
  • категории;
  • тегам;
  • описанию;
  • коду;
  • идентификатору.

Практическая схема для production

const choices = new Choices('#items', {
  searchEnabled: true,

  searchFields: [
    'customProperties.searchText'
  ],

  fuseOptions: {
    threshold: 0.3
  },

  searchResultLimit: 30
});

Формирование данных:

customProperties: {
  searchText: `
    ${name}
    ${category}
    ${vendor}
    ${tags}
    ${article}
  `.toLowerCase()
}

Такой подход обеспечивает:

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