Работа с hidden полями

В типичных сценариях автодополнения отображаемое значение и фактическое значение, которое требуется отправить на сервер, не совпадают. Awesomplete работает с массивами строк, но реальные формы часто требуют более сложной модели данных: идентификатор записи, код справочника, UUID или числовой ключ вместо человекочитаемого названия. Именно здесь используются скрытые поля (hidden input), позволяющие разделить UI-представление и данные, уходящие в форму.


Разделение отображаемого текста и фактического значения

Базовая проблема возникает при использовании списков вида:

  • пользователь выбирает: Москва
  • серверу нужно: 77

Awesomplete по умолчанию возвращает строку, которую выбрал пользователь. Однако форма должна отправить не текст, а идентификатор.

Решение заключается в использовании пары полей:

  • видимое поле — для ввода и автодополнения
  • скрытое поле — для хранения значения (id, code, uuid)

Простейшая HTML-структура:

<input id="city" type="text" />
<input id="city_id" type="hidden" />

Базовая связка Awesomplete с hidden input

При инициализации Awesomplete можно использовать массив объектов, где хранится и метка, и значение:

const cities = [
  { label: "Москва", value: "77" },
  { label: "Санкт-Петербург", value: "78" },
  { label: "Казань", value: "16" }
];

Инициализация:

const input = document.getElementById("city");
const hidden = document.getElementById("city_id");

const awesomplete = new Awesomplete(input, {
  list: cities.map(c => c.label)
});

На этом этапе Awesomplete работает только с отображаемыми строками. Для привязки значения требуется обработка выбора.


Событие выбора и синхронизация hidden поля

Awesomplete предоставляет событие awesomplete-selectcomplete, которое вызывается после выбора элемента из списка.

input.addEventListener("awesomplete-selectcomplete", function(event) {
  const selectedLabel = event.text.value;

  const selectedItem = cities.find(c => c.label === selectedLabel);

  if (selectedItem) {
    hidden.value = selectedItem.value;
  }
});

Здесь происходит ключевой процесс:

  • отображаемое значение остаётся в input
  • скрытое поле получает идентификатор

Проблема неоднозначности строк

Использование поиска по label через find работает только при уникальных значениях. В реальных справочниках возможны совпадения:

  • «Париж» (Франция)
  • «Париж» (Техас)

Поэтому более надёжный подход — связывать данные через индекс или расширенную структуру Awesomplete.


Использование расширенного списка объектов

Awesomplete поддерживает работу с объектами, если задать label и value, но рендеринг нужно контролировать через item и replace.

const awesomplete = new Awesomplete(input, {
  list: cities,

  item: function(text, input) {
    return Awesomplete.ITEM(text.label, input);
  },

  replace: function(text) {
    this.input.value = text.label;
  }
});

Теперь объект полностью доступен внутри логики выбора, и скрытое поле можно обновлять без поиска по массиву:

input.addEventListener("awesomplete-selectcomplete", function(event) {
  const item = event.text;

  hidden.value = item.value;
});

Полная синхронизация состояния

В реальных формах важно учитывать не только выбор из списка, но и ручной ввод. Пользователь может:

  • ввести текст вручную
  • изменить уже выбранное значение
  • стереть поле

Поэтому hidden input должен очищаться при любом изменении текста:

input.addEventListener("input", function() {
  hidden.value = "";
});

Это предотвращает ситуацию, когда отправляется устаревший id при изменённом тексте.


Использование dataset для хранения идентификаторов

Альтернативный подход — хранить идентификатор прямо в DOM через data-*:

input.dataset.selectedId = "";

Обновление при выборе:

input.addEventListener("awesomplete-selectcomplete", function(event) {
  input.dataset.selectedId = event.text.value;
});

Однако такой подход менее надёжен при отправке формы, поскольку dataset не участвует в form submission напрямую.


Привязка к отправке формы

При отправке формы hidden input становится основным источником данных:

