Пошук уроків, статей та іншого контенту
Розберете події введення, зміни та надсилання форм і навчитеся читати та перевіряти дані полів.
Форми в браузері взаємодіють із користувачем через події. Найчастіше використовують:
input — значення поля змінюється під час введення;
change — значення поля змінено й підтверджено користувачем;
submit — форма надсилається;
focus — поле отримує фокус;
blur — поле втрачає фокус.
Для сучасних інтерфейсів важливо розрізняти input і change. Наприклад, input дає змогу перевіряти пароль після кожного натиснення клавіші, а change — перевірити поле після завершення редагування.
inputПодія input виникає щоразу, коли користувач змінює значення текстового поля:
<input id="username" type="text">
<script>
const usernameInput = document.querySelector('#username');
usernameInput.addEventListener('input', (event) => {
console.log(event.target.value);
});
</script>event.target — елемент, на якому відбулася подія. Його властивість value містить поточне значення поля.
На відміну від change, подія input виникає для кожної зміни:
введення символу;
видалення символу;
вставлення тексту;
зміни за допомогою автозаповнення в підтримуваних браузерах.
Це зручно для:
перевірки довжини пароля;
фільтрації списку;
пошуку під час введення;
відображення лічильника символів;
миттєвого оновлення підказок.
<label for="message">Повідомлення</label>
<textarea id="message" maxlength="120"></textarea>
<p>
Символів:
<span id="counter">0</span>/120
</p>
<script>
const message = document.querySelector('#message');
const counter = document.querySelector('#counter');
message.addEventListener('input', (event) => {
counter.textContent = event.target.value.length;
});
</script>Значення textarea.value — це рядок, тому його довжину можна отримати через .length.
changeПодія change також сигналізує про зміну значення, але момент її виникнення залежить від типу елемента.
Для текстових полів подія зазвичай виникає після того, як:
користувач змінив текст;
поле втратило фокус.
Для select, checkbox і radio подія виникає одразу після вибору іншого значення.
<label for="language">Мова програмування</label>
<select id="language">
<option value="">Оберіть мову</option>
<option value="javascript">JavaScript</option>
<option value="python">Python</option>
<option value="go">Go</option>
</select>
<p id="selected-language"></p>
<script>
const language = document.querySelector('#language');
const selectedLanguage = document.querySelector('#selected-language');
language.addEventListener('change', (event) => {
const value = event.target.value;
selectedLanguage.textContent = value
? `Ви обрали: ${value}`
: 'Мову не обрано';
});
</script>Для списків і перемикачів change часто є зручнішим за input, оскільки потрібно реагувати не на кожен етап редагування, а на вибір користувача.
Для більшості елементів форми значення читають через властивість .value:
const email = document.querySelector('#email');
console.log(email.value);Перед перевіркою текстові значення часто очищують від зайвих пробілів:
const normalizedEmail = email.value.trim();Метод trim() не змінює початковий рядок, а повертає новий рядок без пробілів на початку та в кінці.
Для checkbox потрібно використовувати не .value, а .checked:
<label>
<input id="agreement" type="checkbox">
Погоджуюся з умовами
</label>
<script>
const agreement = document.querySelector('#agreement');
agreement.addEventListener('change', (event) => {
console.log(event.target.checked);
});
</script>checked === true — прапорець позначено;
checked === false — прапорець не позначено.
Властивість .value у прапорця описує його значення для надсилання форми, але не факт позначення.
Для групи радіокнопок потрібно знайти позначений елемент:
<label>
<input type="radio" name="plan" value="basic">
Базовий
</label>
<label>
<input type="radio" name="plan" value="pro">
Професійний
</label>
<script>
const selectedPlan = document.querySelector(
'input[name="plan"]:checked'
);
console.log(selectedPlan ? selectedPlan.value : null);
</script>Усі радіокнопки однієї групи повинні мати однакове значення атрибута name.
submitПодія submit виникає, коли користувач надсилає форму:
натискає кнопку з типом submit;
натискає Enter у полі форми.
Обробник потрібно додавати до самої форми, а не лише до кнопки:
<form id="login-form">
<input name="email" type="email" required>
<button type="submit">Увійти</button>
</form>
<script>
const loginForm = document.querySelector('#login-form');
loginForm.addEventListener('submit', (event) => {
event.preventDefault();
console.log('Форма готова до обробки');
});
</script>За замовчуванням браузер може перезавантажити сторінку або перейти за адресою, вказаною в action. Метод event.preventDefault() скасовує стандартну поведінку.
Його використовують, коли дані форми потрібно обробити JavaScript-кодом, наприклад:
перевірити дані;
показати помилки;
надіслати запит через fetch;
оновити інтерфейс без перезавантаження сторінки.
FormDataЯкщо поля мають атрибут name, їх можна прочитати за допомогою FormData:
<form id="profile-form">
<input name="name" type="text" value="Олена">
<input name="email" type="email" value="olena@example.com">
<button type="submit">Зберегти</button>
</form>
<script>
const profileForm = document.querySelector('#profile-form');
profileForm.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(profileForm);
console.log(formData.get('name'));
console.log(formData.get('email'));
});
</script>formData.get('name') повертає значення першого поля з таким ім’ям.
Щоб перетворити дані на звичайний об’єкт:
const data = Object.fromEntries(new FormData(profileForm));
console.log(data);Результат матиме вигляд:
{
name: 'Олена',
email: 'olena@example.com'
}FormData враховує лише поля, які мають name і можуть бути успішно надіслані. Наприклад, непозначений checkbox не потрапляє до результату.
Перевірка може виконуватися на двох рівнях:
вбудованими можливостями HTML;
додатковою логікою JavaScript.
HTML підтримує атрибути:
required — поле обов’язкове;
minlength і maxlength — обмеження довжини тексту;
min і max — обмеження числового значення;
type="email" — базова перевірка адреси електронної пошти;
pattern — перевірка за регулярним виразом.
<form>
<input
name="email"
type="email"
required
placeholder="name@example.com"
>
<input
name="password"
type="password"
minlength="8"
required
>
<button type="submit">Зареєструватися</button>
</form>Браузер не викличе submit, якщо дані не проходять вбудовану перевірку. Замість цього він покаже стандартне повідомлення.
Для перевірки окремого поля можна використати:
input.validity.valid;
input.validity.valueMissing;
input.validity.typeMismatch;
input.validity.tooShort;
input.validity.patternMismatch.
const emailInput = document.querySelector('input[type="email"]');
if (!emailInput.validity.valid) {
console.log('Некоректна електронна адреса');
}Метод checkValidity() повертає true, якщо елемент проходить валідацію:
const form = document.querySelector('form');
if (form.checkValidity()) {
console.log('Усі поля заповнено правильно');
}Метод reportValidity() додатково запускає стандартне відображення помилок браузера:
form.reportValidity();Для складніших сценаріїв можна показувати власні повідомлення. Метод setCustomValidity() встановлює помилку для поля.
Якщо передати непорожній рядок, поле стане невалідним. Якщо передати порожній рядок, власну помилку буде скасовано.
<form id="signup-form">
<label>
Пароль
<input id="password" name="password" type="password" required>
</label>
<label>
Підтвердження пароля
<input
id="password-confirmation"
name="passwordConfirmation"
type="password"
required
>
</label>
<button type="submit">Створити обліковий запис</button>
</form>
<script>
const form = document.querySelector('#signup-form');
const password = document.querySelector('#password');
const confirmation = document.querySelector('#password-confirmation');
function validatePasswords() {
if (password.value !== confirmation.value) {
confirmation.setCustomValidity('Паролі повинні збігатися');
} else {
confirmation.setCustomValidity('');
}
}
password.addEventListener('input', validatePasswords);
confirmation.addEventListener('input', validatePasswords);
form.addEventListener('submit', (event) => {
validatePasswords();
if (!form.checkValidity()) {
event.preventDefault();
}
});
</script>Важливо скасовувати помилку, коли дані знову стали правильними. Якщо залишити старе повідомлення, поле продовжить вважатися невалідним.
Нижче наведено приклад форми реєстрації з:
перевіркою імені під час введення;
перевіркою email після зміни;
перевіркою збігу паролів;
обробкою submit;
читанням даних через FormData;
власними повідомленнями про помилки.
<!doctype html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<title>Реєстрація</title>
<style>
body {
font-family: sans-serif;
max-width: 480px;
margin: 32px auto;
padding: 0 16px;
}
label {
display: block;
margin-top: 16px;
}
input {
display: block;
width: 100%;
box-sizing: border-box;
margin-top: 4px;
padding: 8px;
}
.error {
min-height: 20px;
color: #b00020;
font-size: 14px;
}
.success {
color: #167d2f;
}
</style>
</head>
<body>
<form id="signup-form" novalidate>
<h1>Створення облікового запису</h1>
<label for="name">Ім’я</label>
<input id="name" name="name" type="text" required minlength="2">
<p id="name-error" class="error"></p>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<p id="email-error" class="error"></p>
<label for="password">Пароль</label>
<input
id="password"
name="password"
type="password"
required
minlength="8"
>
<p id="password-error" class="error"></p>
<label for="confirmation">Підтвердження пароля</label>
<input
id="confirmation"
name="confirmation"
type="password"
required
>
<p id="confirmation-error" class="error"></p>
<label>
<input id="terms" name="terms" type="checkbox" required>
Я погоджуюся з умовами
</label>
<p id="terms-error" class="error"></p>
<button type="submit">Зареєструватися</button>
<p id="result"></p>
</form>
<script>
const form = document.querySelector('#signup-form');
const nameInput = document.querySelector('#name');
const emailInput = document.querySelector('#email');
const passwordInput = document.querySelector('#password');
const confirmationInput = document.querySelector('#confirmation');
const termsInput = document.querySelector('#terms');
const nameError = document.querySelector('#name-error');
const emailError = document.querySelector('#email-error');
const passwordError = document.querySelector('#password-error');
const confirmationError = document.querySelector('#confirmation-error');
const termsError = document.querySelector('#terms-error');
const result = document.querySelector('#result');
function validateName() {
const value = nameInput.value.trim();
if (value.length < 2) {
nameError.textContent = 'Ім’я має містити щонайменше 2 символи';
return false;
}
nameError.textContent = '';
return true;
}
function validateEmail() {
if (emailInput.validity.valueMissing) {
emailError.textContent = 'Введіть email';
return false;
}
if (emailInput.validity.typeMismatch) {
emailError.textContent = 'Введіть коректний email';
return false;
}
emailError.textContent = '';
return true;
}
function validatePassword() {
if (passwordInput.value.length < 8) {
passwordError.textContent = 'Пароль має містити щонайменше 8 символів';
return false;
}
passwordError.textContent = '';
return true;
}
function validateConfirmation() {
if (confirmationInput.value !== passwordInput.value) {
confirmationError.textContent = 'Паролі не збігаються';
return false;
}
confirmationError.textContent = '';
return true;
}
function validateTerms() {
if (!termsInput.checked) {
termsError.textContent = 'Потрібно погодитися з умовами';
return false;
}
termsError.textContent = '';
return true;
}
nameInput.addEventListener('input', validateName);
emailInput.addEventListener('change', validateEmail);
passwordInput.addEventListener('input', () => {
validatePassword();
validateConfirmation();
});
confirmationInput.addEventListener('input', validateConfirmation);
termsInput.addEventListener('change', validateTerms);
form.addEventListener('submit', (event) => {
event.preventDefault();
result.textContent = '';
result.className = '';
const isValid = [
validateName(),
validateEmail(),
validatePassword(),
validateConfirmation(),
validateTerms()
].every(Boolean);
if (!isValid) {
return;
}
const data = Object.fromEntries(new FormData(form));
console.log(data);
result.textContent = `Користувача ${data.name} успішно зареєстровано`;
result.className = 'success';
form.reset();
});
</script>
</body>
</html>Атрибут novalidate у цьому прикладі вимикає стандартні повідомлення браузера, щоб інтерфейс використовував власні повідомлення. Це не вимикає JavaScript-перевірку — усі функції validate... виконуються вручну.
Після перевірки дані можна передати на сервер через fetch. Наприклад:
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const data = Object.fromEntries(new FormData(form));
const response = await fetch('/api/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('Не вдалося надіслати форму');
}
});Клієнтська перевірка покращує взаємодію з користувачем, але не замінює перевірку на сервері. Дані з браузера не можна вважати безпечними, оскільки користувач може змінити JavaScript або надіслати запит без форми.
Якщо форма містить багато полів, можна слухати події на самій формі. Події введення та зміни поширюються від дочірнього елемента до батьківського.
<form id="settings-form">
<input name="nickname" type="text">
<select name="theme">
<option value="light">Світла</option>
<option value="dark">Темна</option>
</select>
</form>
<script>
const settingsForm = document.querySelector('#settings-form');
settingsForm.addEventListener('input', (event) => {
if (event.target.matches('input')) {
console.log(`Змінено поле: ${event.target.name}`);
}
});
settingsForm.addEventListener('change', (event) => {
if (event.target.matches('select')) {
console.log(`Обрано тему: ${event.target.value}`);
}
});
</script>Такий підхід називається делегуванням подій. Він особливо корисний для динамічних форм, у яких поля додаються після завантаження сторінки.
submitОбробка лише кліку кнопки не враховує надсилання форми клавішею Enter:
// Неповний підхід
button.addEventListener('click', handleSubmit);Краще обробляти подію форми:
form.addEventListener('submit', handleSubmit);preventDefault()Якщо обробник має працювати без перезавантаження сторінки, потрібно скасувати стандартну поведінку:
form.addEventListener('submit', (event) => {
event.preventDefault();
});.value для checkboxДля прапорця перевіряють .checked, а не .value:
if (termsInput.checked) {
console.log('Умови прийнято');
}nameFormData не включає поля без name:
<!-- Поле не потрапить до FormData -->
<input id="email" type="email">Правильний варіант:
<input id="email" name="email" type="email">Клієнтська валідація не є захистом сервера. Сервер повинен самостійно:
перевіряти типи й формати даних;
перевіряти обов’язкові поля;
обмежувати довжину значень;
перевіряти права доступу;
безпечно обробляти введення користувача.
changeЯкщо потрібно реагувати на кожен символ, change буде запізнілим. Для цього використовуйте input.
innerHTMLДані користувача не слід безпосередньо вставляти через innerHTML. Для звичайного тексту безпечніше використовувати:
message.textContent = userInput;Створіть форму пошуку товарів із такими вимогами:
Поле пошуку обробляє подію input.
Під час введення відображається кількість символів.
Якщо введено менше трьох символів, показується повідомлення про помилку.
Поле категорії обробляє подію change.
Під час submit форма не перезавантажує сторінку.
У консоль виводиться об’єкт, створений із FormData.
input виникає під час кожної зміни значення поля.
change повідомляє про підтверджену зміну значення.
submit потрібно обробляти на рівні форми.
event.preventDefault() скасовує стандартне надсилання форми.
Значення текстових полів читають через .value.
Стан checkbox читають через .checked.
Для групи радіокнопок шукають елемент із :checked.
FormData зручно використовувати для читання всіх полів із атрибутом name.
HTML надає базову валідацію через required, minlength, type та інші атрибути.
Складні правила можна реалізувати через validity, checkValidity() і setCustomValidity().
Перевірка на клієнті не замінює перевірку даних на сервері.