Создание плагинов

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

Ключевая особенность архитектуры заключается в том, что Awesomplete не навязывает строгую систему плагинов. Вместо этого предоставляется набор точек расширения:

  • переопределяемые методы экземпляра
  • события жизненного цикла
  • доступ к DOM-структурам
  • возможность подмены источников данных

Такой дизайн делает плагины легковесными и независимыми от версии ядра при условии сохранения базового API.


Подходы к созданию плагинов

Модификация прототипа

Один из самых прямолинейных способов расширения — добавление или переопределение методов в Awesomplete.prototype.

Awesomplete.prototype.highlightMatch = function (text, input) {
    const regex = new RegExp(input, "gi");
    return text.replace(regex, match => `<strong>${match}</strong>`);
};

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

Типичный сценарий применения:

  • единообразное поведение во всём приложении
  • глобальные улучшения UI
  • расширение базового алгоритма поиска

Обёртка экземпляра

Более безопасный способ — создание обёртки над экземпляром Awesomplete. В этом случае оригинальная библиотека остаётся неизменной.

function AwesompletePlugin(input, options = {}) {
    const aw = new Awesomplete(input, options);

    const originalSelect = aw.select;

    aw.select = function (item) {
        console.log("Выбран элемент:", item);
        return originalSelect.call(this, item);
    };

    return aw;
}

Такой подход позволяет:

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

Обёртка особенно полезна при создании изолированных модулей, работающих в разных частях интерфейса.


Использование событий жизненного цикла

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

input.addEventListener("awesomplete-selectcomplete", function (event) {
    const selected = event.text.value;
    console.log("Завершён выбор:", selected);
});

Событийный подход применяется для:

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

Плагины, основанные на событиях, не вмешиваются в ядро и считаются наиболее совместимыми с будущими версиями библиотеки.


Создание фильтрационного плагина

Фильтрация данных является одной из ключевых точек расширения Awesomplete. По умолчанию используется простое сравнение строк, однако в реальных сценариях часто требуется более сложная логика: нечёткий поиск, работа с транслитерацией, приоритеты совпадений.

Переопределение метода filter

Awesomplete.prototype.filter = function (text, input) {
    const normalize = str =>
        str.toLowerCase().replace(/\s+/g, "");

    return normalize(text).includes(normalize(input));
};

Такой плагин изменяет поведение поиска на уровне ядра. Более сложные реализации могут использовать:

  • Levenshtein distance
  • token-based matching
  • prefix scoring
  • комбинированные алгоритмы

Пример расширенного фильтра с весами

Awesomplete.prototype.score = function (text, input) {
    if (text.startsWith(input)) return 100;
    if (text.includes(input)) return 50;
    return 0;
};

Awesomplete.prototype.filter = function (text, input) {
    return this.score(text, input) > 0;
};

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


Плагины для асинхронных источников

Одним из наиболее распространённых расширений является подключение удалённых API. Awesomplete изначально ориентирован на статические массивы, поэтому асинхронность добавляется через переопределение источника данных.

Базовый асинхронный плагин

function AsyncSourcePlugin(input, fetcher) {
    const aw = new Awesomplete(input);

    aw.list = [];

    input.addEventListener("input", function () {
        const value = input.value;

        fetcher(value).then(results => {
            aw.list = results;
            Awesomplete.prototype.evaluate.call(aw);
        });
    });

    return aw;
}

Здесь ключевым моментом является ручной вызов evaluate, который инициирует обновление списка после получения данных.

Пример использования fetch API

const plugin = AsyncSourcePlugin(input, function (query) {
    return fetch(`/api/search?q=${query}`)
        .then(res => res.json());
});

Такой подход позволяет интегрировать автодополнение с серверными системами поиска, сохраняя при этом лёгкость клиентской логики.


Плагин кастомного рендера

Отдельным направлением расширения является изменение отображения элементов списка. Awesomplete использует метод рендеринга, который можно переопределить.

Переопределение renderItem

Awesomplete.prototype.renderItem = function (text, input) {
    const li = document.createElement("li");

    li.innerHTML = `
        <span class="item-text">${text}</span>
        <span class="item-icon">→</span>
    `;

    li.setAttribute("role", "option");

    return li;
};

Такой плагин позволяет:

  • добавлять иконки
  • внедрять сложную HTML-разметку
  • использовать шаблонизацию
  • изменять структуру элементов списка

Расширение с данными-объектами

Awesomplete.prototype.renderItem = function (item, input) {
    const li = document.createElement("li");

    li.innerHTML = `
        <div class="title">${item.title}</div>
        <div class="subtitle">${item.description}</div>
    `;

    return li;
};

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


Плагины поведения клавиатуры

Работа с клавиатурой является важной частью UX автодополнения. Поведение можно расширять через переопределение обработчиков событий.

Awesomplete.prototype.handleKey = function (event) {
    if (event.key === "Tab") {
        this.select(this.suggestions[0]);
        event.preventDefault();
        return;
    }

    Awesomplete.prototype._super_handleKey.call(this, event);
};

Для корректной работы часто сохраняется ссылка на оригинальный метод:

const original = Awesomplete.prototype.handleKey;

Awesomplete.prototype.handleKey = function (event) {
    if (event.key === "Escape") {
        console.log("Сброс списка");
    }

    return original.call(this, event);
};

Такой подход обеспечивает расширение без потери базового функционала.


Композиция плагинов

При увеличении количества расширений возникает необходимость их комбинирования. Основная проблема заключается в конфликте переопределений методов.

Паттерн цепочки вызовов

function composePlugins(aw, plugins) {
    plugins.forEach(plugin => plugin(aw));
    return aw;
}

Каждый плагин получает доступ к экземпляру и модифицирует его изолированно:

composePlugins(aw, [
    enableHighlighting,
    enableAsyncSource,
    enableCustomRender
]);

Изоляция через namespace

Для предотвращения конфликтов методы можно группировать:

aw.plugins = aw.plugins || {};

aw.plugins.highlight = function () {
    // логика плагина
};

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


Безопасность и стабильность расширений

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

  • изменением внутренних методов Awesomplete
  • отсутствием документированных хуков
  • конфликтами с другими расширениями

Для минимизации проблем применяются следующие практики:

  • сохранение оригинальных методов перед переопределением
  • использование обёрток вместо прямой модификации прототипа
  • изоляция логики в отдельных модулях
  • отказ от обращения к внутренним приватным свойствам

Плагин как независимый модуль

Полноценный плагин обычно оформляется как самодостаточная функция или класс, принимающий экземпляр Awesomplete.

class HighlightPlugin {
    constructor(aw) {
        this.aw = aw;
        this.init();
    }

    init() {
        const original = this.aw.renderItem;

        this.aw.renderItem = (text, input) => {
            const item = original.call(this.aw, text, input);
            item.classList.add("highlight-enabled");
            return item;
        };
    }
}

Использование:

new HighlightPlugin(aw);

Такой стиль позволяет:

  • структурировать код
  • тестировать расширения отдельно
  • переиспользовать плагины в разных проектах

Расширяемые сценарии применения

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

  • интеллектуальный поиск с ранжированием
  • интеграция с API и базами данных
  • мультиязычная обработка ввода
  • кастомные UI-компоненты внутри списка
  • аналитика пользовательских действий
  • расширенная навигация с клавиатуры

Каждый сценарий опирается на комбинацию трёх базовых техник: перехват методов, событийная модель и модификация рендеринга.