Поиск в библиотеке Slim Select предназначен для быстрого нахождения
элементов внутри выпадающего списка. Алгоритм поиска работает поверх
массива опций <option> и позволяет фильтровать данные
в реальном времени при вводе текста.
После инициализации компонента библиотека:
<select>Поиск активируется автоматически при включённом параметре
showSearch.
<select id="users">
<option>Александр</option>
<option>Андрей</option>
<option>Мария</option>
<option>Екатерина</option>
</select>
new SlimSelect({
select: '#users',
settings: {
showSearch: true
}
})
После ввода текста библиотека скрывает элементы, не удовлетворяющие условию поиска.
По умолчанию Slim Select использует простой алгоритм поиска по
подстроке. Проверка выполняется методом, аналогичным
includes().
Условно внутреннюю логику можно представить следующим образом:
function search(query, options) {
return options.filter(option => {
return option.text
.toLowerCase()
.includes(query.toLowerCase())
})
}
Основные особенности алгоритма:
Пример:
| Поисковый запрос | Результат |
|---|---|
ан |
Андрей, Александр |
ма |
Мария |
ка |
Екатерина |
Для корректного сравнения Slim Select приводит обе строки к нижнему регистру.
option.text.toLowerCase()
query.toLowerCase()
Это позволяет находить совпадения независимо от написания.
<select id="cities">
<option>Москва</option>
<option>Алматы</option>
<option>Berlin</option>
</select>
Поиск:
мос
МОС
Мос
во всех случаях вернёт:
Москва
Алгоритм не требует полного совпадения строки.
Если имеется список:
<select id="frameworks">
<option>React</option>
<option>Angular</option>
<option>Vue</option>
<option>Svelte</option>
</select>
Запрос:
ang
вернёт:
Angular
А запрос:
act
найдёт:
React
Такой подход значительно ускоряет работу с большими наборами данных.
Стандартный поиск работает по всей строке, однако алгоритм можно переопределить для проверки только начала текста.
Пример кастомного алгоритма:
new SlimSelect({
select: '#frameworks',
events: {
search: (search, currentData) => {
return currentData.filter(item => {
return item.text
.toLowerCase()
.startsWith(search.toLowerCase())
})
}
}
})
Теперь:
| Запрос | Результат |
|---|---|
re |
React |
ac |
Нет результатов |
ang |
Angular |
Slim Select предоставляет событие search, позволяющее
полностью заменить встроенный механизм.
Структура обработчика:
search: (searchValue, currentData) => {
return filteredData
}
Параметры:
| Параметр | Описание |
|---|---|
searchValue |
введённая строка |
currentData |
массив всех доступных элементов |
Каждый элемент массива содержит объект:
{
text: 'React',
value: 'react'
}
Иногда требуется искать не только по тексту, но и по дополнительным данным.
Пример структуры:
[
{
text: 'Иван',
value: '1',
email: 'ivan@test.com'
},
{
text: 'Мария',
value: '2',
email: 'maria@test.com'
}
]
Кастомный поиск:
new SlimSelect({
select: '#users',
events: {
search: (search, data) => {
return data.filter(item => {
const query = search.toLowerCase()
return (
item.text.toLowerCase().includes(query) ||
item.email.toLowerCase().includes(query)
)
})
}
}
})
Теперь поиск будет учитывать:
При работе с телефонными номерами, артикулами или кодами часто необходимо игнорировать пробелы.
Пример:
new SlimSelect({
select: '#phones',
events: {
search: (search, data) => {
const normalizedSearch =
search.replace(/\s/g, '')
return data.filter(item => {
const text = item.text
.replace(/\s/g, '')
.toLowerCase()
return text.includes(
normalizedSearch.toLowerCase()
)
})
}
}
})
Поиск:
777123
сможет найти:
777 123
777-123
777 123 456
Для международных приложений часто требуется удаление диакритики.
Пример:
José
François
Müller
По умолчанию поиск может работать некорректно для:
jose
francois
muller
Решение через нормализацию Unicode:
function normalize(str) {
return str
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
}
Использование:
new SlimSelect({
select: '#people',
events: {
search: (search, data) => {
return data.filter(item => {
return normalize(item.text)
.includes(normalize(search))
})
}
}
})
Для сложной фильтрации можно хранить скрытые ключевые слова.
[
{
text: 'JavaScript',
value: 'js',
keywords: ['frontend', 'browser', 'web']
}
]
Алгоритм:
new SlimSelect({
select: '#skills',
events: {
search: (search, data) => {
const query = search.toLowerCase()
return data.filter(item => {
const inText =
item.text.toLowerCase().includes(query)
const inKeywords =
item.keywords.some(keyword => {
return keyword
.toLowerCase()
.includes(query)
})
return inText || inKeywords
})
}
}
})
Поиск:
browser
вернёт:
JavaScript
Стандартная фильтрация не сортирует результаты по релевантности. Все совпадения выводятся в исходном порядке.
Для улучшения поиска можно реализовать собственную систему приоритетов.
Пример:
new SlimSelect({
select: '#languages',
events: {
search: (search, data) => {
const query = search.toLowerCase()
return data
.map(item => {
let score = 0
const text = item.text.toLowerCase()
if (text === query) {
score += 100
}
if (text.startsWith(query)) {
score += 50
}
if (text.includes(query)) {
score += 10
}
return {
...item,
score
}
})
.filter(item => item.score > 0)
.sort((a, b) => b.score - a.score)
}
}
})
При запросе:
rea
результаты будут отсортированы так:
Slim Select поддерживает асинхронную загрузку результатов.
Это особенно важно при:
Пример:
new SlimSelect({
select: '#users',
events: {
search: async (search) => {
const response = await fetch(
`/api/users?q=${search}`
)
const users = await response.json()
return users.map(user => ({
text: user.name,
value: user.id
}))
}
}
})
В таком режиме:
Без ограничения частоты запросов поиск может вызывать слишком много обращений к серверу.
Для оптимизации применяется debounce.
Пример:
function debounce(fn, delay) {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
Использование:
const searchUsers = debounce(async (search) => {
const response = await fetch(
`/api/users?q=${search}`
)
return await response.json()
}, 300)
Debounce:
При больших базах данных поиск обычно запускается только после ввода нескольких символов.
Пример:
search: async (search) => {
if (search.length < 3) {
return []
}
const response = await fetch(
`/api/search?q=${search}`
)
return await response.json()
}
Преимущества:
При повторяющихся запросах можно использовать локальный кэш.
Пример:
const cache = {}
new SlimSelect({
select: '#products',
events: {
search: async (search) => {
if (cache[search]) {
return cache[search]
}
const response = await fetch(
`/api/products?q=${search}`
)
const data = await response.json()
cache[search] = data
return data
}
}
})
Кэширование:
При наличии тысяч элементов возникают проблемы производительности.
Основные причины:
Типичная оптимизация:
const normalizedData = data.map(item => ({
...item,
normalized: item.text.toLowerCase()
}))
Далее поиск выполняется уже по подготовленным строкам:
normalizedData.filter(item => {
return item.normalized.includes(query)
})
Такой подход:
Для сложных сценариев можно применять RegExp.
Пример:
new SlimSelect({
select: '#files',
events: {
search: (search, data) => {
const regex = new RegExp(search, 'i')
return data.filter(item => {
return regex.test(item.text)
})
}
}
})
Примеры запросов:
| Запрос | Совпадение |
|---|---|
^test |
строки, начинающиеся с test |
\.js$ |
JavaScript-файлы |
[0-9] |
строки с цифрами |
Для улучшенного UX иногда используется fuzzy search.
Пример простого алгоритма:
function fuzzyMatch(text, search) {
let index = 0
for (const char of text.toLowerCase()) {
if (char === search[index]) {
index++
}
}
return index === search.length
}
Использование:
new SlimSelect({
select: '#frameworks',
events: {
search: (search, data) => {
return data.filter(item => {
return fuzzyMatch(item.text, search)
})
}
}
})
Запрос:
rct
сможет найти:
React
Если совпадений нет, Slim Select отображает специальное сообщение.
Настройка:
new SlimSelect({
select: '#countries',
settings: {
searchText: 'Ничего не найдено'
}
})
При отсутствии совпадений список будет содержать:
Ничего не найдено
Алгоритм поиска можно полностью отключить.
new SlimSelect({
select: '#simple',
settings: {
showSearch: false
}
})
В этом режиме:
Внутренняя схема работы обычно состоит из нескольких этапов:
keypress → input
toLowerCase()
trim()
normalize()
filter()
includes()
startsWith()
RegExp
sort()
score
ranking
DOM update
re-render
Каждый из этапов может быть переопределён через пользовательский алгоритм поиска.