🚀 Bài 4B: HTML Forms Nâng Cao

Master HTML5 Input Types, Custom Validation & Dynamic Forms

📺 Video Bài Giảng

💡 Tip: Xem video trước khi làm bài tập để hiểu rõ hơn về các khái niệm!

📖 Giới thiệu

Trong bài nâng cao này, bạn sẽ học:

💡 Mục tiêu: Sau khi hoàn thành bài này, bạn sẽ có thể xây dựng các form phức tạp với validation nâng cao, tương tác realtime, và trải nghiệm người dùng chuyên nghiệp. Các kỹ năng này rất quan trọng trong phỏng vấn và dự án thực tế!

🔒 0. Thuộc tính Input Nâng cao

📋 Các thuộc tính quan trọng

Thuộc tính Mô tả User có thể Submit? Use case
disabled Vô hiệu hóa hoàn toàn ❌ Không tương tác ❌ Không gửi Tính năng chưa khả dụng
readonly Chỉ đọc, không chỉnh sửa ✅ Có thể select/copy ✅ Được gửi Hiển thị data hệ thống
required Bắt buộc nhập ✅ Phải điền ✅ Nếu có giá trị Fields bắt buộc
pattern Regex validation ✅ Phải match pattern ✅ Nếu hợp lệ Format cụ thể (phone, code)
minlength/maxlength Độ dài ký tự ✅ Trong giới hạn ✅ Nếu hợp lệ Username, password
min/max Giá trị min/max (number, date) ✅ Trong khoảng ✅ Nếu hợp lệ Age, date range
step Bước nhảy (number, range) ✅ Theo bước Slider, số lượng
autocomplete Tự động điền ✅ Browser gợi ý Email, address, phone
autofocus Focus tự động khi load First field trong form
placeholder Gợi ý nhập liệu Hướng dẫn format

📋 HTML Examples

<!-- DISABLED - Không tương tác được, không submit -->
<input type="text" name="userId" value="USER_123" disabled>

<!-- READONLY - Có thể copy, được submit -->
<input type="text" name="createdDate" value="2025-01-15" readonly>

<!-- PATTERN - Validation với Regex -->
<input type="text" name="phone" 
       pattern="[0-9]{4}-[0-9]{3}-[0-9]{3}" 
       placeholder="0123-456-789"
       title="Format: 0123-456-789">

<!-- MINLENGTH/MAXLENGTH -->
<input type="text" name="username" 
       minlength="3" 
       maxlength="20" 
       required>

<!-- MIN/MAX/STEP -->
<input type="number" name="age" 
       min="18" 
       max="100" 
       step="1">

<!-- AUTOCOMPLETE -->
<input type="email" name="email" 
       autocomplete="email">
<input type="text" name="address" 
       autocomplete="street-address">

<!-- AUTOFOCUS -->
<input type="text" name="search" autofocus>

🎯 Demo: Thuộc tính Input

🔒 So sánh disabled vs readonly:

💻 JavaScript: Thao tác với thuộc tính

// Get/Set disabled
const input = document.getElementById('myInput');
console.log(input.disabled); // true/false
input.disabled = true; // Disable
input.disabled = false; // Enable

// Get/Set readonly
input.readOnly = true; // Readonly
input.readOnly = false; // Editable

// Get/Set required
input.required = true;

// Get/Set pattern
input.pattern = '[A-Za-z0-9]{3,20}';

// Get/Set min/max/step
input.min = '18';
input.max = '100';
input.step = '5';

// Get/Set minLength/maxLength
input.minLength = 3;
input.maxLength = 20;

// Check validity
console.log(input.validity.valid); // true/false
console.log(input.validationMessage); // Error message

// Custom validation
input.setCustomValidity('Lỗi tùy chỉnh!');
input.setCustomValidity(''); // Clear error

📅 1. HTML5 Input Types Nâng Cao

📋 Tổng quan các Input Types