form.addEventListener("submit", function(e) {
  if (!hidden.value) {
    e.preventDefault();
    alert("Необходимо выбрать значение из списка");
  }
});

Это позволяет обеспечить строгую валидацию: текст в input не считается валидным без соответствующего id.


Обработка асинхронных источников данных

Во многих приложениях список загружается с сервера:

fetch("/api/cities")
  .then(res => res.json())
  .then(data => {
    awesomplete.list = data.map(c => ({
      label: c.name,
      value: c.id
    }));
  });

Важно учитывать, что данные могут обновляться динамически, и связь label/value должна оставаться стабильной.


Сложные формы: несколько связанных hidden полей

В расширенных сценариях одно автодополнение может управлять несколькими скрытыми значениями:

  • id
  • код региона
  • тип объекта

HTML:

<input id="location" type="text" />
<input id="location_id" type="hidden" />
<input id="location_type" type="hidden" />

Логика:

input.addEventListener("awesomplete-selectcomplete", function(event) {
  hiddenId.value = event.text.value.id;
  hiddenType.value = event.text.value.type;
});

В этом случае value становится объектом, а не строкой.


Очистка и сброс состояния

При реализации форм с возможностью редактирования требуется корректный сброс:

function resetField() {
  input.value = "";
  hidden.value = "";
}

Особенно важно при:

  • переключении шагов формы
  • изменении зависимых полей
  • загрузке данных для редактирования записи

Валидация соответствия текста и hidden значения

Скрытое поле должно соответствовать текущему тексту. Проверка:

function isValid() {
  return input.value.length > 0 && hidden.value.length > 0;
}

Более строгий вариант:

function isConsistent() {
  const match = cities.find(c => c.label === input.value);
  return match && match.value === hidden.value;
}

Интеграция с кастомными форматами Awesomplete

При использовании filter, sort или кастомных источников важно сохранять структуру объекта:

const awesomplete = new Awesomplete(input, {
  list: cities,
  filter: function(text, input) {
    return text.label.toLowerCase().includes(input.toLowerCase());
  }
});

Hidden-поле при этом остаётся независимым от механизма фильтрации и обновляется только при выборе.


Обработка удаления значения пользователем

Удаление содержимого input должно автоматически сбрасывать hidden:

input.addEventListener("keyup", function(e) {
  if (e.key === "Backspace" && input.value === "") {
    hidden.value = "";
  }
});

Это защищает от ситуации, когда пользователь очищает поле вручную, но скрытый id остаётся.


Поведение при программной установке значения

При автозаполнении формы важно синхронизировать оба поля:

function setCity(label, id) {
  input.value = label;
  hidden.value = id;
}

Без этого Awesomplete не участвует в процессе, но состояние формы должно оставаться целостным.


Расширенная модель: двухсторонняя привязка

В сложных интерфейсах hidden поле становится частью состояния:

  • input → UI слой
  • hidden → модель данных

Рекомендуется централизовать обновление:

function updateState(item) {
  input.value = item.label;
  hidden.value = item.value;
}

И использовать её во всех сценариях: выбор, загрузка, сброс.


Типичные ошибки при работе со скрытыми полями

  1. Использование find по label без уникальности
  2. Отсутствие сброса hidden при ручном вводе
  3. Хранение id только в dataset
  4. Отсутствие синхронизации при программном изменении
  5. Использование строк вместо объектов в списке
  6. Игнорирование асинхронного обновления данных

Поведение при множественных Awesomplete-инстансах

При нескольких автодополнениях на странице важно избегать перекрёстного влияния:

function bindAutocomplete(inputId, hiddenId, list) {
  const input = document.getElementById(inputId);
  const hidden = document.getElementById(hiddenId);

  new Awesomplete(input, { list });

  input.addEventListener("awesomplete-selectcomplete", function(e) {
    hidden.value = e.text.value;
  });

  input.addEventListener("input", function() {
    hidden.value = "";
  });
}

Это обеспечивает изоляцию состояния между полями.