Релевантность в списках автодополнения определяет порядок, в котором пользователю предлагаются варианты. В контексте Awesomplete этот механизм строится вокруг разделения двух этапов обработки: фильтрации и сортировки. Фильтрация отвечает за попадание элемента в набор кандидатов, сортировка — за их упорядочивание внутри этого набора.
Awesomplete использует внутренний массив элементов, каждый из которых проходит через фильтр и затем через функцию сортировки. По умолчанию применяется простая эвристика: элементы, которые лучше совпадают с началом строки ввода и имеют меньшую длину, поднимаются выше в списке.
Логика базовой сортировки строится вокруг сравнения двух значений:
Чем раньше найдено совпадение и чем короче строка, тем выше элемент в выдаче.
Ключевой точкой кастомизации является свойство sort,
которое можно переопределить. Оно принимает функцию сравнения,
аналогичную Array.prototype.sort.
Сигнатура:
awesomplete.sort = function(a, b, input) {
return 0;
};
Где:
a — первый элемент спискаb — второй элемент спискаinput — текущее значение ввода пользователяЭта функция должна возвращать:
a релевантнее
bb релевантнее
aОдин из наиболее примитивных способов ранжирования — сортировка по длине строки:
awesomplete.sort = function(a, b) {
return a.length - b.length;
};
Этот подход полезен, когда важна краткость вариантов, но он игнорирует семантическую близость к вводу.
Более распространённый сценарий — повышение приоритета элементов, начинающихся с введённого текста.
awesomplete.sort = function(a, b, input) {
let ai = a.toLowerCase().indexOf(input.toLowerCase());
let bi = b.toLowerCase().indexOf(input.toLowerCase());
if (ai !== bi) {
return ai - bi;
}
return a.length - b.length;
};
Здесь сначала сравнивается позиция вхождения, затем длина строки как вторичный критерий.
Релевантность сильно зависит от приведения строк к единому виду. Без нормализации регистр может искажать результат сортировки.
const normalize = str => str.toLowerCase();
awesomplete.sort = function(a, b, input) {
input = normalize(input);
const aNorm = normalize(a);
const bNorm = normalize(b);
const ai = aNorm.indexOf(input);
const bi = bNorm.indexOf(input);
if (ai !== bi) {
return ai - bi;
}
return aNorm.length - bNorm.length;
};
Такой подход делает сортировку предсказуемой независимо от регистра входных данных.
Более сложные модели используют взвешивание факторов. Например:
Пример реализации через скоринг:
function score(item, input) {
const value = item.toLowerCase();
input = input.toLowerCase();
if (value.startsWith(input)) {
return 0;
}
const index = value.indexOf(input);
if (index > 0) {
return 1 + index / 100;
}
return 100;
}
awesomplete.sort = function(a, b, input) {
return score(a, input) - score(b, input);
};
Здесь сортировка превращается в сравнение числового рейтинга релевантности.
Awesomplete поддерживает использование объектов вместо строк. В этом случае сортировка должна учитывать отображаемое значение.
awesomplete.sort = function(a, b, input) {
const aValue = a.label || a.value || a;
const bValue = b.label || b.value || b;
return aValue.length - bValue.length;
};
При более сложных сценариях можно использовать отдельные поля:
label — отображаемый текстvalue — внутреннее значениеЭто позволяет отделять пользовательский интерфейс от логики ранжирования.
Фильтр в Awesomplete определяет, попадёт ли элемент в список, но не отвечает за его позицию. Однако комбинация фильтра и сортировки формирует итоговую релевантность.
Типичный фильтр:
awesomplete.filter = function(text, input) {
return text.toLowerCase().includes(input.toLowerCase());
};
Если фильтр слишком широкий (например, включает все вхождения), сортировка становится основным инструментом формирования качества выдачи.
Для повышения точности часто используется комбинированный подход:
awesomplete.sort = function(a, b, input) {
const norm = s => s.toLowerCase();
input = norm(input);
const rank = str => {
str = norm(str);
if (str.startsWith(input)) return 0;
if (str.split(" ")[0].startsWith(input)) return 1;
const pos = str.indexOf(input);
return pos >= 0 ? 2 + pos : 1000;
};
return rank(a) - rank(b);
};
Такой подход особенно полезен при автодополнении больших справочников.
При одинаковых значениях релевантности важно сохранять стабильность порядка. Без этого список может «прыгать» при каждом вводе.
Один из способов стабилизации:
awesomplete.sort = function(a, b, input) {
const ra = score(a, input);
const rb = score(b, input);
if (ra === rb) {
return a.localeCompare(b);
}
return ra - rb;
};
Добавление лексикографического сравнения делает порядок воспроизводимым.
При больших списках основная нагрузка приходится на повторное вычисление скоринга. Чтобы снизить затраты, используется предрасчёт нормализованных значений или кэширование:
const cache = new Map();
function getNormalized(str) {
if (!cache.has(str)) {
cache.set(str, str.toLowerCase());
}
return cache.get(str);
}
Это особенно важно при динамическом вводе, где сортировка вызывается на каждом нажатии клавиши.
В реальных приложениях данные часто имеют дополнительную метаинформацию: популярность, частоту использования, категорию.
awesomplete.sort = function(a, b, input) {
const getValue = x => x.value || x;
const popularity = x => x.popularity || 0;
const score = x => {
const v = getValue(x).toLowerCase();
const i = input.toLowerCase();
const base = v.startsWith(i) ? 0 : v.indexOf(i) >= 0 ? 1 : 100;
return base - popularity(x) * 0.01;
};
return score(a) - score(b);
};
Здесь релевантность становится многокритериальной функцией, где пользовательский опыт учитывается через частоту использования элементов.
Сортировка в Awesomplete фактически определяет поведение интерфейса при вводе. Небольшие изменения в формуле ранжирования могут радикально менять восприятие системы:
Комбинация этих факторов формирует итоговую модель релевантности, которая работает как основа поведения автодополнения в реальных интерфейсах