Встроенная система фильтрации в Awesomplete опирается на простую идею: из общего списка вариантов показываются только те элементы, которые соответствуют введённому тексту. Поведение этого соответствия полностью контролируется функцией фильтрации, которая определяет, считается ли элемент подходящим для отображения в выпадающем списке.
Ключевой момент заключается в том, что библиотека не ограничивает разработчика жёсткой логикой поиска. Вместо этого используется заменяемая функция, позволяющая реализовать как строгий поиск по началу строки, так и более гибкий поиск по вхождению подстроки.
Каждый элемент списка проходит проверку через фильтрующую функцию:
Результатом является булево значение:
true — элемент отображаетсяfalse — элемент исключаетсяУпрощённо механизм можно представить так:
function filter(text, input) {
return match(text, input);
}
Где match — логика сравнения, которую можно полностью
переопределить.
В стандартных сценариях часто используется подход «совпадение с началом строки». Он эффективен для автодополнения, где важна предсказуемость результата.
Пример поведения:
["Apple", "Apricot", "Banana", "Grape"]
При вводе:
ap
Результат:
Но при вводе:
pp
Результат пустой, хотя логически пользователь мог искать часть слова.
Ограничение подхода заключается в том, что он плохо работает для поиска внутри строки, особенно при работе с составными словами, именами и описаниями.
Подстрочный поиск расширяет возможности автодополнения, позволяя находить совпадения не только в начале строки, но и внутри неё.
Классический вариант реализации:
new Awesomplete(input, {
list: ["Apple", "Pineapple", "Grape", "Apricot"],
filter: function(text, input) {
return text.toLowerCase().includes(input.trim().toLowerCase());
}
});
Такой подход делает поиск более «интуитивным» для пользователя.
Библиотека предоставляет готовые стратегии фильтрации, которые можно использовать без написания собственной логики.
Типовые варианты:
Пример переключения стратегии:
new Awesomplete(input, {
list: ["Apple", "Pineapple", "Banana"],
filter: Awesomplete.FILTER_CONTAINS
});
или:
new Awesomplete(input, {
list: ["Apple", "Pineapple", "Banana"],
filter: Awesomplete.FILTER_STARTSWITH
});
Разница между подходами заключается в том, насколько глубоко внутри строки допускается совпадение.
Фильтр в Awesomplete можно полностью заменить. Это открывает возможность реализовать:
function normalize(str) {
return str
.toLowerCase()
.replace(/\s+/g, " ")
.trim();
}
new Awesomplete(input, {
list: ["New York", "Newark", "Yorkshire", "Little York"],
filter: function(text, input) {
const t = normalize(text);
const i = normalize(input);
return t.includes(i);
}
});
Здесь добавляется слой нормализации, который уменьшает влияние пробелов и регистра.
Регистронезависимый поиск является базовым требованием для
большинства интерфейсов автодополнения. Однако простого
toLowerCase() часто недостаточно.
function normalize(text) {
return text
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "");
}
Такая обработка позволяет:
Пример:
При использовании includes возникает проблема: все
совпадения равнозначны. Однако пользовательский интерфейс часто требует
сортировки по релевантности.
Можно расширить поведение фильтрации:
new Awesomplete(input, {
list: ["JavaScript", "TypeScript", "CoffeeScript", "Java"],
filter: function(text, input) {
return text.toLowerCase().includes(input.toLowerCase());
},
sort: function(a, b, input) {
const i = input.toLowerCase();
const aIndex = a.toLowerCase().indexOf(i);
const bIndex = b.toLowerCase().indexOf(i);
return aIndex - bIndex;
}
});
Здесь элементы, где подстрока встречается раньше, получают более высокий приоритет.
Подстрочный поиск можно усложнить до анализа отдельных токенов. Это полезно для длинных описаний и тегов.
function tokenMatch(text, input) {
const tokens = text.toLowerCase().split(" ");
const query = input.toLowerCase();
return tokens.some(t => t.includes(query));
}
Такой подход позволяет находить совпадения внутри отдельных слов, а не только всей строки.
В реальных интерфейсах часто используется гибридный подход:
Пример:
function smartFilter(text, input) {
const t = text.toLowerCase();
const i = input.toLowerCase();
if (t === i) return true;
if (t.startsWith(i)) return true;
return t.includes(i);
}
Такая логика обеспечивает баланс между точностью и гибкостью.
Особое значение имеет обработка пустой строки. В зависимости от реализации можно:
Пример:
filter: function(text, input) {
if (!input) return true;
return text.toLowerCase().includes(input.toLowerCase());
}
Такой вариант позволяет пользователю видеть подсказки до начала ввода.
При больших списках (тысячи элементов) фильтрация по
includes может стать узким местом.
Оптимизационные подходы:
Пример кэширования:
const prepared = list.map(item => ({
original: item,
normalized: item.toLowerCase()
}));
filter: function(text, input) {
const i = input.toLowerCase();
return prepared.filter(x => x.normalized.includes(i));
}
Подстрочная фильтрация особенно эффективна в случаях:
Она снижает когнитивную нагрузку, позволяя пользователю вводить фрагменты, а не точные начала слов.
Несмотря на гибкость, у подхода есть слабые стороны:
Эти факторы компенсируются комбинированной фильтрацией и сортировкой, но требуют внимания при проектировании интерфейса.