Session 2.6 – Client-Side Form Validation

Module 2: JavaScript & AJAX | Duration: 1 hr

Learning Objectives

By the end of this session, students will be able to:

  • Understand the importance of client-side form validation
  • Implement HTML5 built-in validation attributes
  • Create custom validation using JavaScript
  • Validate common patterns (email, phone, passwords)
  • Provide user-friendly error messages
  • Implement real-time validation feedback

Introduction

Form validation is essential for ensuring data quality and providing good user experience. Client-side validation offers immediate feedback to users before data is sent to the server, reducing server load and improving responsiveness.

Key Insight

Client-side validation improves user experience but should never be the only validation. Always validate on the server-side as well, since client-side validation can be bypassed.

Benefits of Client-Side Validation
  • Immediate feedback: Users know instantly if their input is valid
  • Reduced server load: Invalid data caught before submission
  • Better UX: Faster response times and clearer error messages
  • Guided input: Help users provide correct information
  • Cost-effective: Less bandwidth used for invalid submissions

HTML5 Built-in Validation

HTML5 provides several attributes for basic form validation without JavaScript.

Common HTML5 Validation Attributes
<!-- Required field --> <input type="text" name="username" required> <!-- Email validation --> <input type="email" name="email" required> <!-- Number validation with min/max --> <input type="number" name="age" min="18" max="100" required> <!-- Pattern matching (regex) --> <input type="text" name="zip" pattern="[0-9]{5}" required> <!-- Min and max length --> <input type="text" name="username" minlength="3" maxlength="20" required> <!-- URL validation --> <input type="url" name="website"> <!-- Date constraints --> <input type="date" name="birthdate" min="1900-01-01" max="2010-12-31"> <!-- Custom validation message --> <input type="text" name="code" pattern="[A-Z]{3}[0-9]{3}" title="Format: ABC123 (3 letters, 3 numbers)">
Input Types with Built-in Validation
Validated Input Types
  • email - Email address format
  • url - Valid URL format
  • number - Numeric values
  • tel - Telephone numbers
  • date - Date format
  • time - Time format
  • color - Color picker
Validation Attributes
  • required - Field must have value
  • pattern - Regex pattern match
  • min/max - Numeric constraints
  • minlength/maxlength - String length
  • step - Number increment
  • novalidate - Disable validation
Example: HTML5 Validation Form
<form action="/submit" method="post"> <label>Name (3-50 characters):</label> <input type="text" name="name" minlength="3" maxlength="50" required> <label>Email:</label> <input type="email" name="email" required> <label>Age (18+):</label> <input type="number" name="age" min="18" max="120" required> <label>ZIP Code:</label> <input type="text" name="zip" pattern="[0-9]{5}" title="5-digit ZIP code" required> <button type="submit">Submit</button> </form>
Browser Support

HTML5 validation is well-supported in modern browsers. For older browsers, you'll need JavaScript fallbacks.

JavaScript Form Validation

JavaScript provides more control and customization for form validation.