Input Type 🎯 Mục đích ⏰ Dùng khi nào 💡 Ví dụ thực tế
date Chọn ngày (YYYY-MM-DD) Cần chọn ngày sinh, deadline, booking Ngày sinh, ngày đặt phòng, ngày hẹn
time Chọn giờ (HH:MM) Cần chọn giờ cụ thể Giờ hẹn bác sĩ, giờ đặt bàn
datetime-local Chọn ngày + giờ Cần cả ngày và giờ Lịch meeting, event, reminder
week Chọn tuần trong năm Báo cáo theo tuần, lịch làm việc Report tuần, schedule tuần
month Chọn tháng/năm Thẻ tín dụng, báo cáo tháng Expiry date thẻ, budget tháng
range Slider chọn giá trị Volume, brightness, price range Âm lượng, độ ưu tiên, filter giá
color Chọn màu sắc Theme, highlight, design tool Chọn màu chủ đạo, màu text
file Upload file Avatar, documents, images Upload CV, ảnh đại diện, tài liệu
search Ô tìm kiếm Search box, filter Tìm sản phẩm, tìm bài viết
url Nhập URL Website, social links Facebook URL, portfolio link
tel Số điện thoại Contact, registration Hotline, SĐT khách hàng

📋 HTML: Date & Time Inputs

<!-- Date input - Chọn ngày -->
<input type="date" name="birthdate" min="1950-01-01" max="2005-12-31">

<!-- Time input - Chọn giờ -->
<input type="time" name="appointment" min="08:00" max="18:00">

<!-- Datetime-local input - Chọn ngày + giờ -->
<input type="datetime-local" name="event_time">

<!-- Week input - Chọn tuần -->
<input type="week" name="work_week">

<!-- Month input - Chọn tháng/năm (dùng cho thẻ tín dụng) -->
<input type="month" name="card_expiry">

📋 HTML: Range, Color & File Inputs

<!-- Range slider - Âm lượng, độ ưu tiên -->
<input type="range" name="volume" min="0" max="100" value="50">

<!-- Color picker - Chọn màu chủ đạo -->
<input type="color" name="theme_color" value="#667eea">

<!-- File upload - Avatar (single file) -->
<input type="file" name="avatar" accept="image/*">

<!-- File upload - Documents (multiple files) -->
<input type="file" name="documents" multiple accept=".pdf,.doc,.docx">

📋 HTML: Search, URL, Tel Inputs

<!-- Search input - Tìm kiếm -->
<input type="search" name="query" placeholder="Tìm kiếm...">

<!-- URL input - Website link -->
<input type="url" name="website" placeholder="https://example.com">

<!-- Tel input - Số điện thoại -->
<input type="tel" name="phone" placeholder="0123456789" pattern="[0-9]{10}">

📊 Thuộc tính đặc biệt

Input Type Thuộc tính Mô tả Ví dụ
date, time min, max Giới hạn ngày/giờ min="2024-01-01"
range min, max, step Giá trị slider step="5"
file accept Loại file chấp nhận accept="image/*"
file multiple Cho phép nhiều file multiple

⚡ Events của Advanced Inputs

Event Input Type Khi nào xảy ra Data truy cập
input range Realtime khi kéo slider e.target.value
change color, date, file Khi chọn xong e.target.value
change file Khi chọn file e.target.files

🎯 Demo: Advanced Input Types

📅 HTML5 Input Types với JavaScript:

💻 Code JavaScript giải thích:

// Date input - Tính tuổi
dateInput.addEventListener('change', function(e) {
  const birthDate = new Date(e.target.value);
  const today = new Date();
  const age = Math.floor((today - birthDate) / (365.25 * 24 * 60 * 60 * 1000));
  
  console.log('Age:', age);
});

// Range input - Realtime value
rangeInput.addEventListener('input', function(e) {
  const value = e.target.value;
  document.getElementById('displayValue').textContent = value;
});

// Color picker
colorInput.addEventListener('change', function(e) {
  const color = e.target.value;
  document.body.style.setProperty('--theme-color', color);
});

// File input - Đọc thông tin files
fileInput.addEventListener('change', function(e) {
  const files = e.target.files;
  
  Array.from(files).forEach(file => {
    console.log('File:', file.name);
    console.log('Size:', file.size, 'bytes');
    console.log('Type:', file.type);
  });
});

📦 2. Fieldset và Legend

📋 HTML: Nhóm Form Elements

<form>
  <fieldset>
    <legend>Thông tin cá nhân</legend>
    
    <label for="firstName">Họ:</label>
    <input type="text" id="firstName" name="firstName" required>
    
    <label for="lastName">Tên:</label>
    <input type="text" id="lastName" name="lastName" required>
  </fieldset>
  
  <fieldset disabled>
    <legend>Thông tin hệ thống (chỉ đọc)</legend>
    
    <label>User ID:</label>
    <input type="text" value="USER_12345" readonly>
  </fieldset>
