Базовая модель работы Awesomplete предполагает линейный список строк
или объектов, который фильтруется и отображается в виде плоской выдачи.
При необходимости группировки элементов эта модель расширяется за счёт
кастомизации трёх ключевых механизмов: list,
filter, item, а также частично
sort. Группировка не является встроенной функцией, поэтому
вся логика реализуется на уровне представления и подготовки данных.
Группировка в контексте автодополнения обычно означает визуальное и логическое разделение элементов на категории без изменения базового механизма выбора значения.
Для организации группировки требуется перейти от простых строк к объектной модели данных. Каждый элемент списка начинает включать метаданные:
const data = [
{ label: "JavaScript", value: "JavaScript", group: "Languages" },
{ label: "TypeScript", value: "TypeScript", group: "Languages" },
{ label: "React", value: "React", group: "Frameworks" },
{ label: "Vue", value: "Vue", group: "Frameworks" }
];
В Awesomplete допустимо использовать объекты, где:
label — текст для отображенияvalue — значение, подставляемое в inputgroup) используются для логики
отображенияПоскольку Awesomplete отображает список в том порядке, в котором он
приходит, сортировка выполняется заранее либо через
sort-функцию библиотеки.
awesomplete.sort = (a, b) => {
if (a.group === b.group) {
return a.label.localeCompare(b.label);
}
return a.group.localeCompare(b.group);
};
Такой подход обеспечивает стабильное расположение элементов внутри одной категории и группировку по алфавиту или заданному порядку категорий.
Ключевой механизм группировки — функция item, которая
отвечает за генерацию DOM-элемента.
awesomplete.item = function (text, input) {
const li = document.createElement("li");
li.textContent = text.label || text.value;
li.setAttribute("data-value", text.value);
if (text.group) {
li.setAttribute("data-group", text.group);
li.classList.add("awesomplete-item");
}
return li;
};
Через эту функцию можно добавлять дополнительные атрибуты, классы и структуру, включая визуальные маркеры групп.
Awesomplete не поддерживает отдельные заголовки списков, поэтому применяется техника вставки “служебных элементов”. Такие элементы не участвуют в выборе, но служат визуальными разделителями.
function buildGroupedList(data) {
const result = [];
let currentGroup = null;
data.forEach(item => {
if (item.group !== currentGroup) {
currentGroup = item.group;
result.push({
label: currentGroup,
value: "",
isHeader: true
});
}
result.push(item);
});
return result;
}
Далее в item происходит различие:
awesomplete.item = function (text) {
const li = document.createElement("li");
if (text.isHeader) {
li.textContent = text.label;
li.classList.add("awesomplete-group-header");
li.setAttribute("aria-disabled", "true");
} else {
li.textContent = text.label;
li.setAttribute("data-value", text.value);
}
return li;
};
Такой подход позволяет визуально отделить группы без изменения внутренней логики библиотеки.
Псевдо-элементы групп не должны участвовать в выборе. Для этого
применяется replace:
awesomplete.replace = function (suggestion) {
if (suggestion.isHeader) return;
this.input.value = suggestion.value;
};
Также можно дополнительно блокировать навигацию:
awesomplete.filter = function (text, input) {
if (text.isHeader) return false;
return Awesomplete.FILTER_CONTAINS(text.label, input);
};
При группировке часто требуется сохранить контекст категорий даже после фильтрации. Это достигается через расширенную фильтрацию списка:
awesomplete.filter = function (text, input) {
if (text.isHeader) return false;
return text.label.toLowerCase().includes(input.toLowerCase());
};
При более сложной логике можно фильтровать по группе:
awesomplete.filter = function (text, input) {
if (text.isHeader) return false;
const matchText = text.label.toLowerCase().includes(input.toLowerCase());
const matchGroup = text.group && text.group.toLowerCase().includes(input.toLowerCase());
return matchText || matchGroup;
};
Псевдо-заголовки групп могут ломать стандартную навигацию стрелками. Для корректной работы требуется исключить их из фокуса.
awesomplete.data = buildGroupedList(data);
И дополнительная фильтрация на уровне DOM:
.awesomplete-group-header {
font-weight: bold;
pointer-events: none;
cursor: default;
}
Для строгого управления навигацией можно переопределить внутреннюю
логику выбора активного элемента через модификацию классов
aria-selected.
Иногда группировка реализуется исключительно визуально через отступы и порядок:
awesomplete.sort = (a, b) => {
return a.group.localeCompare(b.group) || a.label.localeCompare(b.label);
};
И отображение:
awesomplete.item = function (text) {
const li = document.createElement("li");
li.textContent = text.label;
li.classList.add("group-" + text.group.toLowerCase());
return li;
};
CSS-стили:
.group-languages {
padding-left: 10px;
}
.group-frameworks {
padding-left: 20px;
}
Такой подход проще, но не даёт явных разделителей.
Для стабильного UX часто требуется фиксированный порядок категорий:
const groupOrder = {
"Languages": 1,
"Frameworks": 2,
"Tools": 3
};
awesomplete.sort = (a, b) => {
const ga = groupOrder[a.group] || 999;
const gb = groupOrder[b.group] || 999;
if (ga !== gb) return ga - gb;
return a.label.localeCompare(b.label);
};
В более сложных интерфейсах элементы могут содержать дополнительные состояния:
{
label: "React",
value: "React",
group: "Frameworks",
disabled: false,
description: "UI library"
}
И обработка в item:
awesomplete.item = function (text) {
const li = document.createElement("li");
if (text.disabled) {
li.classList.add("disabled");
li.setAttribute("aria-disabled", "true");
}
li.innerHTML = `
<span class="label">${text.label}</span>
<span class="description">${text.description || ""}</span>
`;
if (!text.isHeader) {
li.setAttribute("data-value", text.value);
}
return li;
};
При росте списка до сотен и тысяч элементов группировка начинает влиять на скорость фильтрации. Оптимизация достигается за счёт:
filterconst groupedCache = buildGroupedList(data);
awesomplete.list = groupedCache;
Группировка в Awesomplete строится как комбинация нескольких слоёв:
group)filter)item)replace)Такой подход позволяет реализовать многоуровневые списки автодополнения без изменения ядра библиотеки и сохранять контроль над визуальной и логической структурой данных.