Библиотека Awesomplete построена как компактный класс с минимальным количеством публичных API и значительной долей внутренней логики, скрытой в приватных методах экземпляра. Такая структура делает её быстрой и предсказуемой, но одновременно требует аккуратного подхода при изменении стандартного поведения.
В основе лежит класс Awesomplete, который управляет
жизненным циклом подсказок: получение данных, фильтрация, сортировка,
рендеринг списка и обработка пользовательского ввода. Почти вся логика
сосредоточена в методах с подчёркиванием (_evaluate,
_list, _sort, _renderItem), что
формально обозначает их как внутренние, но фактически оставляет
возможность для модификации через прототип или перехват экземпляра.
Патчинг в контексте Awesomplete сводится к изменению одного из трёх уровней:
AwesompleteКаждый из подходов имеет свою область применимости и набор ограничений, связанных с тем, что библиотека не проектировалась как плагин-ориентированная система.
Одним из самых безопасных способов изменить поведение является
модификация источника данных до того, как он попадёт в Awesomplete. Поле
list может быть как массивом строк, так и массивом
объектов, либо функцией, возвращающей данные.
При использовании функции можно полностью контролировать поток данных:
const aw = new Awesomplete(input, {
list: function() {
return fetch("/api/suggestions")
.then(r => r.json())
.then(data => data.items);
}
});
В этом сценарии фактически происходит подмена механизма получения данных, а сама библиотека остаётся неизменной. Такой подход часто используется для внедрения:
Более агрессивная форма патчинга заключается в оборачивании функции перед её передачей:
const originalSource = fetchSuggestions;
aw.list = function() {
return originalSource().map(item => item.toLowerCase());
};
Здесь происходит вмешательство в поток данных до этапа фильтрации и сортировки.
_evaluateМетод _evaluate является центральной точкой логики
Awesomplete. Он вызывается при каждом изменении значения input и
отвечает за:
Патчинг этого метода позволяет изменить поведение автодополнения на фундаментальном уровне.
const originalEvaluate = Awesomplete.prototype._evaluate;
Awesomplete.prototype._evaluate = function() {
if (this.input.value.length < 3) {
this.ul.innerHTML = "";
return;
}
originalEvaluate.call(this);
};
В данном случае добавлено ограничение минимальной длины запроса, хотя
библиотека уже имеет minChars, но через патч можно
реализовать более сложные условия, например:
Более сложная модификация может полностью заменить поведение:
Awesomplete.prototype._evaluate = function() {
const value = this.input.value.trim();
const filtered = this._list
.filter(x => x.startsWith(value))
.slice(0, 5);
this._render(filtered);
};
Такой подход полностью подменяет встроенную систему фильтрации и сортировки.
_sortСортировка в Awesomplete по умолчанию основана на степени совпадения строки с введённым значением. Однако в реальных приложениях часто требуется более сложная логика: приоритет популярности, частоты использования или контекстных факторов.
Переопределение _sort позволяет внедрить такие
правила:
Awesomplete.prototype._sort = function(a, b) {
const query = this.input.value.toLowerCase();
const aScore = a.toLowerCase().includes(query) ? 1 : 0;
const bScore = b.toLowerCase().includes(query) ? 1 : 0;
return bScore - aScore;
};
В этом примере реализована бинарная сортировка по наличию подстроки.
Более сложные реализации могут использовать внешние метрики:
const frequencyMap = {
"apple": 10,
"apricot": 2,
"banana": 7
};
Awesomplete.prototype._sort = function(a, b) {
return (frequencyMap[b] || 0) - (frequencyMap[a] || 0);
};
Такой подход позволяет превратить Awesomplete в систему с поведенческой адаптацией.
_renderItemМетод _renderItem отвечает за создание DOM-элементов
списка подсказок. Он критически важен при необходимости изменения
визуального представления результатов.
Стандартная реализация создаёт элементы <li> с
текстовым содержимым. Переопределение позволяет внедрять сложную
разметку:
Awesomplete.prototype._renderItem = function(text, input) {
const li = document.createElement("li");
const highlighted = text.replace(
new RegExp(input, "gi"),
match => `<strong>${match}</strong>`
);
li.innerHTML = highlighted;
return li;
};
Здесь добавляется базовая подсветка совпадений. Однако такой подход
требует осторожности, так как innerHTML может приводить к
уязвимостям при работе с неподконтрольными данными.
Более структурированный вариант:
Awesomplete.prototype._renderItem = function(text) {
const li = document.createElement("li");
const span = document.createElement("span");
span.textContent = text;
const meta = document.createElement("small");
meta.textContent = "suggestion";
li.appendChild(span);
li.appendChild(meta);
return li;
};
Таким образом можно превратить список подсказок в сложный компонент с метаданными.
_filterФильтрация в Awesomplete по умолчанию достаточно простая — проверка вхождения строки. Однако часто требуется учитывать:
Патчинг _filter позволяет внедрить кастомные
алгоритмы:
Awesomplete.prototype._filter = function(text, input) {
return text.toLowerCase().includes(input.toLowerCase());
};
Это базовая модификация, но её можно расширить:
function normalize(str) {
return str
.toLowerCase()
.replace(/ё/g, "е")
.replace(/[^a-zа-я0-9]/gi, "");
}
Awesomplete.prototype._filter = function(text, input) {
return normalize(text).includes(normalize(input));
};
Такой подход улучшает работу с мультиязычными данными и пользовательским вводом с ошибками.
Awesomplete не предоставляет полноценной системы событий, но
поведение можно расширить через перехват методов open и
close.
const originalOpen = Awesomplete.prototype.open;
Awesomplete.prototype.open = function() {
console.log("Список открыт");
originalOpen.call(this);
};
Аналогично для закрытия:
const originalClose = Awesomplete.prototype.close;
Awesomplete.prototype.close = function() {
console.log("Список закрыт");
originalClose.call(this);
};
Такие перехваты часто используются для:
Помимо методов прототипа, возможен патчинг конкретного экземпляра. Это позволяет локализовать изменения.
const aw = new Awesomplete(input);
aw._sort = function(a, b) {
return a.length - b.length;
};
В этом случае изменение не затрагивает другие экземпляры. Такой подход предпочтителен при:
Однако следует учитывать, что внутренние методы могут измениться в будущих версиях библиотеки, что делает такие патчи хрупкими.
В более сложных сценариях список может быть полностью виртуализирован. Вместо хранения массива данных используется функция, генерирующая элементы на лету.
Awesomplete.prototype._list = function() {
return Array.from({ length: 1000 }, (_, i) => `Item ${i}`);
};
Это позволяет интегрировать библиотеку в системы с динамическими данными без предварительной загрузки полного набора.
При комбинировании нескольких патчей возникает риск конфликтов, особенно при изменении одних и тех же методов. Для минимизации проблем используется сохранение цепочки оригинальных функций:
(function() {
const original = Awesomplete.prototype._evaluate;
Awesomplete.prototype._evaluate = function() {
// дополнительная логика
if (this.input.value === "") return;
original.call(this);
};
})();
Такой IIFE-подход позволяет изолировать модификации и уменьшить вероятность глобального загрязнения пространства прототипа.
При большом количестве расширений формируется цепочка обёрток, где каждое новое изменение вызывает предыдущее, сохраняя базовое поведение библиотеки.
Любое изменение внутренних методов Awesomplete влияет на:
_evaluateОсобенно чувствительными являются методы _evaluate и
_renderItem, так как они вызываются многократно в течение
короткого времени. Неоптимальный патч может привести к деградации UX,
включая задержки отображения списка и визуальные артефакты.
При глубоком патчинге важно учитывать, что Awesomplete не имеет встроенного механизма отмены операций или очередей задач, поэтому любые тяжёлые вычисления внутри переопределённых методов выполняются синхронно.
При наличии нескольких независимых модификаций целесообразно строить композицию функций:
function patchEvaluate(fn) {
return function() {
if (this.input.value.length < 2) return;
return fn.apply(this, arguments);
};
}
Awesomplete.prototype._evaluate = patchEvaluate(Awesomplete.prototype._evaluate);
Такой функциональный подход позволяет:
Композиция особенно важна при создании библиотек-обёрток вокруг Awesomplete, где каждое расширение должно быть независимым слоем поверх базового поведения.