</form>

📊 Thuộc tính Fieldset

Thuộc tính Mô tả Ví dụ
disabled Disable toàn bộ inputs trong fieldset <fieldset disabled>
name Tên fieldset (optional) name="personalInfo"

⚡ JavaScript với Fieldsets

// Enable/Disable fieldset
function toggleFieldset(fieldsetId, enable) {
  const fieldset = document.getElementById(fieldsetId);
  fieldset.disabled = !enable;
}

// Validate từng fieldset
function validateFieldset(fieldset) {
  const inputs = fieldset.querySelectorAll('input[required]');
  let isValid = true;
  
  inputs.forEach(input => {
    if (!input.value.trim()) {
      input.classList.add('error');
      isValid = false;
    } else {
      input.classList.remove('error');
    }
  });
  
  return isValid;
}

📊 2.5. Form Data Collection - Cách lấy dữ liệu Form

📚 Giải thích chi tiết về FormData và Object.fromEntries

🎓 Hiểu về FormData Constructor

new FormData(form) là gì?

  • new FormData() là một constructor (hàm khởi tạo) tạo ra một object đặc biệt
  • Object này chứa tất cả dữ liệu từ form ở dạng key-value pairs
  • Tham số form là DOM element của form (lấy bằng getElementById, querySelector, v.v.)
  • Nó tự động lấy tất cả namevalue của các input trong form

🔍 FormData lưu dữ liệu như thế nào?

// Ví dụ form HTML:
<input name="username" value="john_doe">
<input name="email" value="john@mail.com">
<input name="age" value="25">

// FormData sẽ lưu như sau:
FormData {
  "username" => "john_doe",
  "email" => "john@mail.com",
  "age" => "25"
}

⚠️ Lưu ý: Tất cả giá trị đều là string (kiểu chuỗi), kể cả số!

Object.fromEntries() là gì?

  • Object.fromEntries() là một phương thức built-in của JavaScript
  • Nó chuyển đổi một danh sách [key, value] thành một object thông thường
  • FormData có cấu trúc đặc biệt, không phải object thông thường, nên cần chuyển đổi
  • Sau khi chuyển đổi, ta có thể dùng object như bình thường: data.username, data.email

🔄 Quy trình hoạt động:

1️⃣ const formData = new FormData(form);
   → Tạo FormData object từ form
   → Lấy tất cả input có name attribute

2️⃣ const data = Object.fromEntries(formData);
   → Chuyển FormData thành object thông thường
   → Bây giờ có thể dùng: data.username, data.email

3️⃣ console.log(data);
   → Kết quả: {username: "john_doe", email: "john@mail.com", age: "25"}

💡 Tại sao không truy cập trực tiếp?

❌ Cách cũ (dài dòng):

const data = {
  username: form.username.value,
  email: form.email.value,
  age: form.age.value,
  phone: form.phone.value,
  address: form.address.value
};

✅ Cách mới (ngắn gọn):

const formData = new FormData(form);
const data = Object.fromEntries(formData);

// Tự động lấy tất cả!
// Không cần viết từng field

🎯 Khi nào dùng FormData?

  • ✅ Khi form có nhiều field (>5 inputs)
  • ✅ Khi cần upload file (bắt buộc dùng FormData)
  • ✅ Khi muốn code ngắn gọn, không phải viết từng field
  • ✅ Khi cần gửi dữ liệu lên server (AJAX/Fetch)
  • ❌ Không phù hợp khi cần validate từng field riêng biệt

📋 Các cách lấy dữ liệu từ Form

🔹 Cách 1: FormData API (Khuyến nghị ⭐)

// Cách đơn giản nhất - FormData API
const form = document.getElementById('myForm');

// Method 1: Get all data as object
form.addEventListener('submit', function(e) {
  e.preventDefault();
  
  const formData = new FormData(this);
  const data = Object.fromEntries(formData);
  
  console.log(data);
  // {username: "john", email: "john@mail.com", age: "25"}
});

// Method 2: Iterate FormData
formData.forEach((value, key) => {
  console.log(key, value);
});

