При интеграции автодополнения Awesomplete в формы основная сложность
возникает не в выборе значения из списка, а в корректной передаче
выбранных данных на сервер. Поведение стандартного
<input> ограничено тем, что оно хранит только строку,
тогда как в сценариях с автодополнением часто требуется работа с
идентификаторами, массивами значений, метаданными и составными
структурами.
Стандартная HTML-форма отправляет данные в виде пар
name=value, где значение берётся напрямую из поля
ввода:
<form action="/submit" method="POST">
<input name="city" id="cityInput">
<button type="submit">Отправить</button>
</form>
При использовании Awesomplete поле остаётся тем же самым, однако его содержимое формируется через выбор из подсказок:
const input = document.getElementById("cityInput");
new Awesomplete(input, {
list: ["Almaty", "Astana", "Karaganda", "Shymkent"]
});
В этом случае в форму отправляется строковое значение, совпадающее с текстом выбранного элемента. Такой подход работает только при совпадении отображаемого значения и значения, которое требуется серверу.
В реальных сценариях список часто состоит из объектов:
const cities = [
{ label: "Almaty", value: 1 },
{ label: "Astana", value: 2 },
{ label: "Karaganda", value: 3 }
];
Awesomplete принимает только строковый список, поэтому требуется промежуточное представление:
const list = cities.map(item => `${item.label}|${item.value}`);
После выбора необходимо восстановить структуру:
input.addEventListener("awesomplete-selectcomplete", function (event) {
const [label, value] = event.text.value.split("|");
input.dataset.value = value;
input.value = label;
});
В результате отображаемое значение отделяется от фактического
идентификатора, который сохраняется в data-* атрибуте.
При отправке формы важно подменить текстовое значение на идентификатор или собрать оба значения:
const form = document.querySelector("form");
form.addEventListener("submit", function (e) {
const input = document.getElementById("cityInput");
const payload = {
cityLabel: input.value,
cityId: input.dataset.value
};
fetch("/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
e.preventDefault();
});
В этом варианте стандартная отправка формы блокируется, а данные
передаются через fetch. Такой подход позволяет
контролировать структуру отправляемого объекта.
При необходимости сохранения классической отправки формы через
FormData используется скрытое поле:
<input type="hidden" name="city_id" id="cityIdInput">
И синхронизация при выборе значения:
input.addEventListener("awesomplete-selectcomplete", function (event) {
const [label, value] = event.text.value.split("|");
input.value = label;
document.getElementById("cityIdInput").value = value;
});
Теперь форма остаётся стандартной:
form.addEventListener("submit", function (e) {
const data = new FormData(form);
fetch("/submit", {
method: "POST",
body: data
});
e.preventDefault();
});
Awesomplete часто используется для ввода тегов или списка элементов. В этом случае поле превращается в контейнер значений, разделённых разделителем.
const input = document.getElementById("tagsInput");
const aw = new Awesomplete(input, {
list: ["JavaScript", "HTML", "CSS", "Node.js"],
filter: Awesomplete.FILTER_CONTAINS
});
Обработка добавления значений:
let values = [];
input.addEventListener("awesomplete-selectcomplete", function (event) {
values.push(event.text.value);
input.value = values.join(", ");
});
Перед отправкой данные нормализуются:
form.addEventListener("submit", function (e) {
const data = new FormData(form);
data.set("tags", JSON.stringify(values));
fetch("/submit", {
method: "POST",
body: data
});
e.preventDefault();
});
В этом случае поле ввода используется только как интерфейс, а
реальная структура хранится в массиве values.
При накоплении значений возникает необходимость исключать повторения:
function addUnique(arr, item) {
if (!arr.includes(item)) {
arr.push(item);
}
}
Использование:
input.addEventListener("awesomplete-selectcomplete", function (event) {
addUnique(values, event.text.value);
input.value = values.join(", ");
});
Для корректной синхронизации важно учитывать удаление значений через клавиатуру:
input.addEventListener("input", function () {
const parts = input.value.split(",").map(s => s.trim());
values = parts.filter(v => v.length > 0);
});
Так поддерживается актуальность внутреннего состояния относительно отображаемого текста.
При наличии объектов вместо строк используется индексирование:
const map = new Map([
["Almaty", { id: 1, region: "south" }],
["Astana", { id: 2, region: "north" }]
]);
Выбор значения:
input.addEventListener("awesomplete-selectcomplete", function (event) {
const key = event.text.value;
const obj = map.get(key);
input.dataset.id = obj.id;
input.dataset.region = obj.region;
});
Перед отправкой:
form.addEventListener("submit", function (e) {
const data = {
name: input.value,
id: input.dataset.id,
region: input.dataset.region
};
fetch("/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
e.preventDefault();
});
При использовании автодополнения часто требуется запретить ручной ввод произвольных значений:
let isValid = false;
input.addEventListener("awesomplete-selectcomplete", function () {
isValid = true;
});
input.addEventListener("input", function () {
isValid = false;
});
Проверка перед отправкой:
form.addEventListener("submit", function (e) {
if (!isValid) {
e.preventDefault();
return;
}
});
Так обеспечивается, что отправляются только значения, выбранные из списка.
Даже при клиентской проверке серверная сторона должна получать структурированные данные. Для этого часто используется единый контракт:
{
"field": "city",
"value": 3,
"label": "Karaganda"
}
Формирование такого объекта:
form.addEventListener("submit", function (e) {
const payload = {
field: "city",
value: input.dataset.value,
label: input.value
};
fetch("/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
e.preventDefault();
});
При наличии нескольких полей логика отправки унифицируется:
const form = document.querySelector("form");
form.addEventListener("submit", function (e) {
const data = {};
document.querySelectorAll("[data-awesomplete-field]").forEach(el => {
data[el.name] = {
value: el.dataset.value || el.value,
label: el.value
};
});
fetch("/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
e.preventDefault();
});
Такой подход позволяет масштабировать форму без привязки к конкретным полям.
При сложных сценариях требуется дополнительная обработка перед отправкой:
form.addEventListener("submit", async function (e) {
e.preventDefault();
const enriched = await Promise.all(values.map(async v => {
const res = await fetch(`/api/resolve?q=${v}`);
return res.json();
}));
fetch("/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(enriched)
});
});
Такая схема применяется при необходимости преобразования пользовательского ввода в нормализованные сущности.
Поведение формы с Awesomplete формируется из нескольких этапов:
FormData или JSON-запросКаждый этап влияет на конечную структуру запроса, а корректная организация этих этапов определяет надёжность всей формы.