Библиотека Awesomplete построена вокруг минималистичного ядра, отвечающего за три основные задачи: обработку входных данных, фильтрацию списка и управление выпадающим меню. Такой подход делает систему легко расширяемой без изменения исходного кода. Расширение функциональности обычно реализуется через плагины, которые используют три механизма: перехват поведения, обёртки и модификацию прототипа.
Ключевая особенность архитектуры заключается в том, что Awesomplete не навязывает строгую систему плагинов. Вместо этого предоставляется набор точек расширения:
Такой дизайн делает плагины легковесными и независимыми от версии ядра при условии сохранения базового API.
Один из самых прямолинейных способов расширения — добавление или
переопределение методов в Awesomplete.prototype.
Awesomplete.prototype.highlightMatch = function (text, input) {
const regex = new RegExp(input, "gi");
return text.replace(regex, match => `<strong>${match}</strong>`);
};
После добавления метода его можно использовать внутри кастомной логики рендера или фильтрации. Такой подход обладает высокой производительностью, но требует осторожности: изменения прототипа влияют на все экземпляры.
Типичный сценарий применения:
Более безопасный способ — создание обёртки над экземпляром 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. По умолчанию используется простое сравнение строк, однако в реальных сценариях часто требуется более сложная логика: нечёткий поиск, работа с транслитерацией, приоритеты совпадений.
Awesomplete.prototype.filter = function (text, input) {
const normalize = str =>
str.toLowerCase().replace(/\s+/g, "");
return normalize(text).includes(normalize(input));
};
Такой плагин изменяет поведение поиска на уровне ядра. Более сложные реализации могут использовать:
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,
который инициирует обновление списка после получения данных.
const plugin = AsyncSourcePlugin(input, function (query) {
return fetch(`/api/search?q=${query}`)
.then(res => res.json());
});
Такой подход позволяет интегрировать автодополнение с серверными системами поиска, сохраняя при этом лёгкость клиентской логики.
Отдельным направлением расширения является изменение отображения элементов списка. Awesomplete использует метод рендеринга, который можно переопределить.
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;
};
Такой плагин позволяет:
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
]);
Для предотвращения конфликтов методы можно группировать:
aw.plugins = aw.plugins || {};
aw.plugins.highlight = function () {
// логика плагина
};
Такой подход уменьшает риск перезаписи методов ядра.
При создании плагинов критическим аспектом становится устойчивость к изменениям ядра. Основные риски связаны с:
Для минимизации проблем применяются следующие практики:
Полноценный плагин обычно оформляется как самодостаточная функция или класс, принимающий экземпляр 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 обычно используются для реализации следующих возможностей:
Каждый сценарий опирается на комбинацию трёх базовых техник: перехват методов, событийная модель и модификация рендеринга.