Hooks и точки расширения

Архитектура расширяемости Awesomplete опирается на комбинацию конфигурационных колбэков и DOM-событий, формируя двухуровневую систему перехвата поведения: внутренние точки расширения (через опции конструктора) и внешние (через события и модификацию экземпляра). Такая модель позволяет изменять практически любой этап жизненного цикла автодополнения без форка библиотеки.

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

  1. Получение исходных данных
  2. Фильтрация
  3. Сортировка
  4. Построение DOM-элементов
  5. Отображение списка
  6. Выбор элемента
  7. Подстановка значения

Каждая стадия имеет собственные перехваты, которые можно заменить или дополнить.

Ключевая особенность заключается в том, что Awesomplete не скрывает свой pipeline — он параметризован через набор функций, передаваемых в конструктор.


Колбэки как основной механизм расширения

Наиболее важные точки расширения реализованы через свойства экземпляра:

  • filter
  • sort
  • item
  • replace
  • data

Эти функции образуют ядро кастомизации.

Фильтрация данных

Функция фильтрации определяет, какие элементы списка попадут в результат.

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

Фильтр вызывается для каждого элемента исходного массива. Важно, что Awesomplete не навязывает формат сравнения: можно реализовать:

  • нечувствительный к регистру поиск
  • поиск по подстроке
  • регулярные выражения
  • токенизированный поиск
  • fuzzy matching

Пример расширенной логики:

awesomplete.filter = function(text, input) {
    const normalize = s => s.toLowerCase().trim();
    return normalize(text).includes(normalize(input));
};

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


Сортировка результатов

После фильтрации элементы проходят стадию упорядочивания.

awesomplete.sort = function(a, b) {
    return a.length - b.length;
};

Сортировка может учитывать:

  • длину строки
  • позицию совпадения
  • частоту использования
  • внешние метрики (например, популярность)

Расширенный вариант — приоритет совпадения начала строки:

awesomplete.sort = function(a, b) {
    const input = this.input.value.toLowerCase();
    const ap = a.toLowerCase().indexOf(input) === 0 ? 0 : 1;
    const bp = b.toLowerCase().indexOf(input) === 0 ? 0 : 1;

    return ap - bp || a.length - b.length;
};

Здесь сортировка становится контекстной, зависящей от текущего ввода.


Формирование DOM-элементов

Одна из самых мощных точек расширения — функция item, отвечающая за создание элемента списка.

awesomplete.item = function(text, input) {
    const li = document.createElement("li");
    li.textContent = text;
    return li;
};

Через неё можно полностью изменить визуальное представление:

  • добавить подсветку совпадений
  • вставлять HTML-разметку
  • добавлять метаданные
  • внедрять иконки или бейджи

Пример подсветки:

awesomplete.item = function(text, input) {
    const li = document.createElement("li");

    const regex = new RegExp(input, "gi");
    li.innerHTML = text.replace(regex, match => `<mark>${match}</mark>`);

    return li;
};

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


Механизм подстановки значения

Функция replace определяет, как выбранный элемент вставляется в input.

awesomplete.replace = function(text) {
    this.input.value = text;
};

Это позволяет:

  • подставлять не только строку, но и структурированные данные
  • преобразовывать формат (например, имя → email)
  • вставлять скрытые идентификаторы

Пример с нормализацией:

awesomplete.replace = function(text) {
    this.input.value = text.split(" — ")[0];
};

Здесь отображаемое значение может содержать дополнительную информацию, но в input попадает только основная часть.


Преобразование данных через data

Опция data контролирует, как элементы списка интерпретируются внутри библиотеки.

awesomplete.data = function(item) {
    return {
        label: item.name,
        value: item.id
    };
};

Это особенно важно при работе с объектами:

const list = [
    { id: 1, name: "Almaty" },
    { id: 2, name: "Astana" }
];

Awesomplete по умолчанию ожидает строки, но через data можно внедрить полноценные структуры.


Событийная модель как внешний слой расширения

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

Основные события:

  • awesomplete-open
  • awesomplete-close
  • awesomplete-select
  • awesomplete-selectcomplete
  • awesomplete-highlight

Открытие списка

input.addEventListener("awesomplete-open", function() {
    console.log("Список открыт");
});

Это событие полезно для:

  • аналитики
  • динамической загрузки данных
  • синхронизации UI

Закрытие списка

input.addEventListener("awesomplete-close", function() {
    console.log("Список закрыт");
});

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


Выбор элемента

Событие awesomplete-select вызывается до финальной подстановки значения.

input.addEventListener("awesomplete-select", function(event) {
    console.log(event.text);
});

Здесь можно:

  • отменить выбор
  • изменить данные
  • записать метрики

Событие awesomplete-selectcomplete происходит после завершения вставки.


Подсветка элемента

input.addEventListener("awesomplete-highlight", function(event) {
    console.log(event.text);
});

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


Перехват и модификация через прототип

Хотя Awesomplete не поощряет изменение внутреннего API, возможно расширение через прототип:

Awesomplete.prototype.open = function() {
    console.log("Переопределено");
    Awesomplete.prototype.constructor.prototype.open.call(this);
};

Такой подход используется редко, но позволяет:

  • внедрять глобальное поведение
  • модифицировать базовую логику всех экземпляров
  • добавлять instrumentation

Однако он создаёт риск несовместимости при обновлениях.


Асинхронные источники данных как расширение pipeline

Одним из ключевых сценариев расширения является замена статического списка на динамический источник.

awesomplete.list = [];

И последующее обновление:

fetch("/api/suggestions?q=" + input.value)
    .then(r => r.json())
    .then(data => {
        awesomplete.list = data;
        awesomplete.evaluate();
    });

Здесь Awesomplete используется как UI-слой, а не как источник данных.


Кастомные стратегии фильтрации как архитектурный слой

В сложных приложениях фильтр превращается в отдельный модуль.

Пример гибридного поиска:

awesomplete.filter = function(text, input) {
    const score = fuzzyScore(text, input);
    return score > 0.5;
};

Где fuzzyScore может учитывать:

  • расстояние Левенштейна
  • совпадения n-грамм
  • частотную модель

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


Расширение визуального слоя

Функция item часто используется для внедрения сложного UI:

awesomplete.item = function(text) {
    const li = document.createElement("li");

    const wrapper = document.createElement("div");
    wrapper.className = "suggestion";

    const title = document.createElement("span");
    title.textContent = text;

    const icon = document.createElement("i");
    icon.className = "icon";

    wrapper.appendChild(icon);
    wrapper.appendChild(title);
    li.appendChild(wrapper);

    return li;
};

Это позволяет интегрировать:

  • аватары пользователей
  • иконки типов сущностей
  • дополнительные описания

Перехват управления вводом

Через комбинацию событий и replace можно реализовать нестандартные сценарии:

  • автозаполнение с форматированием
  • вставка шаблонов
  • multi-value input

Пример multi-value:

awesomplete.replace = function(text) {
    const values = this.input.value.split(",");
    values[values.length - 1] = text;
    this.input.value = values.join(",");
};

Композиция расширений

На практике расширения комбинируются:

  • фильтр реализует семантику поиска
  • сортировка управляет приоритетами
  • item отвечает за визуал
  • replace управляет структурой данных
  • события синхронизируют внешний мир

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