Вивчай
Домашнє завдання #13 ·
балівintermediate

Форма з валідацією та localStorage

Створи повноцінну форму реєстрації з валідацією в реальному часі та збереженням даних у localStorage. Це типове завдання, яке зустрічається на будь-якому вебсайті.


Файлова структура

form-validation/
├── index.html
├── style.css
└── script.js

Частина 1: HTML-форма

Створи форму реєстрації з наступними полями:

<form id="registration-form" novalidate>
  <div class="form-group">
    <label for="name">Ім'я</label>
    <input type="text" id="name" name="name" placeholder="Мінімум 2 символи">
    <span class="error-message"></span>
  </div>

  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" id="email" name="email" placeholder="example@mail.com">
    <span class="error-message"></span>
  </div>

  <div class="form-group">
    <label for="password">Пароль</label>
    <input type="password" id="password" name="password" placeholder="Мінімум 8 символів, включно з цифрою">
    <span class="error-message"></span>
  </div>

  <div class="form-group">
    <label for="confirm-password">Підтвердження пароля</label>
    <input type="password" id="confirm-password" name="confirmPassword" placeholder="Повторіть пароль">
    <span class="error-message"></span>
  </div>

  <div class="form-group">
    <label for="age">Вік</label>
    <input type="number" id="age" name="age" placeholder="16-100">
    <span class="error-message"></span>
  </div>

  <div class="form-group">
    <label>
      <input type="checkbox" id="terms" name="terms">
      Я погоджуюсь з умовами використання
    </label>
    <span class="error-message"></span>
  </div>

  <button type="submit">Зареєструватися</button>
</form>

Атрибут novalidate вимикає вбудовану браузерну валідацію. Ми напишемо свою.


Частина 2: Правила валідації

Створи функції валідації для кожного поля.

Правила

ПолеПравилоПовідомлення при помилці
Ім'яМінімум 2 символи"Ім'я має містити мінімум 2 символи"
EmailВідповідає патерну email"Введіть коректний email"
ПарольМінімум 8 символів + хоча б 1 цифра"Пароль: мінімум 8 символів та 1 цифра"
ПідтвердженняЗбігається з паролем"Паролі не збігаються"
ВікЧисло від 16 до 100"Вік має бути від 16 до 100"
УмовиЧекбокс обов'язково відмічений"Необхідно погодитись з умовами"

Приклад реалізації

// Функція валідації одного поля
function validateField(field, value) {
  switch (field) {
    case 'name':
      if (value.trim().length < 2) {
        return 'Ім\'я має містити мінімум 2 символи';
      }
      return '';

    case 'email':
      // Простий regex для email
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (!emailRegex.test(value)) {
        return 'Введіть коректний email';
      }
      return '';

    case 'password':
      if (value.length < 8) {
        return 'Пароль має містити мінімум 8 символів';
      }
      if (!/\d/.test(value)) {
        return 'Пароль має містити хоча б 1 цифру';
      }
      return '';

    // ... інші поля
  }
}

Частина 3: Валідація в реальному часі

Валідація має працювати на подіях input та blur.

Вимоги

1. Подія blur (коли поле втрачає фокус):

  • Перевірити поле та показати помилку, якщо значення некоректне

2. Подія input (під час набору):

  • Якщо поле вже має помилку, перевіряти під час набору та прибирати помилку, коли значення стане коректним
// Показати помилку
function showError(input, message) {
  const formGroup = input.closest('.form-group');
  const errorSpan = formGroup.querySelector('.error-message');
  formGroup.classList.add('error');
  formGroup.classList.remove('success');
  errorSpan.textContent = message;
}

// Показати успіх
function showSuccess(input) {
  const formGroup = input.closest('.form-group');
  const errorSpan = formGroup.querySelector('.error-message');
  formGroup.classList.remove('error');
  formGroup.classList.add('success');
  errorSpan.textContent = '';
}

3. Візуальні стилі:

.form-group.error input {
  border-color: #e74c3c;
}

.form-group.success input {
  border-color: #2ecc71;
}