// Method 3: Get specific field
const username = formData.get('username');
const files = formData.getAll('photos'); // Multiple files

🔹 Cách 2: Truy cập trực tiếp từng input

// Cách cũ - truy cập từng input
const form = document.getElementById('myForm');

const data = {
  username: form.elements.username.value,
  email: form.elements.email.value,
  age: form.elements.age.value
};

// Hoặc dùng querySelector
const username = form.querySelector('[name="username"]').value;

// Hoặc getElementById
const email = document.getElementById('email').value;

🔹 Cách 3: Serialize form thành JSON

// Function serialize form to JSON
function serializeForm(form) {
  const formData = new FormData(form);
  const data = {};
  
  for (let [key, value] of formData.entries()) {
    // Handle multiple values (checkboxes)
    if (data[key]) {
      if (!Array.isArray(data[key])) {
        data[key] = [data[key]];
      }
      data[key].push(value);
    } else {
      data[key] = value;
    }
  }
  
  return data;
}

// Usage
const jsonData = serializeForm(form);
console.log(JSON.stringify(jsonData, null, 2));

🔹 Xử lý Checkboxes và Radio buttons

// Get all checked checkboxes
const hobbies = Array.from(
  form.querySelectorAll('input[name="hobbies"]:checked')
).map(cb => cb.value);

// Get selected radio
const gender = form.querySelector('input[name="gender"]:checked')?.value;

// Get select value
const country = form.querySelector('select[name="country"]').value;

// Get select text (not value)
const select = form.querySelector('select[name="country"]');
const selectedText = select.options[select.selectedIndex].text;

🔹 Xử lý File Input

// Get file(s) from input
const fileInput = form.querySelector('input[type="file"]');
const files = fileInput.files; // FileList

// Single file
const file = files[0];
console.log('Name:', file.name);
console.log('Size:', file.size, 'bytes');
console.log('Type:', file.type);

// Multiple files
Array.from(files).forEach(file => {
  console.log(file.name);
});

// Read file content
const reader = new FileReader();
reader.onload = function(e) {
  console.log('File content:', e.target.result);
};
reader.readAsText(file); // or readAsDataURL() for images

🎯 Demo: Form Data Collection

📊 Các cách lấy dữ liệu Form:

🔧 3. Custom Validation với JavaScript

📋 Validation API

// Validity State API
const input = document.getElementById('email');

// Check validity
console.log(input.validity.valid); // true/false

// Validity properties
console.log(input.validity.valueMissing);  // required field empty
console.log(input.validity.typeMismatch);   // wrong type (email)
console.log(input.validity.patternMismatch); // pattern không match
console.log(input.validity.tooLong);        // vượt maxlength
console.log(input.validity.tooShort);       // dưới minlength
console.log(input.validity.rangeUnderflow); // dưới min
console.log(input.validity.rangeOverflow);  // trên max
console.log(input.validity.stepMismatch);   // không đúng step

// Get validation message
console.log(input.validationMessage);

// Set custom error
input.setCustomValidity('Email này đã tồn tại!');

// Clear custom error
input.setCustomValidity('');

// Check form validity
const form = document.getElementById('myForm');
console.log(form.checkValidity()); // Validate toàn bộ form

📋 Validation Functions (Copy & Paste được luôn!)

// 1. Validate Required Field
function validateRequired(input) {
  if (!input.value.trim()) {
    input.setCustomValidity('Field này là bắt buộc!');
    return false;
  }
  input.setCustomValidity('');
  return true;
}

// 2. Validate Email
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!regex.test(email)) {
    return { valid: false, message: 'Email không đúng định dạng!' };
  }
  return { valid: true };
}

// 3. Validate Phone (Vietnam)
function validatePhone(phone) {
  const regex = /^(0|\+84)[0-9]{9}$/;
  if (!regex.test(phone)) {
    return { valid: false, message: 'SĐT phải 10 số, bắt đầu bằng 0' };
  }
  return { valid: true };
}

// 4. Validate Age Range
function validateAge(age, min = 18, max = 100) {
  const numAge = parseInt(age);
  if (isNaN(numAge)) {
    return { valid: false, message: 'Tuổi phải là số!' };
  }
  if (numAge < min) {
    return { valid: false, message: `Tuổi phải >= ${min}` };
  }
  if (numAge > max) {
    return { valid: false, message: `Tuổi phải <= ${max}` };
  }
  return { valid: true };
}

