Перейти к содержанию

Форма и FormData: применение параметров

Немедленный отбор по событию change удобен для одного поля. Теперь добавим порядок карточек и будем применять оба параметра одним действием. HTML-форма объединяет поля, а событие submit позволяет обработать применение независимо от того, нажал читатель кнопку мышью или использовал доступный клавиатурный способ.

Данные четырёх курсов и функция создания карточек остаются прежними. Из прошлого урока удалите весь блок управления: topicSelect, прежние selection и showSelection, регистрацию обработчика change и первоначальный вызов showSelection. Ниже заменим его обработкой отправки формы. Если оставить старый блок и добавить новый, возникнут повторные объявления; если оставить оба события, параметры будут применяться в разные моменты.

Два поля одного действия

Замените только form в существующем index.html следующим фрагментом. Сообщение, список и подключение скрипта остаются за пределами формы.

<form id="catalog-controls">
  <fieldset>
    <legend>Отбор курсов</legend>
    <label>Тема <select name="topic">
      <option value="all">Все темы</option>
      <option value="frontend">Фронтенд</option>
      <option value="publishing">Публикация</option>
    </select></label>
    <label>Порядок <select name="sort">
      <option value="original">Исходный</option>
      <option value="lessons">По числу уроков</option>
    </select></label>
    <button type="submit">Применить</button>
  </fieldset>
</form>

fieldset объединяет настройки, legend называет их общую задачу. У каждого поля есть name, по которому получим значение. Машинный код lessons обозначает выбранный способ сортировки, а видимая подпись объясняет его человеку. Явный type="submit" делает назначение кнопки понятным при чтении разметки.

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

Сбор именованных значений

В app.js оставьте данные, ссылки на form, list и status, а также renderCourses. Добавьте selectCourses из урока о деструктуризации, если её ещё нет в файле. На месте удалённого блока управления запишите следующий полный блок:

let selection = { topic: 'all', sort: 'original' };
function showSelection() {
  const visible = selectCourses(courses, selection);
  renderCourses(list, visible);
  status.textContent = visible.length === 0
    ? 'По выбранной теме курсов нет.'
    : `Курсов: ${visible.length}.`;
}
form.addEventListener('submit', event => {
  event.preventDefault();
  const values = new FormData(form);
  selection = {
    topic: String(values.get('topic') ?? 'all'),
    sort: String(values.get('sort') ?? 'original'),
  };
  showSelection();
});
showSelection();

Ожидаемое начальное отображение содержит четыре карточки. Выберите фронтенд и порядок по числу уроков, затем нажмите «Применить». Ожидается последовательность производительность, HTML и CSS, JavaScript с количествами двенадцать, шестнадцать и двадцать. До применения выбор полей не меняет текущий результат.

FormData(form) собирает текущие значения подходящих именованных полей. В нашем случае get получает строковый код. При отсутствии имени результат может быть null, поэтому используется ?? со смысловым начальным вариантом. Преобразование String делает вход для модели явным, но не является проверкой разрешённых кодов.

Форма может содержать несколько полей с одинаковым именем. Тогда get вернёт первое значение, а для набора применяется getAll. Наши одиночные select специально имеют разные имена, поэтому список значений пока не требуется. Поведение коллекции описано в MDN о FormData.

Отправка формы и стандартное действие

Обработчик зарегистрирован на форме, а не только на кнопке. Это сохраняет единое место применения параметров для разных способов отправки. Метод preventDefault отменяет стандартное действие текущего события, но не превращает обработчик в серверную проверку и не останавливает выполнение всей программы.

Не путайте preventDefault с stopPropagation: второй метод влияет на распространение события, а не отменяет навигацию формы сам по себе. В нашем интерфейсе остановка распространения не требуется. Событие submit и связанные способы запуска рассматриваются в MDN.

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

Числовой ввод отдельно от кодов

Количество уроков в данных является числом, а значения обычных полей формы часто являются строками. Если будущий интерфейс получит поле минимального количества, одно чтение FormData не даст готовое корректное число. Рассмотрим самостоятельный учебный фрагмент, который пока не добавляется в наш постоянный каталог:

const rawMinimum = '12';
const minimum = Number(rawMinimum);
const valid = rawMinimum.trim() !== '' &&
  Number.isInteger(minimum) && minimum >= 0;
console.log(valid, minimum);

Ожидаются true и двенадцать. Проверка непустой строки нужна до принятия результата, потому что пустой ввод преобразуется в ноль. Минимум может допускать ноль даже тогда, когда количество уроков отдельного курса обязано быть положительным. У каждого поля собственный предметный смысл.

Выключенные поля не включаются в обычный сбор FormData. В итоговом проекте fieldset будет временно отключён во время загрузки. Поэтому обработка параметров должна происходить в состоянии готового каталога, а не читать искусственно пустую форму и объявлять это новым выбором. Блокировка интерфейса и состояние программы должны выражать одно правило.

Снимок полей и состояние программы

Созданный FormData отражает значения подходящих полей в момент построения. Он не является живой связью, которая автоматически меняет ранее собранные значения после следующего выбора. Поэтому создаём новый экземпляр при каждом submit, а не один раз при загрузке страницы. Точно так же объект selection хранит уже применённый выбор, а поля могут временно показывать ещё неприменённые настройки.

Это различие становится полезным, если читатель отменил изменение или только выбирает варианты. Карточки соответствуют применённому состоянию, кнопка обозначает переход к новому. Если интерфейс требует немедленную реакцию, следует вернуться к модели события изменения; нельзя одновременно обещать оба поведения без ясной причины.

Кнопка сброса формы меняет поля к исходным значениям, но сама по себе не обязана восстановить наш отдельный объект selection и карточки. Для такого продукта понадобится обработка сброса или явная функция возвращения состояния. В текущий пример её не добавляем: оставляем одну кнопку применения и понятный поток. Эта оговорка предотвращает ожидание, что любое HTML-действие автоматически синхронизирует все переменные.

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

Для дополнительного наблюдения удалите временно name у одного поля и предскажите результат get. После опыта восстановите имя. Так видно, почему подписи полей и идентификаторы DOM не заменяют именованные ключи формы.

При чтении через FormData имя формы не является ключом полей. Мы передаём сам объект form в конструктор, а внутри используем name каждого элемента. Переименование id потребует изменить поиск DOM, переименование name — изменить чтение значений. Это две разные связи, которые стоит проверить отдельно при реорганизации разметки.

Кнопка вне формы с type="button" позднее будет выполнять обновление источника. Она не должна случайно отправлять параметры отбора. Явные типы кнопок делают эти обязанности различимыми даже после добавления новых элементов. Если кнопку перенести внутрь формы и оставить неявный тип, поведение может измениться без изменения обработчика.

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

Для ручного наблюдения выберите публикацию, примените и ожидайте одну карточку. Затем выберите все темы и исходный порядок: снова должны появиться четыре записи в порядке данных. Это показывает, что форма управляет представлением, не редактируя сами курсы. Следующим шагом разнесём модель, представление и запуск по модулям, сохранив поведение формы.

Оглавление курса