.error-message {
  color: #e74c3c;
  font-size: 0.85rem;
  margin-top: 0.25rem;
  min-height: 1.2em; /* щоб макет не стрибав */
}

Частина 4: Відправка форми та localStorage

Вимоги

1. Обробка submit:

form.addEventListener('submit', (e) => {
  e.preventDefault();

  // Провалідувати ВСІ поля
  const isValid = validateAllFields();

  if (isValid) {
    saveUser();
    showSuccessMessage();
    form.reset();
    clearValidationStyles();
  }
});

2. Збереження в localStorage:

  • Зберігай масив зареєстрованих користувачів
  • Кожен запис: { name, email, age, registeredAt }
  • Пароль НЕ зберігаємо (навіть у localStorage це погана практика)
function saveUser() {
  const users = JSON.parse(localStorage.getItem('users') || '[]');

  users.push({
    name: document.getElementById('name').value.trim(),
    email: document.getElementById('email').value.trim(),
    age: Number(document.getElementById('age').value),
    registeredAt: new Date().toLocaleString('uk-UA'),
  });

  localStorage.setItem('users', JSON.stringify(users));
  renderUsers(); // оновити список знизу
}

3. Повідомлення про успіх:

  • Після збереження покажи зелене повідомлення "Реєстрація успішна!" над або під формою
  • Повідомлення зникає через 3 секунди

Частина 5: Відображення користувачів

Під формою покажи список зареєстрованих користувачів з localStorage.

function renderUsers() {
  const users = JSON.parse(localStorage.getItem('users') || '[]');
  const container = document.getElementById('users-list');

  if (users.length === 0) {
    container.innerHTML = '<p>Поки немає зареєстрованих користувачів</p>';
    return;
  }

  container.innerHTML = users
    .map(user => `
      <div class="user-card">
        <strong>${user.name}</strong>
        <span>${user.email}</span>
        <span>Вік: ${user.age}</span>
        <small>Зареєстровано: ${user.registeredAt}</small>
      </div>
    `)
    .join('');
}

// Відобразити при завантаженні сторінки
renderUsers();

Частина 6 (бонус): Індикатор складності пароля

Додай візуальний індикатор сили пароля.

Рівні

РівеньУмоваКолір
СлабкийМенше 8 символівЧервоний
Середній8+ символів, є цифраЖовтий
Сильний8+ символів, цифра + спецсимвол + велика літераЗелений
function getPasswordStrength(password) {
  let strength = 0;

  if (password.length >= 8) strength++;
  if (/\d/.test(password)) strength++;
  if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) strength++;
  if (/[A-Z]/.test(password)) strength++;
  if (password.length >= 12) strength++;

  if (strength <= 1) return { level: 'weak', label: 'Слабкий', color: '#e74c3c' };
  if (strength <= 3) return { level: 'medium', label: 'Середній', color: '#f39c12' };
  return { level: 'strong', label: 'Сильний', color: '#2ecc71' };
}

Візуалізація:

  • Прогрес-бар під полем пароля (ширина залежить від рівня)
  • Текстовий лейбл ("Слабкий", "Середній", "Сильний")
  • Оновлюється на кожен input

Підказки

  • novalidate на формі вимикає вбудовану HTML5-валідацію
  • e.preventDefault() в обробнику submit запобігає перезавантаженню сторінки
  • input.closest('.form-group') шукає найближчого батька з таким класом
  • Для перевірки email достатньо простого regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  • JSON.parse(localStorage.getItem('key') || '[]') — безпечне зчитування масиву
  • setTimeout(() => message.remove(), 3000) — видалити повідомлення через 3 секунди
  • Не зберігай пароль в localStorage навіть у навчальному проєкті

Критерії оцінки

КритерійБали
HTML-форма з усіма полями та правильною структурою10
Валідація кожного поля за правилами25
Валідація в реальному часі (blur + input)15
Візуальне відображення помилок/успіху (CSS)10
Збереження в localStorage та відображення користувачів25
Повідомлення про успішну реєстрацію5
Загальна якість коду та стилізації10
Бонус: Індикатор складності пароля+10