Пользовательская сортировка

В Awesomplete сортировка элементов списка отделена от фильтрации, что позволяет гибко управлять порядком отображения подсказок после применения поискового запроса. Библиотека использует комбинацию двух механизмов: filter (отбор элементов) и sort (упорядочивание уже отобранных элементов).

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


Базовый контракт сортировки

Сортировка в Awesomplete реализуется как функция сравнения двух элементов:

sort: function(a, b, input) {
    return a.localeCompare(b);
}

Функция получает:

  • a — первый элемент списка
  • b — второй элемент списка
  • input — текущее значение поля ввода

Результат:

  • отрицательное число → a выше b
  • положительное число → b выше a
  • 0 → порядок сохраняется

Переопределение сортировки

Для кастомного поведения сортировка передаётся через опции конструктора:

new Awesomplete(input, {
    list: ["Apple", "Apricot", "Banana", "Blueberry"],

    sort: function(a, b, input) {
        return a.length - b.length;
    }
});

В этом примере приоритет получают более короткие строки, независимо от совпадения с вводом.


Сортировка с приоритетом префикса

Часто требуется поднимать элементы, которые начинаются с введённого текста:

function prefixPrioritySort(a, b, input) {
    const ia = a.toLowerCase().indexOf(input.toLowerCase());
    const ib = b.toLowerCase().indexOf(input.toLowerCase());

    const aIsPrefix = ia === 0;
    const bIsPrefix = ib === 0;

    if (aIsPrefix && !bIsPrefix) return -1;
    if (!aIsPrefix && bIsPrefix) return 1;

    return a.localeCompare(b);
}

Логика:

  • элементы с совпадением в начале строки всегда выше
  • остальные сортируются лексикографически

Ранжирование по степени релевантности

Более сложная стратегия включает вычисление «веса совпадения». Например:

function relevanceSort(a, b, input) {
    input = input.toLowerCase();

    function score(str) {
        str = str.toLowerCase();

        if (str === input) return 0;
        if (str.startsWith(input)) return 1;
        if (str.includes(input)) return 2;

        return 3;
    }

    const diff = score(a) - score(b);

    if (diff !== 0) return diff;

    return a.length - b.length;
}

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

  1. точное совпадение
  2. совпадение по префиксу
  3. вхождение внутри строки
  4. остальные случаи

Сортировка с учетом локали

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

function localeSort(a, b) {
    return a.localeCompare(b, 'ru', {
        sensitivity: 'base',
        ignorePunctuation: true
    });
}

Параметры:

  • sensitivity: 'base' — игнорирование регистра и диакритики
  • ignorePunctuation — игнорирование знаков пунктуации

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


Стабильная сортировка с сохранением исходного порядка

Иногда требуется сохранить порядок исходного массива при равных условиях:

function stableSortFactory(list) {
    const indexMap = new Map(list.map((v, i) => [v, i]));

    return function(a, b, input) {
        const diff = a.localeCompare(b);

        if (diff !== 0) return diff;

        return indexMap.get(a) - indexMap.get(b);
    };
}

Такая стратегия предотвращает «прыгающий» порядок элементов при одинаковом ранге.


Комбинированная сортировка с весами

Распространённый подход — использование нескольких факторов одновременно:

function weightedSort(a, b, input) {
    input = input.toLowerCase();

    function weight(str) {
        str = str.toLowerCase();

        let w = 0;

        if (str.startsWith(input)) w += 100;
        if (str.includes(input)) w += 50;
        if (str === input) w += 200;

        w += Math.max(0, 30 - str.length);

        return w;
    }

    return weight(b) - weight(a);
}

Здесь итоговый порядок определяется суммой факторов:

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

Интеграция с фильтрацией Awesomplete

Сортировка работает после фильтрации, поэтому её поведение зависит от выбранного фильтра:

Awesomplete.FILTER_CONTAINS = function(text, input) {
    return text.toLowerCase().includes(input.toLowerCase());
};

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


Влияние кастомной сортировки на UX автодополнения

Изменение сортировки напрямую влияет на восприятие интерфейса:

  • приоритет «логичных» вариантов ускоряет выбор
  • строгая лексикографическая сортировка полезна для справочников
  • весовая модель подходит для поисковых подсказок
  • префиксная сортировка усиливает предсказуемость ввода

Сортировка объектов вместо строк

Awesomplete поддерживает не только строки, но и объекты:

new Awesomplete(input, {
    list: [
        { label: "Apple", value: "apple" },
        { label: "Apricot", value: "apricot" },
        { label: "Banana", value: "banana" }
    ],

    sort: function(a, b, input) {
        return a.label.localeCompare(b.label);
    }
});

В этом случае сортировка должна работать по полю label, иначе сравнение будет некорректным.


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

Сортировка может динамически зависеть от длины введённого текста:

function adaptiveSort(a, b, input) {
    if (input.length < 2) {
        return a.localeCompare(b);
    }

    const ia = a.toLowerCase().indexOf(input.toLowerCase());
    const ib = b.toLowerCase().indexOf(input.toLowerCase());

    if (ia !== ib) return ia - ib;

    return a.length - b.length;
}

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


Приоритет точных совпадений с нормализацией

Для более устойчивого сравнения используется нормализация строк:

function normalize(str) {
    return str
        .toLowerCase()
        .normalize("NFD")
        .replace(/\p{Diacritic}/gu, "");
}

function exactPrioritySort(a, b, input) {
    const nInput = normalize(input);
    const na = normalize(a);
    const nb = normalize(b);

    if (na === nInput && nb !== nInput) return -1;
    if (nb === nInput && na !== nInput) return 1;

    return na.localeCompare(nb);
}

Нормализация устраняет различия в акцентах и регистрах, делая сортировку предсказуемой.


Композиция сортировок

Несколько стратегий можно объединять через композицию:

function composeSort(...fns) {
    return function(a, b, input) {
        for (const fn of fns) {
            const res = fn(a, b, input);
            if (res !== 0) return res;
        }
        return 0;
    };
}

const sort = composeSort(
    exactPrioritySort,
    prefixPrioritySort,
    localeSort
);

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