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.