Files
Gyannetra_website/js/form-handler.js

364 lines
11 KiB
JavaScript

/**
* Enhanced Contact Form Handler
* Handles form validation, submission, and user feedback
*/
class ContactFormHandler {
constructor() {
this.form = document.getElementById('contact-form');
this.submitBtn = document.querySelector('.btn-send-message');
this.messageDiv = document.getElementById('form-message');
this.init();
}
init() {
if (!this.form) return;
this.setupFormListeners();
this.setupRealTimeValidation();
this.setupAccessibility();
}
setupFormListeners() {
this.form.addEventListener('submit', this.handleSubmit.bind(this));
// Add input event listeners for real-time validation
const inputs = this.form.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
input.addEventListener('blur', this.validateField.bind(this));
input.addEventListener('input', this.clearFieldError.bind(this));
});
}
setupRealTimeValidation() {
// Email validation as user types
const emailInput = document.getElementById('your-email');
if (emailInput) {
emailInput.addEventListener('input', (e) => {
this.debounce(() => this.validateEmail(e.target), 500)();
});
}
// Name validation
const nameInput = document.getElementById('your-name');
if (nameInput) {
nameInput.addEventListener('input', (e) => {
this.validateName(e.target);
});
}
}
setupAccessibility() {
// Add ARIA labels and improve accessibility
const inputs = this.form.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
const label = this.form.querySelector(`label[for="${input.id}"]`);
if (label) {
input.setAttribute('aria-labelledby', label.id || `${input.id}-label`);
if (!label.id) label.id = `${input.id}-label`;
}
});
}
async handleSubmit(e) {
e.preventDefault();
// Validate all fields
if (!this.validateForm()) {
this.showMessage('Please fix the errors below before submitting.', 'error');
return;
}
// Show loading state
this.setLoadingState(true);
try {
// Simulate form submission (replace with actual endpoint)
await this.submitForm();
this.showMessage('Thank you for your message! We\'ll get back to you soon.', 'success');
this.resetForm();
} catch (error) {
console.error('Form submission error:', error);
this.showMessage('Sorry, there was an error sending your message. Please try again.', 'error');
} finally {
this.setLoadingState(false);
}
}
async submitForm() {
// Simulate API call - replace with actual submission logic
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true });
}, 2000);
});
}
validateForm() {
const inputs = this.form.querySelectorAll('input[required], select[required], textarea[required]');
let isValid = true;
inputs.forEach(input => {
if (!this.validateField({ target: input })) {
isValid = false;
}
});
return isValid;
}
validateField(e) {
const field = e.target;
const value = field.value.trim();
const fieldGroup = field.closest('.form-group');
// Clear previous validation state
this.clearFieldError(e);
let isValid = true;
let errorMessage = '';
// Required field validation
if (field.hasAttribute('required') && !value) {
errorMessage = 'This field is required.';
isValid = false;
}
// Email validation
else if (field.type === 'email' && value && !this.isValidEmail(value)) {
errorMessage = 'Please enter a valid email address.';
isValid = false;
}
// Name validation
else if (field.id === 'your-name' && value && value.length < 2) {
errorMessage = 'Name must be at least 2 characters long.';
isValid = false;
}
// Message validation
else if (field.id === 'your-message' && value && value.length < 10) {
errorMessage = 'Message must be at least 10 characters long.';
isValid = false;
}
if (!isValid) {
this.showFieldError(fieldGroup, errorMessage);
} else if (value) {
this.showFieldSuccess(fieldGroup);
}
return isValid;
}
validateEmail(emailField) {
const value = emailField.value.trim();
const fieldGroup = emailField.closest('.form-group');
if (value && this.isValidEmail(value)) {
this.showFieldSuccess(fieldGroup);
} else if (value) {
this.showFieldError(fieldGroup, 'Please enter a valid email address.');
}
}
validateName(nameField) {
const value = nameField.value.trim();
const fieldGroup = nameField.closest('.form-group');
if (value && value.length >= 2) {
this.showFieldSuccess(fieldGroup);
}
}
clearFieldError(e) {
const fieldGroup = e.target.closest('.form-group');
fieldGroup.classList.remove('error', 'success');
// Remove any existing error message
const existingError = fieldGroup.querySelector('.field-error');
if (existingError) {
existingError.remove();
}
}
showFieldError(fieldGroup, message) {
fieldGroup.classList.add('error');
fieldGroup.classList.remove('success');
// Add error message
let errorElement = fieldGroup.querySelector('.field-error');
if (!errorElement) {
errorElement = document.createElement('div');
errorElement.className = 'field-error';
fieldGroup.appendChild(errorElement);
}
errorElement.textContent = message;
}
showFieldSuccess(fieldGroup) {
fieldGroup.classList.add('success');
fieldGroup.classList.remove('error');
// Remove any existing error message
const existingError = fieldGroup.querySelector('.field-error');
if (existingError) {
existingError.remove();
}
}
setLoadingState(isLoading) {
if (isLoading) {
this.submitBtn.classList.add('loading');
this.submitBtn.disabled = true;
} else {
this.submitBtn.classList.remove('loading');
this.submitBtn.disabled = false;
}
}
showMessage(message, type) {
this.messageDiv.textContent = message;
this.messageDiv.className = `form-message ${type}`;
this.messageDiv.style.display = 'block';
// Auto-hide success messages after 5 seconds
if (type === 'success') {
setTimeout(() => {
this.messageDiv.style.display = 'none';
}, 5000);
}
// Scroll to message
this.messageDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
resetForm() {
this.form.reset();
// Clear all validation states
const fieldGroups = this.form.querySelectorAll('.form-group');
fieldGroups.forEach(group => {
group.classList.remove('error', 'success');
const errorElement = group.querySelector('.field-error');
if (errorElement) {
errorElement.remove();
}
});
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
}
// Form Enhancement Utilities
class FormEnhancements {
static addFloatingLabels() {
const formGroups = document.querySelectorAll('.form-group');
formGroups.forEach(group => {
const input = group.querySelector('input, textarea');
const label = group.querySelector('label');
if (input && label && input.type !== 'select') {
group.classList.add('floating-label');
// Move label after input for floating effect
if (input.nextElementSibling !== label) {
input.parentNode.insertBefore(label, input.nextSibling);
}
}
});
}
static addFormAnimations() {
// Add staggered animation to form fields
const formGroups = document.querySelectorAll('.form-group');
formGroups.forEach((group, index) => {
group.style.animationDelay = `${index * 0.1}s`;
group.classList.add('fade-in-up');
});
}
static enhanceSelectBoxes() {
const selects = document.querySelectorAll('select');
selects.forEach(select => {
// Add custom styling hook
select.addEventListener('change', (e) => {
if (e.target.value) {
e.target.classList.add('has-value');
} else {
e.target.classList.remove('has-value');
}
});
});
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
// Initialize main form handler
new ContactFormHandler();
// Add form enhancements
FormEnhancements.addFloatingLabels();
FormEnhancements.addFormAnimations();
FormEnhancements.enhanceSelectBoxes();
});
// Additional CSS for error messages and animations
function injectFormStyles() {
if (document.querySelector('#form-enhancement-styles')) return;
const styleElement = document.createElement('style');
styleElement.id = 'form-enhancement-styles';
styleElement.textContent = `
.field-error {
color: #ef4444;
font-size: 0.875rem;
margin-top: 0.5rem;
font-weight: 100;
animation: shake 0.3s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.fade-in-up {
animation: fadeInUp 0.6s ease-out forwards;
opacity: 0;
transform: translateY(20px);
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
.form-group.has-value input,
.form-group.has-value textarea {
border-color: rgba(82, 238, 245, 0.6);
}
`;
document.head.appendChild(styleElement);
}
// Inject styles when DOM is ready
injectFormStyles();