// 5. Validate Password Match
function validatePasswordMatch(password, confirmPassword) {
  if (password !== confirmPassword) {
    return { valid: false, message: 'Mật khẩu không khớp!' };
  }
  return { valid: true };
}

// 6. Validate File (type & size)
function validateFile(file, allowedTypes, maxSize) {
  // Check type
  if (!allowedTypes.includes(file.type)) {
    return { 
      valid: false, 
      message: `Chỉ chấp nhận: ${allowedTypes.join(', ')}` 
    };
  }
  
  // Check size (maxSize in MB)
  const maxBytes = maxSize * 1024 * 1024;
  if (file.size > maxBytes) {
    return { 
      valid: false, 
      message: `File quá lớn! Max: ${maxSize}MB` 
    };
  }
  
  return { valid: true };
}

// 7. Show/Hide Error Message
function showError(input, message) {
  // Remove existing error
  const existingError = input.parentElement.querySelector('.error-message');
  if (existingError) {
    existingError.remove();
  }
  
  // Add new error
  const errorDiv = document.createElement('div');
  errorDiv.className = 'error-message';
  errorDiv.style.color = 'red';
  errorDiv.style.fontSize = '0.85em';
  errorDiv.style.marginTop = '5px';
  errorDiv.textContent = message;
  
  input.parentElement.appendChild(errorDiv);
  input.style.borderColor = 'red';
}

function clearError(input) {
  const errorDiv = input.parentElement.querySelector('.error-message');
  if (errorDiv) {
    errorDiv.remove();
  }
  input.style.borderColor = 'green';
}

🎯 Demo: Complete Validation Example

🔧 Form với đầy đủ validation:

🎯 Bài tập thực hành nâng cao

📚 Hướng dẫn làm bài

  • ✅ Mỗi bài có demo minh họa để bạn hiểu rõ yêu cầu
  • ✅ Đọc kỹ phần checklist để không bỏ sót
  • ✅ Tham khảo code gợi ý nếu gặp khó khăn
  • ✅ Các bài sắp xếp từ đơn giản → phức tạp
  • ✅ Tập trung vào validationUX (trải nghiệm người dùng)
  • 💡 Làm lần lượt từng bài, không nên nhảy bước!

🎫 Bài tập 1: Form Đặt vé sự kiện

📺 Demo minh họa:
╔══════════════════════════════════════════════╗
║       🎫 FORM ĐẶT VÉ SỰ KIỆN ONLINE         ║
╚══════════════════════════════════════════════╝

┌─────────────────────────────────────────────┐
│ 👤 Họ tên: [Nguyễn Văn A          ] *      │
│ 📧 Email:  [nguyenvana@mail.com   ] *      │
│ 📅 Ngày sự kiện: [2025-12-25      ] *      │
│    (Không được chọn ngày quá khứ)           │
│ ⏰ Giờ bắt đầu:   [14:00          ] *      │
│    (Chỉ từ 09:00 - 18:00)                   │
│                                              │
│ 🎟️ Loại vé:                                 │
│    ○ VIP (500,000đ)                         │
│    ● Standard (200,000đ)  ← Selected        │
│    ○ Student (100,000đ)                     │
│                                              │
│ 🔢 Số lượng vé: [━━━━●━━━━] 5              │
│    Tổng tiền: 1,000,000đ                    │
│    (Cập nhật realtime khi kéo)              │
│                                              │
│ 📄 Upload thông tin (PDF):                  │
│    [📁 Choose File] resume.pdf (2.3 MB)    │
│    ✅ File hợp lệ                           │
│                                              │
│         [🎯 ĐẶT VÉ NGAY]                    │
└─────────────────────────────────────────────┘

💾 Form tự động lưu vào LocalStorage!

📋 Yêu cầu HTML:

  • ✅ Họ tên (text, required, minlength="3")
  • ✅ Email (email, required)
  • ✅ Ngày sự kiện (date, min=hôm nay, required)
  • ✅ Giờ bắt đầu (time, min="09:00", max="18:00", required)
  • ✅ Số lượng vé (range, min="1", max="10", value="1")
  • ✅ Loại vé (radio: VIP, Standard, Student - required)
  • ✅ Upload file (file, accept=".pdf", required)

