Регистронезависимая фильтрация

Регистронезависимая фильтрация в Awesomplete строится вокруг механизма функции filter, которая определяет, включается ли элемент списка в текущие результаты автодополнения. Базовая идея заключается в приведении сравниваемых строк к единому виду перед проверкой вхождения подстроки, что позволяет игнорировать различия в регистре символов.

Внутри Awesomplete каждый ввод пользователя проходит через функцию фильтрации, которая вызывается для каждого элемента списка:

filter: function (text, input) {
    return text.indexOf(input) !== -1;
}

Такой вариант предполагает чувствительность к регистру, поскольку сравнение выполняется напрямую. При вводе "app" и наличии элемента "Apple" совпадение не будет найдено.

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


Базовая регистронезависимая реализация

Наиболее распространённый подход — приведение строк к нижнему регистру:

new Awesomplete(input, {
    list: ["Apple", "apricot", "banana", "Avocado"],
    filter: function (text, input) {
        return text.toLowerCase().indexOf(input.toLowerCase()) !== -1;
    }
});

Здесь обе строки преобразуются через toLowerCase(), что устраняет влияние регистра символов на результат сравнения.

Аналогичный вариант через toUpperCase() даёт идентичный эффект:

filter: function (text, input) {
    return text.toUpperCase().indexOf(input.toUpperCase()) !== -1;
}

Оба варианта эквивалентны, выбор зависит только от стиля кодовой базы.


Использование встроенных констант Awesomplete

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

Awesomplete.FILTER_CONTAINS
Awesomplete.FILTER_STARTSWITH
Awesomplete.FILTER_CUSTOM

Внутренняя реализация FILTER_CONTAINS уже учитывает нормализацию регистра, но при необходимости расширения поведения создаётся собственная функция поверх неё.

Пример модификации стандартного поведения:

filter: function (text, input) {
    return Awesomplete.FILTER_CONTAINS(
        text.toLowerCase(),
        input.toLowerCase()
    );
}

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

В реальных приложениях регистронезависимость часто недостаточна, поскольку строки могут содержать диакритические знаки и различные Unicode-представления символов.

Типичный шаг нормализации включает:

  1. Приведение регистра
  2. Unicode normalization
  3. Удаление диакритики
function normalize(str) {
    return str
        .normalize("NFD")
        .replace(/[\u0300-\u036f]/g, "")
        .toLowerCase();
}

Применение в фильтре:

new Awesomplete(input, {
    list: ["Éclair", "eclair", "banana", "Crème"],
    filter: function (text, input) {
        return normalize(text).indexOf(normalize(input)) !== -1;
    }
});

Такой подход устраняет различия между "Éclair" и "eclair", а также снижает влияние различий в кодировке символов.


Работа с объектами списка

Awesomplete поддерживает не только строки, но и объекты. В этом случае фильтрация должна учитывать поле значения:

new Awesomplete(input, {
    list: [
        { label: "Apple", value: "apple" },
        { label: "Apricot", value: "apricot" },
        { label: "Avocado", value: "avocado" }
    ],
    filter: function (item, input) {
        return item.value.toLowerCase().indexOf(input.toLowerCase()) !== -1;
    }
});

Регистронезависимость применяется уже к конкретному полю, а не ко всему объекту.


Использование startsWith-фильтра без учёта регистра

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

filter: function (text, input) {
    return text.toLowerCase().startsWith(input.toLowerCase());
}

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


Оптимизация фильтрации

При больших списках повторные вызовы toLowerCase() могут создавать лишнюю нагрузку. Оптимизация достигается предварительной нормализацией списка:

const normalizedList = ["Apple", "Apricot", "Avocado"].map(item => ({
    original: item,
    normalized: item.toLowerCase()
}));

new Awesomplete(input, {
    list: normalizedList,
    filter: function (item, input) {
        return item.normalized.indexOf(input.toLowerCase()) !== -1;
    },
    item: function (item) {
        return item.original;
    }
});

Такой подход переносит вычисления на этап подготовки данных и снижает стоимость фильтрации при вводе.


Учёт локалей при регистронезависимости

В некоторых случаях стандартные методы toLowerCase() и toUpperCase() дают неоднозначные результаты для разных языков. Более корректное сравнение достигается через Intl.Collator:

const collator = new Intl.Collator("ru", {
    sensitivity: "base"
});

new Awesomplete(input, {
    list: ["Ёлка", "Ель", "яблоко"],
    filter: function (text, input) {
        return collator.compare(text, input) === 0 ||
               text.toLowerCase().indexOf(input.toLowerCase()) !== -1;
    }
});

Параметр sensitivity: "base" обеспечивает игнорирование регистра и диакритики на уровне локали.


Комбинирование регистронезависимости и других условий

Фильтрация часто комбинируется с дополнительными правилами, например ограничением по длине или приоритетом совпадений:

filter: function (text, input) {
    const t = text.toLowerCase();
    const i = input.toLowerCase();

    if (i.length < 2) return false;

    return t.indexOf(i) !== -1;
}

В этом случае регистронезависимость является лишь частью более сложной логики отбора.


Поведение при пустом вводе

Регистронезависимая фильтрация сохраняет смысл только при наличии входной строки. При пустом вводе часто применяется отдельная логика:

filter: function (text, input) {
    if (!input) return true;
    return text.toLowerCase().indexOf(input.toLowerCase()) !== -1;
}

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


Интеграция с кастомной сортировкой

Фильтрация и сортировка работают совместно. После применения регистронезависимого фильтра результаты часто дополнительно упорядочиваются:

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

    return a.toLowerCase().indexOf(input) - b.toLowerCase().indexOf(input);
}

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