Отправка данных формы

При интеграции автодополнения 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

При необходимости сохранения классической отправки формы через 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();
});

Работа с несколькими Awesomplete-инстансами

При наличии нескольких полей логика отправки унифицируется:

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 формируется из нескольких этапов:

  1. Ввод текста пользователем
  2. Выбор значения из списка подсказок
  3. Сохранение отображаемого значения и метаданных
  4. Синхронизация скрытых или вспомогательных полей
  5. Сериализация данных при отправке
  6. Передача через FormData или JSON-запрос

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