Урок 22 из 49 · Месяц 3. JavaScript, часть 2
Работа с формами и метод POST
Содержание урока
- Из чего состоит форма
- Тег form
- Тег input
- Тег label
- Остальные элементы
- Кнопки
- Работа с формой в JavaScript
- Событие submit и preventDefault
- Получаем значения полей
- События полей: input и change
- Проверка данных (валидация)
- Встроенная проверка браузера
- Своя проверка на JavaScript
- Метод POST
- Чем POST отличается от GET
- POST-запрос через fetch
- Собираем всё вместе: форма отзыва
- Типичные ошибки
- Практика
- Итоги
Формы — это главный способ, которым пользователь передаёт сайту данные: регистрация, вход, комментарий, заказ. В этом уроке разберём HTML-теги для форм (form, input, label, select, textarea, button), научимся получать и проверять введённые значения в JavaScript и отправлять их на сервер методом POST.
Из чего состоит форма
Тег form
<form> — контейнер для всех полей формы. Он объединяет поля в одну группу и умеет «отправляться» — по кнопке или по нажатию Enter.
<form id="signup-form">
<!-- поля формы -->
<button type="submit">Зарегистрироваться</button>
</form>
Тег input
<input> — поле ввода. Это одиночный тег (без закрывающего). Его вид и поведение задаёт атрибут type:
type |
Для чего | Особенность |
|---|---|---|
text |
обычный текст | значение по умолчанию |
email |
браузер проверяет наличие @ |
|
password |
пароль | символы скрыты точками |
number |
число | стрелки, min, max, step |
tel |
телефон | на телефоне открывается цифровая клавиатура |
checkbox |
галочка «да/нет» | значение в checked |
radio |
один вариант из нескольких | у группы одинаковый name |
date |
дата | встроенный календарь |
file |
загрузка файла | выбор файла с компьютера |
Важные атрибуты:
name— имя поля. По нему мы потом достаём значение. Безnameполе не попадёт в данные формы.id— уникальный идентификатор, нужен для связи сlabel.placeholder— серая подсказка внутри пустого поля.value— значение по умолчанию.required— поле обязательно.minlength,maxlength— ограничения длины текста.min,max— ограничения для чисел и дат.pattern— регулярное выражение для проверки (вспомните урок про регулярные выражения).
Тег label
<label> — подпись к полю. Его связывают с полем через атрибут for, равный id поля:
<label for="email">Email</label>
<input id="email" name="email" type="email" placeholder="you@mail.com" required>
Зачем это нужно:
- клик по подписи ставит курсор в поле (для чекбокса — переключает галочку);
- программы чтения с экрана для незрячих людей озвучивают, к чему относится поле.
Можно и просто вложить поле внутрь label — тогда for не нужен:
<label>
<input type="checkbox" name="agree" required>
Согласен с правилами
</label>
Остальные элементы
<!-- Выпадающий список -->
<label for="city">Город</label>
<select id="city" name="city">
<option value="">Выберите город</option>
<option value="bishkek">Бишкек</option>
<option value="osh">Ош</option>
</select>
<!-- Многострочный текст -->
<label for="about">О себе</label>
<textarea id="about" name="about" rows="4"></textarea>
<!-- Переключатели: одинаковый name — выбрать можно только один -->
<p>Уровень:</p>
<label><input type="radio" name="level" value="junior" checked> Junior</label>
<label><input type="radio" name="level" value="middle"> Middle</label>
- У
<option>атрибутvalue— то, что уйдёт в данные, а текст между тегами — то, что видит пользователь. <textarea>— парный тег, значение пишется между тегами, а не вvalue.
Кнопки
<button> внутри формы по умолчанию имеет type="submit" — то есть отправляет форму. Если нужна обычная кнопка (например, «Показать пароль»), указывайте type="button":
<button type="submit">Отправить</button> <!-- отправляет форму -->
<button type="button">Показать пароль</button> <!-- просто кнопка -->
<button type="reset">Очистить</button> <!-- сбрасывает поля -->
Работа с формой в JavaScript
Событие submit и preventDefault
Когда форма отправляется, браузер по умолчанию перезагружает страницу — так было принято в старых сайтах. Нам это не нужно: мы хотим обработать данные сами. Поэтому слушаем событие submit на форме и отменяем стандартное поведение:
const form = document.querySelector("#signup-form");
form.addEventListener("submit", (event) => {
event.preventDefault(); // не перезагружать страницу
console.log("Форма отправлена!");
});
Получаем значения полей
Способ 1 — по одному полю через value:
const emailInput = document.querySelector("#email");
console.log(emailInput.value); // строка из поля
const agree = document.querySelector('[name="agree"]');
console.log(agree.checked); // true / false для чекбокса
value у input всегда строка, даже у type="number". Чтобы получить число, используйте Number(input.value) или input.valueAsNumber.
Способ 2 — все поля сразу через FormData:
form.addEventListener("submit", (event) => {
event.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data);
// { email: "anna@mail.com", city: "bishkek", about: "...", level: "junior", agree: "on" }
});
new FormData(form)собирает все поля, у которых естьname.Object.fromEntries(...)превращает их в обычный объект.- Отмеченный чекбокс даёт значение
"on", а неотмеченный вообще не попадает в данные.
События полей: input и change
inputсрабатывает при каждом изменении — на каждую нажатую букву. Подходит для живого поиска и счётчика символов.changeсрабатывает, когда изменение завершено: поле потеряло фокус, выбран вариант вselect, переключён чекбокс.
Пусть под полем «О себе» из примера выше стоит элемент для счётчика:
<small id="counter">0 / 200</small>
const about = document.querySelector("#about");
const counter = document.querySelector("#counter");
about.addEventListener("input", () => {
counter.textContent = `${about.value.length} / 200`;
});
Проверка данных (валидация)
Встроенная проверка браузера
Атрибуты required, type="email", minlength, pattern браузер проверяет сам: если что-то не так, он не даст отправить форму и покажет подсказку. Это бесплатно и работает без JavaScript.
<input name="phone" type="tel" placeholder="+996700123456"
pattern="\+996\d{9}" required>
Своя проверка на JavaScript
Встроенных сообщений часто недостаточно: нужно проверить, совпадают ли пароли, и показать ошибку красиво. Сделаем свою проверку:
function validate(data) {
const errors = {};
if (data.name.trim().length < 2) {
errors.name = "Имя должно быть не короче 2 символов";
}
if (!/^[\w.-]+@[\w-]+\.[a-z]{2,}$/i.test(data.email)) {
errors.email = "Введите корректный email";
}
if (data.password.length < 8) {
errors.password = "Пароль — минимум 8 символов";
}
if (data.password !== data.confirm) {
errors.confirm = "Пароли не совпадают";
}
return errors; // пустой объект — ошибок нет
}
Функция возвращает объект, где ключ — имя поля, а значение — текст ошибки. Если объект пустой, всё в порядке: Object.keys(errors).length === 0.
Метод POST
Чем POST отличается от GET
В уроке про fetch и GET мы получали данные. Чтобы создать что-то новое на сервере (зарегистрировать пользователя, добавить комментарий), используют метод POST.
| GET | POST | |
|---|---|---|
| Назначение | получить данные | создать новые данные |
| Где данные | в URL (query-параметры) | в теле запроса (body) |
| Видны в адресной строке | да | нет |
| Типичный ответ | 200 OK |
201 Created |
POST-запрос через fetch
Для POST в fetch передают второй аргумент — объект настроек:
async function createPost(post) {
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(post),
});
if (!response.ok) {
throw new Error(`Ошибка ${response.status}`);
}
return response.json();
}
createPost({ title: "Мой первый пост", body: "Привет, мир!", userId: 1 })
.then((created) => console.log(created));
// { title: "Мой первый пост", body: "Привет, мир!", userId: 1, id: 101 }
Разбор настроек:
method: "POST"— указываем метод (по умолчанию был бы GET).headers— заголовокContent-Type: application/jsonговорит серверу: «в теле лежит JSON».body: JSON.stringify(post)— тело запроса. Отправить можно только строку (или FormData), поэтому объект превращаем в JSON.- Сервер отвечает созданной записью с новым
idи статусом201.
Собираем всё вместе: форма отзыва
HTML:
<form id="review-form" novalidate>
<label for="name">Имя</label>
<input id="name" name="name" type="text">
<small class="error" data-error-for="name"></small>
<label for="email">Email</label>
<input id="email" name="email" type="email">
<small class="error" data-error-for="email"></small>
<label for="text">Отзыв</label>
<textarea id="text" name="text" rows="4"></textarea>
<small class="error" data-error-for="text"></small>
<button type="submit">Отправить</button>
<p id="status"></p>
</form>
Атрибут novalidate отключает встроенные подсказки браузера, потому что ошибки мы покажем сами.
JavaScript:
const form = document.querySelector("#review-form");
const statusText = document.querySelector("#status");
const submitBtn = form.querySelector('button[type="submit"]');
function validateReview(data) {
const errors = {};
if (data.name.trim().length < 2) errors.name = "Укажите имя";
if (!data.email.includes("@")) errors.email = "Неверный email";
if (data.text.trim().length < 10) errors.text = "Отзыв — минимум 10 символов";
return errors;
}
function showErrors(errors) {
form.querySelectorAll(".error").forEach((el) => {
el.textContent = errors[el.dataset.errorFor] ?? "";
});
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(form));
const errors = validateReview(data);
showErrors(errors);
if (Object.keys(errors).length > 0) return; // есть ошибки — не отправляем
submitBtn.disabled = true;
statusText.textContent = "Отправляем...";
try {
const response = await fetch("https://jsonplaceholder.typicode.com/comments", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...data, postId: 1 }),
});
if (!response.ok) throw new Error(`Ошибка ${response.status}`);
const saved = await response.json();
statusText.textContent = `Спасибо! Отзыв №${saved.id} отправлен.`;
form.reset(); // очищаем поля
} catch (error) {
statusText.textContent = "Не удалось отправить. Попробуйте ещё раз.";
console.error(error);
} finally {
submitBtn.disabled = false;
}
});
Как это работает по шагам:
- Отменяем перезагрузку страницы через
preventDefault. - Собираем данные через
FormDataв объект. - Проверяем данные;
showErrorsнаходит все элементы.errorи по атрибутуdata-error-forподставляет текст ошибки нужного поля (или очищает его). - Если ошибок нет — блокируем кнопку, чтобы пользователь не отправил отзыв дважды.
- Отправляем POST. Через spread
{ ...data, postId: 1 }добавляем к данным формы ещё одно поле. - При успехе показываем сообщение и очищаем форму методом
form.reset(); при ошибке — понятный текст. - В
finallyразблокируем кнопку в любом случае.
Типичные ошибки
- Нет
event.preventDefault()— страница перезагружается, данные пропадают, а запрос может не успеть уйти. - У поля нет
name—FormDataего не видит. - Отправили объект без
JSON.stringify— на сервер уйдёт строка"[object Object]". - Забыли заголовок
Content-Type— сервер может не понять, что в теле JSON. - Кнопка без
typeвнутри формы — неожиданно отправляет форму. Для вспомогательных кнопок пишитеtype="button".
Справка: MDN: элемент form, MDN: FormData.
Практика
- Форма входа. Сверстайте форму с полями email и пароль, у каждого поля —
label. При отправке без перезагрузки страницы выведите объект с данными в консоль. - Счётчик символов. Добавьте
textareaс ограничением 200 символов и живым счётчиком «N / 200». Когда символов больше 180, счётчик становится красным. - Регистрация с проверкой. Форма: имя, email, пароль, повтор пароля, чекбокс согласия. Показывайте ошибки под полями и не отправляйте форму, пока они есть.
- POST. Подключите отправку формы из задания 3 на
https://jsonplaceholder.typicode.com/usersметодом POST. После успеха выведите «Пользователь создан, id: …» и очистите форму. Пароль в теле запроса не отправляйте — это тестовый сервер. - Мини-блог. Форма «заголовок + текст» отправляет POST на
/posts, а ответ сервера сразу добавляется карточкой в начало списка постов на странице.
Итоги
formобъединяет поля,inputс разнымиtype— поля ввода,label— подписи, связанные черезforиid.- У каждого поля должен быть
name— по нему собираются данные. - Событие
submitслушаем на форме и отменяем перезагрузку черезevent.preventDefault(). new FormData(form)+Object.fromEntries— самый быстрый способ получить все значения.- Валидация в браузере удобна, но сервер обязан проверять данные сам.
- POST создаёт данные:
method: "POST", заголовокContent-Type: application/jsonиbody: JSON.stringify(...).