Basic Form Validation
<form id="registrationForm"> <input type="text" id="username" name="username"> <span id="usernameError" class="error-message"></span> <input type="email" id="email" name="email"> <span id="emailError" class="error-message"></span> <button type="submit">Submit</button> </form> <script> document.getElementById('registrationForm').addEventListener('submit', function(e) { e.preventDefault(); // Prevent form submission // Clear previous errors clearErrors(); // Validate fields let isValid = true; // Validate username const username = document.getElementById('username').value.trim(); if (username === '') { showError('usernameError', 'Username is required'); isValid = false; } else if (username.length < 3) { showError('usernameError', 'Username must be at least 3 characters'); isValid = false; } // Validate email const email = document.getElementById('email').value.trim(); if (email === '') { showError('emailError', 'Email is required'); isValid = false; } else if (!isValidEmail(email)) { showError('emailError', 'Please enter a valid email address'); isValid = false; } // Submit if valid if (isValid) { this.submit(); // or handle with AJAX } }); function showError(elementId, message) { document.getElementById(elementId).textContent = message; } function clearErrors() { const errors = document.querySelectorAll('.error-message'); errors.forEach(error => error.textContent = ''); } function isValidEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } </script>
Constraint Validation API
// Using built-in validation API const input = document.getElementById('email'); // Check validity if (!input.validity.valid) { // Validity states if (input.validity.valueMissing) { console.log('Field is required'); } if (input.validity.typeMismatch) { console.log('Invalid type'); } if (input.validity.patternMismatch) { console.log('Pattern doesn\'t match'); } if (input.validity.tooShort) { console.log('Value too short'); } if (input.validity.tooLong) { console.log('Value too long'); } if (input.validity.rangeUnderflow) { console.log('Value below minimum'); } if (input.validity.rangeOverflow) { console.log('Value above maximum'); } // Get validation message console.log(input.validationMessage); } // Custom validation input.setCustomValidity('This username is already taken'); // Clear custom validation input.setCustomValidity(''); // Check form validity const form = document.getElementById('myForm'); if (form.checkValidity()) { console.log('Form is valid'); }

Common Validation Patterns

Here are validation functions for common input types.

Email Validation
function validateEmail(email) { // Basic email regex const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } // More comprehensive email validation function validateEmailStrict(email) { const regex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; return regex.test(email); }
Password Validation
function validatePassword(password) { // At least 8 characters, 1 uppercase, 1 lowercase, 1 number const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/; return regex.test(password); } // Detailed password strength check function checkPasswordStrength(password) { const checks = { length: password.length >= 8, uppercase: /[A-Z]/.test(password), lowercase: /[a-z]/.test(password), number: /\d/.test(password), special: /[@$!%*?&]/.test(password) }; const passed = Object.values(checks).filter(v => v).length; if (passed === 5) return 'Strong'; if (passed >= 3) return 'Medium'; return 'Weak'; }
Phone Number Validation
// US phone number formats function validateUSPhone(phone) { // Matches: (123) 456-7890, 123-456-7890, 1234567890 const regex = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/; return regex.test(phone); } // International format function validateInternationalPhone(phone) { const regex = /^\+?[1-9]\d{1,14}$/; return regex.test(phone); }
Credit Card Validation
// Luhn algorithm for credit card validation function validateCreditCard(cardNumber) { cardNumber = cardNumber.replace(/\s/g, ''); if (!/^\d+$/.test(cardNumber)) return false; let sum = 0; let isEven = false; for (let i = cardNumber.length - 1; i >= 0; i--) { let digit = parseInt(cardNumber[i]); if (isEven) { digit *= 2; if (digit > 9) digit -= 9; } sum += digit; isEven = !isEven; } return sum % 10 === 0; }
URL Validation
function validateURL(url) { try { new URL(url); return true; } catch (e) { return false; } } // Regex approach function validateURLRegex(url) { const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; return regex.test(url); }

Custom Validation Logic

Create reusable validation functions for complex scenarios.

Validation Class
class FormValidator { constructor(form) { this.form = form; this.errors = {}; } // Add validation rule addRule(fieldName, rules) { const field = this.form.querySelector(`[name="${fieldName}"]`); rules.forEach(rule => { const value = field.value.trim(); if (rule.type === 'required' && !value) { this.addError(fieldName, rule.message || 'This field is required'); } if (rule.type === 'minLength' && value.length < rule.value) { this.addError(fieldName, rule.message || `Minimum length is ${rule.value}`); } if (rule.type === 'maxLength' && value.length > rule.value) { this.addError(fieldName, rule.message || `Maximum length is ${rule.value}`); } if (rule.type === 'pattern' && !rule.value.test(value)) { this.addError(fieldName, rule.message || 'Invalid format'); } if (rule.type === 'custom' && !rule.validator(value)) { this.addError(fieldName, rule.message || 'Invalid value'); } }); } // Add error addError(fieldName, message) { if (!this.errors[fieldName]) { this.errors[fieldName] = []; } this.errors[fieldName].push(message); } // Check if valid isValid() { return Object.keys(this.errors).length === 0; } // Get errors getErrors() { return this.errors; } // Display errors displayErrors() { for (const [fieldName, messages] of Object.entries(this.errors)) { const errorElement = this.form.querySelector(`#${fieldName}Error`); if (errorElement) { errorElement.textContent = messages.join(', '); } } } // Clear errors clearErrors() { this.errors = {}; const errorElements = this.form.querySelectorAll('.error-message'); errorElements.forEach(el => el.textContent = ''); } } // Usage const form = document.getElementById('myForm'); const validator = new FormValidator(form); form.addEventListener('submit', function(e) { e.preventDefault(); validator.clearErrors(); validator.addRule('username', [ { type: 'required', message: 'Username is required' }, { type: 'minLength', value: 3, message: 'Username too short' } ]); validator.addRule('email', [ { type: 'required' }, { type: 'pattern', value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: 'Invalid email' } ]); validator.addRule('age', [ { type: 'custom', validator: (val) => val >= 18, message: 'Must be 18 or older' } ]); if (validator.isValid()) { console.log('Form is valid!'); // Submit form } else { validator.displayErrors(); } });

Real-time Validation

Provide instant feedback as users type for better user experience.

Live Validation on Input
// Validate on input event const emailInput = document.getElementById('email'); const emailError = document.getElementById('emailError'); emailInput.addEventListener('input', function() { const email = this.value.trim(); if (email === '') { emailError.textContent = ''; this.classList.remove('is-valid', 'is-invalid'); } else if (validateEmail(email)) { emailError.textContent = ''; this.classList.remove('is-invalid'); this.classList.add('is-valid'); } else { emailError.textContent = 'Invalid email format'; this.classList.remove('is-valid'); this.classList.add('is-invalid'); } }); // Debounced validation (wait for user to stop typing) function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; } const debouncedValidation = debounce(function(value) { // Perform validation or AJAX check console.log('Validating:', value); }, 500); emailInput.addEventListener('input', function() { debouncedValidation(this.value); });
Password Strength Indicator
<input type="password" id="password"> <div id="strengthBar"></div> <div id="strengthText"></div> <script> const passwordInput = document.getElementById('password'); const strengthBar = document.getElementById('strengthBar'); const strengthText = document.getElementById('strengthText'); passwordInput.addEventListener('input', function() { const password = this.value; const strength = calculatePasswordStrength(password); strengthBar.style.width = strength.percentage + '%'; strengthBar.style.backgroundColor = strength.color; strengthText.textContent = strength.text; }); function calculatePasswordStrength(password) { let score = 0; if (password.length >= 8) score++; if (password.length >= 12) score++; if (/[a-z]/.test(password)) score++; if (/[A-Z]/.test(password)) score++; if (/\d/.test(password)) score++; if (/[@$!%*?&]/.test(password)) score++; if (score === 0) return { percentage: 0, color: '#ccc', text: '' }; if (score <= 2) return { percentage: 33, color: '#f44336', text: 'Weak' }; if (score <= 4) return { percentage: 66, color: '#ff9800', text: 'Medium' }; return { percentage: 100, color: '#4caf50', text: 'Strong' }; } </script> <style> #strengthBar { height: 5px; transition: width 0.3s, background-color 0.3s; } </style>
Async Username Check
const usernameInput = document.getElementById('username'); const usernameStatus = document.getElementById('usernameStatus'); usernameInput.addEventListener('input', debounce(async function() { const username = this.value.trim(); if (username.length < 3) { usernameStatus.textContent = ''; return; } usernameStatus.textContent = 'Checking...'; try { const response = await fetch(`/api/check-username?username=${username}`); const data = await response.json(); if (data.available) { usernameStatus.textContent = '✓ Available'; usernameStatus.style.color = 'green'; } else { usernameStatus.textContent = '✗ Already taken'; usernameStatus.style.color = 'red'; } } catch (error) { usernameStatus.textContent = 'Error checking availability'; } }, 500));

Complete Form Example

<!DOCTYPE html> <html> <head> <style> .form-group { margin-bottom: 15px; } .form-control { width: 100%; padding: 8px; border: 1px solid #ddd; } .form-control.is-valid { border-color: #28a745; } .form-control.is-invalid { border-color: #dc3545; } .error-message { color: #dc3545; font-size: 14px; } .success-message { color: #28a745; font-size: 14px; } </style> </head> <body> <form id="registrationForm" novalidate> <div class="form-group"> <label>Full Name:</label> <input type="text" class="form-control" id="fullName" required> <span class="error-message" id="fullNameError"></span> </div> <div class="form-group"> <label>Email:</label> <input type="email" class="form-control" id="email" required> <span class="error-message" id="emailError"></span> </div> <div class="form-group"> <label>Password:</label> <input type="password" class="form-control" id="password" required> <span class="error-message" id="passwordError"></span> </div> <div class="form-group"> <label>Confirm Password:</label> <input type="password" class="form-control" id="confirmPassword" required> <span class="error-message" id="confirmPasswordError"></span> </div> <button type="submit">Register</button> </form> <script> const form = document.getElementById('registrationForm'); // Real-time validation document.querySelectorAll('.form-control').forEach(input => { input.addEventListener('blur', () => validateField(input)); input.addEventListener('input', () => clearError(input)); }); // Form submission form.addEventListener('submit', function(e) { e.preventDefault(); let isValid = true; document.querySelectorAll('.form-control').forEach(input => { if (!validateField(input)) { isValid = false; } }); if (isValid) { alert('Form is valid! Submitting...'); // this.submit(); } }); function validateField(input) { const value = input.value.trim(); const errorSpan = document.getElementById(input.id + 'Error'); // Clear previous state input.classList.remove('is-valid', 'is-invalid'); errorSpan.textContent = ''; // Validation logic if (input.id === 'fullName') { if (value === '') { showError(input, 'Name is required'); return false; } else if (value.length < 3) { showError(input, 'Name must be at least 3 characters'); return false; } } if (input.id === 'email') { if (value === '') { showError(input, 'Email is required'); return false; } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { showError(input, 'Invalid email format'); return false; } } if (input.id === 'password') { if (value === '') { showError(input, 'Password is required'); return false; } else if (value.length < 8) { showError(input, 'Password must be at least 8 characters'); return false; } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { showError(input, 'Password must contain uppercase, lowercase, and number'); return false; } } if (input.id === 'confirmPassword') { const password = document.getElementById('password').value; if (value === '') { showError(input, 'Please confirm password'); return false; } else if (value !== password) { showError(input, 'Passwords do not match'); return false; } } // Valid input.classList.add('is-valid'); return true; } function showError(input, message) { input.classList.add('is-invalid'); document.getElementById(input.id + 'Error').textContent = message; } function clearError(input) { input.classList.remove('is-invalid'); document.getElementById(input.id + 'Error').textContent = ''; } </script> </body> </html>

Session Summary

Key Points
  • HTML5 Validation: Built-in attributes like required, pattern, min/max for basic validation
  • JavaScript Validation: Custom logic for complex validation scenarios
  • Constraint Validation API: Access browser validation state and messages
  • Common Patterns: Ready-to-use validation for email, passwords, phone numbers
  • Real-time Feedback: Validate as users type for better UX
  • Custom Messages: Provide clear, helpful error messages
  • Security: Always validate on server-side as well
Best Practices
  • Validate both client-side and server-side
  • Provide immediate, specific feedback
  • Use appropriate input types and attributes
  • Debounce validation for real-time checks
  • Show validation state visually (colors, icons)
  • Test validation with various inputs including edge cases
Next Session Preview

In the next session, we'll explore AJAX, XML, and JSON, learning how to create dynamic web applications that communicate with servers asynchronously without page reloads.