Работа с объектами данных

В реальных интерфейсах автодополнения редко используются простые строки. Чаще данные поступают в виде объектов, содержащих несколько полей: идентификатор, отображаемое имя, дополнительную метаинформацию. Awesomplete поддерживает работу с объектами через механизм кастомного отображения и выбора значения, позволяя разделить «то, что видит пользователь» и «то, что хранится в данных».

Базовая модель объектов

Awesomplete может принимать массив объектов вместо массива строк:

const list = [
  { label: "JavaScript", value: "js" },
  { label: "TypeScript", value: "ts" },
  { label: "Python", value: "py" }
];

Здесь:

  • label — текст, который отображается в списке автодополнения;
  • value — значение, которое должно быть подставлено в input.

По умолчанию библиотека не «знает» про поля label и value, поэтому требуется явно указать, как их интерпретировать.

Настройка отображения через item

Awesomplete позволяет переопределить способ отображения элементов списка с помощью функции item:

new Awesomplete(input, {
  list: list,
  item: function (text, input) {
    const li = document.createElement("li");

    li.textContent = text.label;
    li.setAttribute("data-value", text.value);

    return li;
  }
});

Здесь каждый элемент списка создаётся вручную, что даёт полный контроль над структурой DOM.

Ключевой момент: text в данном контексте — это исходный объект, а не строка.

Управление значением через replace

Для корректной подстановки выбранного значения используется опция replace:

new Awesomplete(input, {
  list: list,

  item: function (text) {
    const li = document.createElement("li");
    li.textContent = text.label;
    li.setAttribute("data-value", text.value);
    return li;
  },

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

В этом случае:

  • список отображает label;
  • в поле ввода записывается value.

Такой подход полезен, когда визуальное представление и внутреннее значение принципиально различаются.

Использование сложных объектов с дополнительными полями

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

const list = [
  {
    label: "React",
    value: "react",
    version: "18",
    type: "library"
  },
  {
    label: "Vue",
    value: "vue",
    version: "3",
    type: "framework"
  }
];

Такая структура позволяет строить расширенные интерфейсы автодополнения, где отображается не только название, но и дополнительный контекст.

Отображение дополнительных данных в списке

Awesomplete не ограничивает содержимое <li>, поэтому можно визуализировать дополнительные поля:

new Awesomplete(input, {
  list: list,

  item: function (text) {
    const li = document.createElement("li");

    const title = document.createElement("span");
    title.textContent = text.label;

    const meta = document.createElement("small");
    meta.textContent = `v${text.version} — ${text.type}`;

    li.appendChild(title);
    li.appendChild(meta);

    return li;
  },

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

В этом случае пользователь получает многоуровневую информацию, а не просто список значений.

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

Часто требуется, чтобы поиск осуществлялся по одному полю, а отображение — по другому. Awesomplete позволяет управлять этим через нормализацию списка перед передачей.

Пример подготовки данных:

const rawData = [
  { name: "JavaScript", id: 1 },
  { name: "TypeScript", id: 2 }
];

const list = rawData.map(item => ({
  label: item.name,
  value: item.id,
  search: item.name.toLowerCase()
}));

Хотя Awesomplete не использует поле search автоматически, его можно задействовать в кастомной фильтрации.

Кастомная фильтрация объектов

Стандартный механизм фильтрации Awesomplete рассчитан на строки. Для объектов его можно переопределить через filter:

new Awesomplete(input, {
  list: list,

  filter: function (text, input) {
    const value = input.trim().toLowerCase();
    return text.label.toLowerCase().includes(value);
  }
});

Теперь поиск выполняется по label, независимо от структуры объекта.

Сортировка объектов по релевантности

Дополнительно можно управлять порядком выдачи через sort:

new Awesomplete(input, {
  list: list,

  filter: function (text, input) {
    return text.label.toLowerCase().includes(input.toLowerCase());
  },

  sort: function (a, b) {
    return a.label.length - b.label.length;
  }
});

В этом примере более короткие совпадения отображаются выше.

Работа с идентификаторами и внешними данными

Частый сценарий — сохранение ID, а не текста:

const list = [
  { label: "Москва", id: 101 },
  { label: "Санкт-Петербург", id: 102 }
];

new Awesomplete(input, {
  list: list,

  item: function (text) {
    const li = document.createElement("li");
    li.textContent = text.label;
    return li;
  },

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

Таким образом:

  • пользователь видит название города;
  • форма хранит числовой идентификатор в data-id.

Обновление списка объектов динамически

Awesomplete позволяет изменять список после инициализации:

awesomplete.list = [
  { label: "Go", value: "go" },
  { label: "Rust", value: "rust" }
];

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

Интеграция с API

Типичный сценарий — загрузка объектов с сервера:

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

Здесь преобразование данных является обязательным этапом, так как Awesomplete не нормализует серверные ответы автоматически.

Сложные структуры с вложенными объектами

Иногда данные приходят в виде вложенных структур:

const list = [
  {
    label: "Frontend",
    items: [
      { label: "React", value: "react" },
      { label: "Vue", value: "vue" }
    ]
  }
];

Awesomplete не поддерживает группировку напрямую, поэтому такие данные требуют предварительного «разворачивания»:

const flatList = list.flatMap(group => group.items);

После этого можно использовать стандартные механизмы отображения.

Безопасность при работе с объектами

При использовании пользовательских данных важно учитывать потенциальные XSS-риски при вставке HTML:

li.innerHTML = text.label;

Такой подход допустим только при гарантированной очистке данных. Более безопасный вариант:

li.textContent = text.label;

Для сложных интерфейсов следует использовать явное создание DOM-узлов вместо innerHTML.

Стабильность структуры данных

Ключевым требованием при работе с объектами в Awesomplete является неизменность структуры элементов списка. Все функции:

  • item
  • replace
  • filter
  • sort

должны опираться на одинаковую модель объекта. Любое несоответствие приводит к некорректной работе автодополнения, особенно при динамическом обновлении списка.