⚡ JavaScript nâng cao:

  • 🔍 Date validation: Kiểm tra không được chọn ngày quá khứ
  • 💰 Range input: Hiển thị tổng tiền realtime (VIP=500k, Standard=200k, Student=100k)
  • 📁 File validation: Chỉ chấp nhận PDF, max 5MB
  • 💾 Auto-save: Lưu form vào LocalStorage mỗi khi input thay đổi
  • 🔄 Load saved data: Khôi phục dữ liệu khi refresh trang
  • UX: Hiển thị loading state khi submit

✅ Checklist hoàn thành:

💡 Code gợi ý:

// Tính tổng tiền realtime
const prices = { vip: 500000, standard: 200000, student: 100000 };
const quantityInput = document.getElementById('quantity');
const totalDisplay = document.getElementById('total');

function updateTotal() {
  const ticketType = document.querySelector('input[name="ticketType"]:checked').value;
  const quantity = quantityInput.value;
  const total = prices[ticketType] * quantity;
  totalDisplay.textContent = total.toLocaleString('vi-VN') + 'đ';
}

quantityInput.addEventListener('input', updateTotal);
document.querySelectorAll('input[name="ticketType"]').forEach(radio => {
  radio.addEventListener('change', updateTotal);
});

// File validation
const fileInput = document.getElementById('file');
fileInput.addEventListener('change', function(e) {
  const file = e.target.files[0];
  if (!file) return;
  
  // Check type
  if (file.type !== 'application/pdf') {
    alert('Chỉ chấp nhận file PDF!');
    this.value = '';
    return;
  }
  
  // Check size (5MB = 5 * 1024 * 1024 bytes)
  if (file.size > 5 * 1024 * 1024) {
    alert('File không được vượt quá 5MB!');
    this.value = '';
    return;
  }
  
  console.log('File hợp lệ:', file.name);
});

// LocalStorage auto-save
const form = document.getElementById('eventForm');
form.addEventListener('input', function() {
  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  localStorage.setItem('eventForm', JSON.stringify(data));
  console.log('✅ Đã lưu tự động');
});

// Load saved data
window.addEventListener('load', function() {
  const saved = localStorage.getItem('eventForm');
  if (saved) {
    const data = JSON.parse(saved);
    Object.keys(data).forEach(key => {
      const input = form.elements[key];
      if (input) input.value = data[key];
    });
    updateTotal();
  }
});

💳 Bài tập 2: Form Thanh toán (Credit Card)

📺 Demo minh họa:
╔══════════════════════════════════════════════╗
║       💳 FORM THANH TOÁN THẺ TÍN DỤNG       ║
╚══════════════════════════════════════════════╝

┌─────────────────────────────────────────────┐
│ 👤 Tên chủ thẻ: [NGUYEN VAN A      ] *     │
│    (Tự động chuyển UPPERCASE)                │
│                                              │
│ 💳 Số thẻ: [1234 5678 9012 3456    ] *     │
│    (Tự động format 4 số mỗi nhóm)           │
│    ✅ Số thẻ hợp lệ (Luhn algorithm)        │
│                                              │
│ 📅 Ngày hết hạn: [2025-12         ] *      │
│    ✅ Thẻ còn hiệu lực                      │
│                                              │
│ 🔒 CVV: [***] *                             │
│    (3-4 số, type=password)                   │
│                                              │
│ 🎨 Màu thẻ: [🟦] #2196F3                   │
│                                              │
│ ┌──────────── PREVIEW THẺ ────────────┐    │
│ │  ╔════════════════════════════════╗ │    │
│ │  ║         [🟦 BLUE CARD]         ║ │    │
│ │  ║                                 ║ │    │
│ │  ║  💳 1234 5678 9012 3456        ║ │    │
│ │  ║                                 ║ │    │
│ │  ║  NGUYEN VAN A      12/25       ║ │    │
│ │  ╚════════════════════════════════╝ │    │
│ └──────────────────────────────────────┘    │
│                                              │
│         [💰 THANH TOÁN NGAY]                │
└─────────────────────────────────────────────┘

⚡ Real-time validation và preview!

