Элемент datalist в HTML: автозаполнение, JSON и серверный поиск

Элемент <datalist> добавляет подсказки к полю <input>. Пользователь выбирает вариант или вводит свой текст, поэтому список не ограничивает значение сам по себе. Вы свяжете элементы, загрузите варианты из JSON через fetch, настроите серверный поиск с debounce и отменой старых запросов, а затем проверите значение формы.

Элемент <datalist> и связь с input

Задайте списку уникальный id. Поле ввода получит тот же идентификатор в атрибуте list, после чего браузер покажет подсказки при вводе.

Подсказки datalist в поле ввода

Элемент <datalist> хранит варианты для поля. Атрибут value передает значение в input. Атрибут label или текст внутри option добавляет подпись.

<label for="language">Язык</label>
<input id="language" name="language" list="languages" autocomplete="off">

<datalist id="languages">
  <option value="HTML" label="Разметка страницы">HTML</option>
  <option value="CSS" label="Стили страницы">CSS</option>
  <option value="JavaScript" label="Скрипты в браузере">JavaScript</option>
  <option value="PHP">PHP</option>
</datalist>

Браузер обычно подставляет в поле значение value. Подпись из label или текстовое содержимое может отображаться рядом с ним. Храните важные данные в value, потому что вид подписи отличается.

datalist только подсказывает варианты и не ограничивает ввод. Пользователь может напечатать строку, которой нет среди option. Если форма принимает только значения списка, сравните введенное значение с option при отправке.

<form id="language-form">
  <input id="language-input" list="languages" required>
  <button type="submit">Сохранить</button>
</form>

<script>
(() => {
  const form = document.getElementById('language-form');
  const input = document.getElementById('language-input');
  const list = document.getElementById('languages');

  form.addEventListener('submit', (event) => {
    const values = [...list.options].map((option) => option.value);

    if (!values.includes(input.value)) {
      event.preventDefault();
      input.setCustomValidity('Выберите значение из списка.');
      input.reportValidity();
      return;
    }

    input.setCustomValidity('');
  });

  input.addEventListener('input', () => input.setCustomValidity(''));
})();
</script>

При отправке скрипт сравнивает значение input со всеми option. При несовпадении браузер оставляет пользователя в форме и показывает сообщение.

Загрузка вариантов из JSON через fetch

Статический список подходит для нескольких значений. Для большого набора вынесите массив в файл html-elements.json рядом с HTML-страницей.

[
  "article",
  "aside",
  "button",
  "canvas",
  "dialog",
  "form",
  "header",
  "main",
  "nav",
  "section"
]

Создайте в одной папке файлы index.html и html-elements.json. Откройте терминал в этой папке и запустите python -m http.server 8000. Затем перейдите на http://localhost:8000. Такой запуск разрешает браузеру выполнить fetch для страницы.

<label for="element-input">HTML-элемент</label>
<input id="element-input" list="element-list" placeholder="Загрузка вариантов...">
<datalist id="element-list"></datalist>

Скрипт проверяет HTTP-ответ, разбирает JSON и создает option через DocumentFragment. Метод replaceChildren удаляет старые варианты перед вставкой новых.

(() => {
  const dataList = document.getElementById('element-list');
  const input = document.getElementById('element-input');

  async function loadOptions() {
    input.placeholder = 'Загрузка вариантов...';

    try {
      const response = await fetch('html-elements.json');

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const options = await response.json();
      const fragment = document.createDocumentFragment();

      options.forEach((item) => {
        const option = document.createElement('option');
        option.value = item;
        fragment.appendChild(option);
      });

      dataList.replaceChildren(fragment);
      input.placeholder = 'Начните вводить HTML-элемент';
    } catch (error) {
      dataList.replaceChildren();
      input.placeholder = 'Не удалось загрузить варианты';
      console.error(error);
    }
  }

  loadOptions();
})();

Проверка response.ok отбрасывает ответы с ошибками HTTP, а catch меняет placeholder при сбое сети. DocumentFragment вставляет новые option одной операцией.

Варианты datalist после загрузки данных из JSON

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

Большой каталог лучше искать на сервере по мере ввода. Обработчик input ждет паузу debounce, ограничивает минимальную длину запроса и отменяет устаревший запрос.

Сохраните серверный файл как api/html-elements.php. Запустите сайт из корневой папки командой php -S localhost:8000. API будет доступно по адресу /api/html-elements.php?q=he.

<?php
header('Content-Type: application/json; charset=utf-8');

$items = [
    'header',
    'head',
    'html',
    'main',
    'nav',
    'section',
    'article',
    'aside',
    'button',
    'canvas'
];

$query = trim($_GET['q'] ?? '');
$result = array_values(array_filter(
    $items,
    static fn(string $item): bool => $query === '' || stripos($item, $query) !== false
));