📋 Yêu cầu HTML:

  • ✅ Tên thẻ (text, required, pattern="[A-Z ]+", uppercase)
  • ✅ Số thẻ (text, required, maxlength="19", auto-format với space)
  • ✅ Ngày hết hạn (month, required, min=tháng hiện tại)
  • ✅ CVV (password, required, pattern="[0-9]{3,4}", maxlength="4")
  • ✅ Màu thẻ (color, value="#2196F3")
  • ✅ Preview thẻ realtime với HTML

⚡ JavaScript nâng cao:

  • 🔢 Luhn Algorithm: Validate số thẻ theo chuẩn quốc tế
  • Auto-format: Tự động thêm space sau mỗi 4 số (1234 5678 9012 3456)
  • 🔒 CVV validation: Kiểm tra 3-4 số, ẩn giá trị (type=password)
  • 🎨 Card preview: Cập nhật realtime khi nhập liệu
  • 📅 Expiry validation: Không được chọn tháng quá khứ
  • 🔠 Name uppercase: Tự động chuyển tên thành UPPERCASE
  • 🎭 Card type detection: Nhận diện Visa/Mastercard/... (bonus)

✅ Checklist hoàn thành:

💡 Code gợi ý:

// Auto-format card number (4 số mỗi nhóm)
const cardInput = document.getElementById('cardNumber');
cardInput.addEventListener('input', function(e) {
  let value = e.target.value.replace(/\s/g, ''); // Remove spaces
  let formatted = value.match(/.{1,4}/g)?.join(' ') || value;
  e.target.value = formatted;
  
  // Update preview
  document.getElementById('previewNumber').textContent = formatted || '•••• •••• •••• ••••';
});

// Luhn Algorithm - Credit card validation
function validateLuhn(cardNumber) {
  const digits = cardNumber.replace(/\s/g, '').split('').reverse();
  let sum = 0;
  
  for (let i = 0; i < digits.length; i++) {
    let digit = parseInt(digits[i]);
    
    if (i % 2 === 1) { // Every second digit
      digit *= 2;
      if (digit > 9) digit -= 9;
    }
    
    sum += digit;
  }
  
  return sum % 10 === 0;
}

// CVV validation
const cvvInput = document.getElementById('cvv');
cvvInput.addEventListener('input', function(e) {
  e.target.value = e.target.value.replace(/\D/g, '').slice(0, 4);
});

// Name uppercase
const nameInput = document.getElementById('cardName');
nameInput.addEventListener('input', function(e) {
  e.target.value = e.target.value.toUpperCase();
  document.getElementById('previewName').textContent = e.target.value || 'YOUR NAME';
});

// Color picker - Update card preview
const colorInput = document.getElementById('cardColor');
colorInput.addEventListener('input', function(e) {
  document.getElementById('cardPreview').style.background = 
    `linear-gradient(135deg, ${e.target.value} 0%, ${e.target.value}dd 100%)`;
});

// Expiry validation
const expiryInput = document.getElementById('expiry');
expiryInput.addEventListener('change', function(e) {
  const selected = new Date(e.target.value + '-01');
  const now = new Date();
  now.setDate(1); // First day of month
  
  if (selected < now) {
    alert('Thẻ đã hết hạn!');
    e.target.value = '';
  }
});

🏆 MASTER CHECKLIST - Tổng hợp tất cả kỹ năng

📝 HTML5 Features:

  • ☐ Input types (date, time, range, color, file)
  • ☐ Fieldset và Legend
  • ☐ HTML5 validation attributes
  • ☐ Pattern và custom messages

⚡ JavaScript Advanced:

  • ☐ Custom validation logic
  • ☐ Credit card Luhn algorithm
  • ☐ Auto-format inputs
  • ☐ Realtime calculation

🎨 UX/UI Features:

  • ☐ Real-time preview (card, color)
  • ☐ File validation (type, size)
  • ☐ Dynamic price calculation
  • ☐ Input formatting (card numbers)

💾 Data Management:

  • ☐ LocalStorage auto-save
  • ☐ Load saved data on refresh
  • ☐ FormData API usage
  • ☐ Object.fromEntries conversion

🎓 Khi hoàn thành 2 bài tập, bạn đã thành thạo HTML Forms nâng cao!

Những kỹ năng này rất quan trọng trong thực tế và phỏng vấn!

💡 Tip: Tập trung vào validationUX để tạo forms chuyên nghiệp!