echo json_encode($result, JSON_UNESCAPED_UNICODE);

PHP получает параметр q и возвращает JSON-массив строк. Документация PHP пригодится при замене массива запросом к базе данных.

<label for="server-input">HTML-элемент</label>
<input id="server-input" list="server-list" placeholder="Введите минимум 2 символа" autocomplete="off">
<datalist id="server-list"></datalist>

<script>
(() => {
  const serverInput = document.getElementById('server-input');
  const serverList = document.getElementById('server-list');
  let timer;
  let controller;

  serverInput.addEventListener('input', () => {
    clearTimeout(timer);

    const query = serverInput.value.trim();

    if (controller) {
      controller.abort();
      controller = null;
    }

    if (query.length < 2) {
      serverList.replaceChildren();
      serverInput.placeholder = 'Введите минимум 2 символа';
      return;
    }

    timer = setTimeout(async () => {
      controller = new AbortController();
      serverInput.placeholder = 'Поиск вариантов...';

      try {
        const response = await fetch(
          `/api/html-elements.php?q=${encodeURIComponent(query)}`,
          { signal: controller.signal }
        );

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }

        const options = await response.json();
        const fragment = document.createDocumentFragment();

        options.forEach((item) => {
          const option = document.createElement('option');
          option.value = item;
          fragment.appendChild(option);
        });

        serverList.replaceChildren(fragment);
        serverInput.placeholder = 'Введите минимум 2 символа';
      } catch (error) {
        if (error.name !== 'AbortError') {
          serverList.replaceChildren();
          serverInput.placeholder = 'Не удалось загрузить варианты';
          console.error(error);
        }
      }
    }, 300);
  });
})();
</script>

Скрипт ждет 300 миллисекунд после последнего ввода. Запрос короче 2 символов он не отправляет.

Новый запрос отменяет предыдущий через AbortController. Перед вставкой код очищает datalist методом replaceChildren, а DocumentFragment добавляет текущие option одной операцией.

Чем datalist отличается от select

Оба элемента показывают варианты, но подходят для разных полей. Выбирайте datalist для подсказок, а select – для обязательного выбора из закрытого набора.

Свойствоdatalistselect
Свободный вводПользователь может ввести любое значениеПользователь выбирает вариант списка
ФильтрацияБраузер фильтрует подсказки при вводеОбычно показывает варианты без свободного поиска
ОформлениеВыпадающий список почти не стилизуется через CSSВид зависит от браузера и операционной системы
Мобильное поведениеВид подсказок зависит от клавиатуры и браузераСистема часто открывает нативный список выбора

Возьмите datalist, когда человеку удобно напечатать значение вручную. Используйте select, когда форма принимает только один вариант из закрытого набора.

Ограничения datalist и кастомный combobox

У datalist простое подключение без JavaScript, но браузер управляет внешним видом и частью поведения. Поэтому компонент подходит не каждой форме.

  • Слабая стилизация. CSS почти не меняет внешний вид выпадающих подсказок.
  • Разная фильтрация. Один браузер ищет совпадение с начала строки, другой может искать подстроку.
  • Нет события выбора. У datalist нет отдельного надежного события выбора option, поэтому отслеживайте input и проверяйте значение.
  • Разное поведение на мобильных. Клавиатура и состояние списка зависят от браузера.

Если нужны группы, собственное оформление, сложная фильтрация и предсказуемые события, соберите кастомный combobox по шаблону combobox из WAI-ARIA APG. Такой компонент требует больше кода, зато дает контроль над ролями, клавиатурой и состоянием списка.

Поддержка браузерами

datalist поддерживается в Chrome, Firefox, Safari, Opera и Edge для текстовых полей. Детали интерфейса зависят от браузера, устройства и типа input.

БраузерПоддержкаОговорки
ChromeЕстьПоддержка datalist для текстовых полей
EdgeЕстьПоддержка datalist для текстовых полей
FirefoxЕстьВ Firefox для Android выпадающее меню может не появляться
OperaЕстьПоведение зависит от типа поля
SafariЕстьВнешний вид списка зависит от версии и устройства

В Firefox для Android отдельно учитывайте input с типами date, color и range: такие сочетания поддерживаются частично. Для критичной формы протестируйте ввод, клавиатуру и мобильный экран в целевых браузерах.

Когда datalist подходит для формы

Выбирайте datalist для подсказок, свободного ввода и простого списка вариантов. Если форма принимает только значения из закрытого набора, используйте select; для сложной фильтрации и полного контроля соберите combobox по шаблону WAI-ARIA APG.

Совет от нашей редакции: храните в value проверяемое значение и валидируйте его при отправке формы.

Комментарии

Оставьте свой комментарий
Пока никто не оставил комментариев