Now.js Framework Documentation
FormError
FormError
Overview
FormError is a comprehensive error management system for forms in the Now.js Framework. It provides a complete set of tools for displaying, managing, and clearing validation errors and success messages in forms. The system supports both field-level and form-level error handling with automatic focus management, scrolling, accessibility features, and configurable auto-clear functionality.
Key Features
- Field-level Error Display - Show validation errors next to specific form fields
- General Message Display - Display form-level errors or success messages
- Automatic Focus Management - Automatically focus on the first error field
- Smart Scrolling - Scroll to error fields with configurable offset
- Accessibility Support - ARIA attributes for screen readers (aria-invalid, aria-errormessage)
- Auto-clear Messages - Automatically clear messages after a specified delay
- Original Content Restoration - Preserve and restore original container content
- Multiple Configuration Levels - Global config, form-specific config
- Event System Integration - Emit events for all error operations
- Flexible Container Detection - Auto-detect or specify error/success containers
Use Cases
- Form Validation - Display inline validation errors for form fields
- Server Response Errors - Show error messages from API responses
- Success Notifications - Display success messages after form submission
- Multi-step Forms - Manage errors across multiple form steps
- Real-time Validation - Show/clear errors as user types or blurs fields
- Accessibility Compliance - Ensure forms are accessible with proper ARIA attributes
Installation and Import
FormError is loaded with the Now.js Framework and is ready to use immediately via the window object:
// No import needed - ready to use immediately
console.log(window.FormError); // FormError objectQuick Start
Basic Field Error Display
<!-- Form with error containers -->
<form id="loginForm">
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<div id="result_email" class="comment"></div>
</div>
<div class="form-control">
<label for="password">Password</label>
<input type="password" id="password" name="password">
<div id="result_password" class="comment"></div>
</div>
<div id="form-message" class="form-message"></div>
<button type="submit">Login</button>
</form>
<script>
// Show field error
FormError.showFieldError('email', 'Please enter a valid email address');
// Show general error
FormError.showGeneralError('Invalid login credentials', document.getElementById('loginForm'));
// Clear specific field error
FormError.clearFieldError('email');
// Clear all errors
FormError.clearAll();
</script>Success Message Display
<script>
// Show success message
FormError.showSuccess('Login successful! Redirecting...', document.getElementById('loginForm'));
// Auto-clear after 3 seconds
setTimeout(() => {
FormError.clearSuccess(document.getElementById('loginForm'));
}, 3000);
</script>Configuration
Global Configuration
Configure default behavior for all forms:
FormError.configure({
// CSS class for invalid fields (default: 'invalid')
errorClass: 'has-error',
// CSS class for error messages (default: 'error')
errorMessageClass: 'error-text',
// Auto-focus first error field (default: true)
autoFocus: true,
// Auto-scroll to first error field (default: true)
autoScroll: true,
// Scroll offset in pixels (default: 100)
scrollOffset: 120,
// Enable debug mode (default: false)
debug: true,
// Show errors inline next to fields (default: true)
showErrorsInline: true,
// Show errors in notification system (default: false)
showErrorsInNotification: false,
// Auto-clear errors after delay (default: true)
autoClearErrors: true,
// Auto-clear delay in milliseconds (default: 5000)
autoClearErrorsDelay: 3000,
// Default error container ID (default: 'form-message')
defaultErrorContainer: 'global-error',
// Default success container ID (default: 'form-message')
defaultSuccessContainer: 'global-success'
});Form-specific Configuration
FormError automatically inherits configuration from FormManager instances:
<form
data-form="registrationForm"
data-error-container="registration-errors"
data-success-container="registration-success"
data-auto-focus="true"
data-auto-scroll="false">
<!-- form fields -->
</form>
<script>
// FormError will use form-specific config when showing errors
const form = document.querySelector('[data-form="registrationForm"]');
// Uses form's error-container setting
FormError.showGeneralError('Registration failed', form);
</script>Configuration Priority
- Form-specific config (from FormManager instance)
- Global config (from
FormError.config) - Default config (hardcoded defaults)
Field Error Management
Show Field Error
Display validation error for a specific field:
// By field ID
FormError.showFieldError('email', 'Email is required');
// By field name
FormError.showFieldError('username', 'Username must be at least 6 characters');
// Multiple error messages (shows first)
FormError.showFieldError('password', [
'Password is required',
'Password must be at least 8 characters',
'Password must contain a number'
]);
// With form context
const form = document.getElementById('myForm');
FormError.showFieldError('email', 'Invalid email', form);
// With custom options
FormError.showFieldError('email', 'Invalid email', form, {
focus: false, // Don't auto-focus
scroll: false // Don't auto-scroll
});Error Container Structure
FormError expects this HTML structure for field errors:
<div class="form-control">
<label for="fieldName">Field Label</label>
<input type="text" id="fieldName" name="fieldName">
<!-- Error message container (must have id="result_{fieldName}") -->
<div id="result_fieldName" class="comment"></div>
</div>What Happens When Showing Field Error
- Locate Field - Find element by ID or name attribute
- Store Original State - Save original message content if exists
- Add Error Classes - Add
errorClassto field, parent.form-control, and field container - Display Message - Show error message in
result_{fieldName}container - Set ARIA Attributes - Add
aria-invalid="true"andaria-errormessage - Focus Management - Focus first error field (if
autoFocusenabled) - Scroll to Field - Scroll to error field (if
autoScrollenabled) - Emit Event - Fire
form:errorevent with error details
Clear Field Error
Remove validation error from a specific field:
// By field ID or name
FormError.clearFieldError('email');
// By field element
const emailField = document.getElementById('email');
FormError.clearFieldError(emailField);What Happens When Clearing Field Error
- Remove Error Classes - Remove
errorClassfrom field, parent, and container - Restore Original Message - Restore original content of
result_{fieldName}container - Remove ARIA Attributes - Remove
aria-invalidandaria-errormessage - Clear State - Remove from internal errors map
- Emit Event - Fire
form:clearErrorevent
General Error Messages
Show General Error
Display form-level error message:
// With form element
const form = document.getElementById('loginForm');
FormError.showGeneralError('Invalid login credentials', form);
// With container ID
FormError.showGeneralError('Server error occurred', 'error-container');
// Without form (uses default container)
FormError.showGeneralError('An unexpected error occurred');
// With auto-clear
FormError.showGeneralError('Please fix the errors below', form);
// Auto-clears after 5 seconds (if autoClearErrors is enabled)Container Detection Priority
FormError looks for error containers in this order:
-
Form's
data-error-containerattribute<form data-error-container="my-errors"> -
Element with
[data-error-container]inside form<div data-error-container class="error-box"></div> -
Element with
.form-messageclass<div class="form-message"></div> -
Element with
.error-messageclass<div class="error-message"></div> -
Element with
.login-messageclass<div class="login-message"></div> -
Default error container (from config)
FormError.config.defaultErrorContainer // 'form-message'
Original Content Preservation
FormError preserves original container content:
<div id="form-message" class="form-message">
<p>Welcome! Please log in to continue.</p>
</div>
<script>
// Show error - original content saved
FormError.showGeneralError('Login failed', document.getElementById('loginForm'));
// Clear error - original content restored
FormError.clearGeneralError('form-message');
// Now shows "Welcome! Please log in to continue." again
</script>Clear General Error
Remove form-level error message:
// By container ID
FormError.clearGeneralError('form-message');
// By form element
const form = document.getElementById('loginForm');
FormError.clearGeneralError(form);
// By container element
const container = document.getElementById('error-container');
FormError.clearGeneralError(container);
// Clear default container
FormError.clearGeneralError();Success Messages
Show Success Message
Display success notification:
// With form element
const form = document.getElementById('loginForm');
FormError.showSuccess('Login successful!', form);
// With container ID
FormError.showSuccess('Profile updated successfully', 'success-container');
// Without form (uses default container)
FormError.showSuccess('Changes saved');
// With auto-clear
FormError.showSuccess('Registration complete', form);
// Auto-clears after 5 seconds (if autoClearErrors is enabled)Success Container Detection
Similar to error containers, but looks for:
- Form's
data-success-containerattribute - Element with
[data-success-container]inside form - Element with
.form-messageclass - Element with
.success-messageclass - Element with
.login-messageclass - Default success container (from config)
Clear Success Message
Remove success notification:
// By container ID
FormError.clearSuccess('success-container');
// By form element
const form = document.getElementById('loginForm');
FormError.clearSuccess(form);
// By container element
const container = document.getElementById('success-container');
FormError.clearSuccess(container);
// Clear default container
FormError.clearSuccess();Bulk Operations
Show Multiple Errors
Display multiple field errors at once:
const errors = {
email: 'Email is required',
password: 'Password must be at least 8 characters',
username: 'Username is already taken'
};
// Show all errors (clears existing errors first)
FormError.showErrors(errors);
// First error field gets focus and scroll
// Other errors are shown inline
// With custom options
FormError.showErrors(errors, {
focus: false, // Don't focus any field
scroll: false // Don't scroll
});Clear All Messages
Remove all errors and success messages:
// Clear all errors in document
FormError.clearAll();
// Clear all errors in specific form
const form = document.getElementById('loginForm');
FormError.clearAll(form);Clear Form-specific Messages
Clear all messages for a specific form:
// By form element
const form = document.getElementById('registrationForm');
FormError.clearFormMessages(form);
// By form ID
FormError.clearFormMessages('registrationForm');
// By form data-form attribute
FormError.clearFormMessages('myForm');Focus and Scroll Management
Automatic Focus
First error field receives focus automatically:
FormError.showFieldError('email', 'Email is required');
FormError.showFieldError('password', 'Password is required');
// Email field gets focus (first error)Disable Auto-focus
// Globally
FormError.configure({ autoFocus: false });
// Per operation
FormError.showFieldError('email', 'Error', null, { focus: false });Automatic Scrolling
Smart scroll behavior:
// Scrolls to element only if not visible
FormError.scrollToElement(element);
// Element is considered visible if:
// - 30% of its height is visible in viewport
// - Either top or bottom is in viewportScroll Configuration
FormError.configure({
autoScroll: true, // Enable auto-scroll
scrollOffset: 100 // Offset from top in pixels
});Manual Scroll
const element = document.getElementById('email');
FormError.scrollToElement(element);Accessibility Features
ARIA Attributes
FormError automatically manages ARIA attributes:
<!-- Before error -->
<input type="email" id="email" name="email">
<!-- After showing error -->
<input
type="email"
id="email"
name="email"
aria-invalid="true"
aria-errormessage="result_email">
<div id="result_email" role="alert">Email is required</div>Screen Reader Support
// Error messages are announced by screen readers
FormError.showFieldError('email', 'Email is required');
// aria-live regions for dynamic messages
// Error containers should have role="alert" for immediate announcementKeyboard Navigation
// First error field receives focus for keyboard users
FormError.showErrors({
email: 'Email is required',
password: 'Password is required'
});
// Email field is focused, user can tab to password fieldState Management
Check for Errors
// Check if any errors exist
if (FormError.hasErrors()) {
console.log('Form has errors');
}
// Get number of errors
const count = FormError.getErrorsCount();
console.log(`${count} error(s) found`);Get Error Details
// Get all current errors
const errors = FormError.getErrors();
// Returns: [
// { field: 'email', element: <input>, messages: ['Email is required'] },
// { field: 'password', element: <input>, messages: ['Password too short'] }
// ]
// Process errors
errors.forEach(error => {
console.log(`Field: ${error.field}`);
console.log(`Messages: ${error.messages.join(', ')}`);
console.log(`Element:`, error.element);
});Reset State
Clear all errors and reset internal state:
FormError.reset();
// Clears:
// - All field errors
// - All general messages
// - Original message cache
// - Last focused element
// - Internal state mapsEvent System
FormError emits events for all operations:
Available Events
// Field error shown
Now.on('form:error', (data) => {
console.log('Field error:', data.field, data.messages);
console.log('Element:', data.element);
console.log('Config:', data.config);
});
// Field error cleared
Now.on('form:clearError', (data) => {
console.log('Error cleared:', data.field);
console.log('Element:', data.element);
});
// Multiple errors shown
Now.on('form:errors', (data) => {
console.log('Errors:', data.errors);
});
// General error shown
Now.on('form:generalError', (data) => {
console.log('General error:', data.message);
console.log('Container:', data.containerId);
});
// General error cleared
Now.on('form:clearGeneralError', (data) => {
console.log('General error cleared:', data.containerId);
});
// Success message shown
Now.on('form:generalSuccess', (data) => {
console.log('Success:', data.message);
console.log('Container:', data.containerId);
});
// Success message cleared
Now.on('form:clearSuccess', (data) => {
console.log('Success cleared:', data.containerId);
});
// All errors cleared
Now.on('form:clearAllErrors', (data) => {
console.log('All errors cleared');
if (data.form) {
console.log('Form:', data.form);
}
});Event Usage Example
// Track errors for analytics
Now.on('form:error', (data) => {
analytics.track('Form Error', {
field: data.field,
message: data.messages[0],
formId: data.element.form?.id
});
});
// Show custom notifications
Now.on('form:generalError', (data) => {
if (data.config.showErrorsInNotification) {
NotificationSystem.error(data.message);
}
});
// Log error clearing
Now.on('form:clearAllErrors', () => {
console.log('User cleared all form errors');
});Integration with FormManager
FormError works seamlessly with FormManager:
Automatic Error Display
// FormManager automatically uses FormError for validation errors
const form = document.getElementById('loginForm');
const formManager = FormManager.init(form);
// When validation fails, FormError displays errors automatically
formManager.validate(); // Validation errors shown via FormErrorConfiguration Inheritance
<form
data-form="myForm"
data-error-class="has-error"
data-error-message-class="error-text"
data-auto-focus="true"
data-auto-scroll="false"
data-error-container="form-errors">
<!-- FormError uses these settings -->
</form>Manual Error Handling
// Show custom errors after form submission
Now.on('form:submit', async (data) => {
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data.formData)
});
const result = await response.json();
if (!result.success && result.errors) {
// Show server validation errors
FormError.showErrors(result.errors);
}
} catch (error) {
FormError.showGeneralError('Server error occurred', data.form);
}
});Complete Examples
Example 1: Login Form with Error Handling
<!DOCTYPE html>
<html>
<head>
<title>Login Form</title>
<script src="/Now/Now.js"></script>
<style>
.form-control { margin-bottom: 1rem; }
.form-control.invalid input { border-color: #dc3545; }
.comment { min-height: 1.5rem; font-size: 0.875rem; }
.comment.error { color: #dc3545; }
.form-message { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
.form-message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.form-message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
</style>
</head>
<body>
<form id="loginForm" data-form="loginForm">
<div id="form-message" class="form-message"></div>
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<div id="result_email" class="comment"></div>
</div>
<div class="form-control">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
<div id="result_password" class="comment"></div>
</div>
<button type="submit">Login</button>
</form>
<script>
// Configure FormError
FormError.configure({
errorClass: 'invalid',
autoFocus: true,
autoScroll: true,
autoClearErrors: false // Manual clearing for login forms
});
// Initialize form
const form = document.getElementById('loginForm');
// Handle form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Clear previous errors
FormError.clearAll(form);
// Get form data
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// Client-side validation
const errors = {};
if (!data.email) {
errors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.email = 'Please enter a valid email address';
}
if (!data.password) {
errors.password = 'Password is required';
} else if (data.password.length < 8) {
errors.password = 'Password must be at least 8 characters';
}
// Show validation errors
if (Object.keys(errors).length > 0) {
FormError.showErrors(errors);
FormError.showGeneralError('Please fix the errors below', form);
return;
}
// Submit to server
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
FormError.showSuccess('Login successful! Redirecting...', form);
setTimeout(() => {
window.location.href = result.redirect || '/dashboard';
}, 1500);
} else {
// Show server errors
if (result.errors) {
FormError.showErrors(result.errors);
}
FormError.showGeneralError(result.message || 'Login failed', form);
}
} catch (error) {
FormError.showGeneralError('Network error. Please try again.', form);
}
});
// Clear field error on input
form.querySelectorAll('input').forEach(input => {
input.addEventListener('input', () => {
if (FormError.state.errors.has(input.name)) {
FormError.clearFieldError(input.name);
}
});
});
</script>
</body>
</html>Example 2: Registration Form with Multi-field Validation
<!DOCTYPE html>
<html>
<head>
<title>Registration Form</title>
<script src="/Now/Now.js"></script>
<style>
.form-control { margin-bottom: 1rem; }
.form-control.invalid input { border-color: #dc3545; background-color: #fff5f5; }
.comment { min-height: 1.5rem; font-size: 0.875rem; }
.comment.error { color: #dc3545; }
.form-message { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; display: none; }
.form-message.show { display: block; }
.form-message.error { background: #f8d7da; color: #721c24; }
.form-message.success { background: #d4edda; color: #155724; }
</style>
</head>
<body>
<form id="registrationForm" data-form="registrationForm">
<div id="form-message" class="form-message"></div>
<div class="form-control">
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
<div id="result_username" class="comment"></div>
</div>
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<div id="result_email" class="comment"></div>
</div>
<div class="form-control">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
<div id="result_password" class="comment"></div>
</div>
<div class="form-control">
<label for="confirmPassword">Confirm Password</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
<div id="result_confirmPassword" class="comment"></div>
</div>
<div class="form-control">
<label>
<input type="checkbox" id="terms" name="terms" required>
I agree to the terms and conditions
</label>
<div id="result_terms" class="comment"></div>
</div>
<button type="submit">Register</button>
</form>
<script>
// Configure FormError
FormError.configure({
errorClass: 'invalid',
errorMessageClass: 'error',
autoFocus: true,
autoScroll: true,
scrollOffset: 80,
autoClearErrors: false
});
const form = document.getElementById('registrationForm');
// Validation rules
const validationRules = {
username: {
validate: (value) => {
if (!value) return 'Username is required';
if (value.length < 4) return 'Username must be at least 4 characters';
if (!/^[a-zA-Z0-9_]+$/.test(value)) return 'Username can only contain letters, numbers, and underscores';
return null;
}
},
email: {
validate: (value) => {
if (!value) return 'Email is required';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Please enter a valid email address';
return null;
}
},
password: {
validate: (value) => {
if (!value) return 'Password is required';
if (value.length < 8) return 'Password must be at least 8 characters';
if (!/[A-Z]/.test(value)) return 'Password must contain at least one uppercase letter';
if (!/[a-z]/.test(value)) return 'Password must contain at least one lowercase letter';
if (!/[0-9]/.test(value)) return 'Password must contain at least one number';
return null;
}
},
confirmPassword: {
validate: (value, formData) => {
if (!value) return 'Please confirm your password';
if (value !== formData.password) return 'Passwords do not match';
return null;
}
},
terms: {
validate: (value) => {
if (!value) return 'You must agree to the terms and conditions';
return null;
}
}
};
// Real-time validation on blur
form.querySelectorAll('input').forEach(input => {
input.addEventListener('blur', () => {
validateField(input.name);
});
// Clear error on input
input.addEventListener('input', () => {
if (FormError.state.errors.has(input.name)) {
FormError.clearFieldError(input.name);
}
});
});
// Validate single field
function validateField(fieldName) {
const rule = validationRules[fieldName];
if (!rule) return true;
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const value = data[fieldName];
const error = rule.validate(value, data);
if (error) {
FormError.showFieldError(fieldName, error, form, {
focus: false,
scroll: false
});
return false;
} else {
FormError.clearFieldError(fieldName);
return true;
}
}
// Validate all fields
function validateForm() {
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const errors = {};
Object.keys(validationRules).forEach(fieldName => {
const rule = validationRules[fieldName];
const value = data[fieldName];
const error = rule.validate(value, data);
if (error) {
errors[fieldName] = error;
}
});
return errors;
}
// Handle form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Clear previous errors
FormError.clearAll(form);
// Validate all fields
const errors = validateForm();
if (Object.keys(errors).length > 0) {
FormError.showErrors(errors);
FormError.showGeneralError(`Please fix ${Object.keys(errors).length} error(s) below`, form);
return;
}
// Submit to server
const formData = new FormData(form);
const data = Object.fromEntries(formData);
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
FormError.showSuccess('Registration successful! Please check your email.', form);
form.reset();
setTimeout(() => {
window.location.href = '/login';
}, 2000);
} else {
// Show server validation errors
if (result.errors) {
FormError.showErrors(result.errors);
}
FormError.showGeneralError(result.message || 'Registration failed', form);
}
} catch (error) {
FormError.showGeneralError('Network error. Please try again.', form);
}
});
</script>
</body>
</html>Example 3: Contact Form with Auto-clear Messages
<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
<script src="/Now/Now.js"></script>
<style>
.form-control { margin-bottom: 1rem; }
.form-control.invalid input,
.form-control.invalid textarea { border-color: #dc3545; }
.comment { min-height: 1.5rem; font-size: 0.875rem; }
.comment.error { color: #dc3545; }
#form-message {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 4px;
display: none;
animation: slideDown 0.3s ease-out;
}
#form-message.show { display: block; }
#form-message.error { background: #f8d7da; color: #721c24; }
#form-message.success { background: #d4edda; color: #155724; }
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<form id="contactForm" data-form="contactForm">
<div id="form-message" class="form-message"></div>
<div class="form-control">
<label for="name">Name</label>
<input type="text" id="name" name="name" required>
<div id="result_name" class="comment"></div>
</div>
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<div id="result_email" class="comment"></div>
</div>
<div class="form-control">
<label for="subject">Subject</label>
<input type="text" id="subject" name="subject" required>
<div id="result_subject" class="comment"></div>
</div>
<div class="form-control">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<div id="result_message" class="comment"></div>
</div>
<button type="submit">Send Message</button>
</form>
<script>
// Configure with auto-clear
FormError.configure({
errorClass: 'invalid',
errorMessageClass: 'error',
autoFocus: true,
autoScroll: true,
autoClearErrors: true,
autoClearErrorsDelay: 4000 // Clear after 4 seconds
});
const form = document.getElementById('contactForm');
// Add 'show' class when message is displayed
Now.on('form:generalError', (data) => {
document.getElementById(data.containerId).classList.add('show');
});
Now.on('form:generalSuccess', (data) => {
document.getElementById(data.containerId).classList.add('show');
});
// Handle form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Clear previous messages
FormError.clearAll(form);
// Get form data
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// Simple validation
const errors = {};
if (!data.name) errors.name = 'Name is required';
if (!data.email) errors.email = 'Email is required';
if (!data.subject) errors.subject = 'Subject is required';
if (!data.message) errors.message = 'Message is required';
if (Object.keys(errors).length > 0) {
FormError.showErrors(errors);
FormError.showGeneralError('Please fill in all required fields', form);
return;
}
// Simulate API call
try {
// Show loading state
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
await new Promise(resolve => setTimeout(resolve, 1500));
// Simulate success
FormError.showSuccess('Thank you! Your message has been sent successfully.', form);
form.reset();
// Messages auto-clear after 4 seconds (autoClearErrorsDelay)
// Reset button
submitBtn.disabled = false;
submitBtn.textContent = 'Send Message';
} catch (error) {
FormError.showGeneralError('Failed to send message. Please try again.', form);
// Reset button
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.disabled = false;
submitBtn.textContent = 'Send Message';
}
});
// Clear field errors on input
form.querySelectorAll('input, textarea').forEach(field => {
field.addEventListener('input', () => {
if (FormError.state.errors.has(field.name)) {
FormError.clearFieldError(field.name);
}
});
});
</script>
</body>
</html>Example 4: Multi-step Form with Error Persistence
<!DOCTYPE html>
<html>
<head>
<title>Multi-step Form</title>
<script src="/Now/Now.js"></script>
<style>
.form-step { display: none; }
.form-step.active { display: block; }
.form-control { margin-bottom: 1rem; }
.form-control.invalid input { border-color: #dc3545; }
.comment { min-height: 1.5rem; font-size: 0.875rem; }
.comment.error { color: #dc3545; }
.form-message { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
.form-message.error { background: #f8d7da; color: #721c24; }
.form-message.success { background: #d4edda; color: #155724; }
.form-nav { margin-top: 1rem; display: flex; gap: 1rem; }
.step-indicator { display: flex; gap: 1rem; margin-bottom: 1rem; }
.step-indicator span {
padding: 0.5rem 1rem;
background: #e9ecef;
border-radius: 4px;
}
.step-indicator span.active { background: #007bff; color: white; }
.step-indicator span.completed { background: #28a745; color: white; }
</style>
</head>
<body>
<div class="step-indicator">
<span class="active" data-step="1">Step 1: Personal Info</span>
<span data-step="2">Step 2: Address</span>
<span data-step="3">Step 3: Confirmation</span>
</div>
<form id="multiStepForm" data-form="multiStepForm">
<div id="form-message" class="form-message"></div>
<!-- Step 1: Personal Info -->
<div class="form-step active" data-step="1">
<h3>Personal Information</h3>
<div class="form-control">
<label for="firstName">First Name</label>
<input type="text" id="firstName" name="firstName" required>
<div id="result_firstName" class="comment"></div>
</div>
<div class="form-control">
<label for="lastName">Last Name</label>
<input type="text" id="lastName" name="lastName" required>
<div id="result_lastName" class="comment"></div>
</div>
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<div id="result_email" class="comment"></div>
</div>
</div>
<!-- Step 2: Address -->
<div class="form-step" data-step="2">
<h3>Address Information</h3>
<div class="form-control">
<label for="street">Street Address</label>
<input type="text" id="street" name="street" required>
<div id="result_street" class="comment"></div>
</div>
<div class="form-control">
<label for="city">City</label>
<input type="text" id="city" name="city" required>
<div id="result_city" class="comment"></div>
</div>
<div class="form-control">
<label for="zipcode">Zip Code</label>
<input type="text" id="zipcode" name="zipcode" required>
<div id="result_zipcode" class="comment"></div>
</div>
</div>
<!-- Step 3: Confirmation -->
<div class="form-step" data-step="3">
<h3>Confirmation</h3>
<div id="confirmationData"></div>
</div>
<div class="form-nav">
<button type="button" id="prevBtn" style="display: none;">Previous</button>
<button type="button" id="nextBtn">Next</button>
<button type="submit" id="submitBtn" style="display: none;">Submit</button>
</div>
</form>
<script>
FormError.configure({
errorClass: 'invalid',
autoFocus: true,
autoScroll: false, // Disable for multi-step
autoClearErrors: false
});
const form = document.getElementById('multiStepForm');
let currentStep = 1;
const totalSteps = 3;
const stepErrors = {}; // Track errors per step
// Step fields configuration
const stepFields = {
1: ['firstName', 'lastName', 'email'],
2: ['street', 'city', 'zipcode']
};
// Update UI
function updateStepUI() {
// Update step visibility
document.querySelectorAll('.form-step').forEach(step => {
step.classList.remove('active');
});
document.querySelector(`.form-step[data-step="${currentStep}"]`).classList.add('active');
// Update step indicator
document.querySelectorAll('.step-indicator span').forEach((span, index) => {
const stepNum = index + 1;
span.classList.remove('active', 'completed');
if (stepNum < currentStep) {
span.classList.add('completed');
} else if (stepNum === currentStep) {
span.classList.add('active');
}
});
// Update navigation buttons
document.getElementById('prevBtn').style.display = currentStep > 1 ? 'block' : 'none';
document.getElementById('nextBtn').style.display = currentStep < totalSteps ? 'block' : 'none';
document.getElementById('submitBtn').style.display = currentStep === totalSteps ? 'block' : 'none';
// Show confirmation data on last step
if (currentStep === 3) {
showConfirmation();
}
// Clear form-level messages when changing steps
FormError.clearGeneralError(form);
}
// Validate current step
function validateCurrentStep() {
const fields = stepFields[currentStep];
if (!fields) return true; // No validation for confirmation step
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const errors = {};
fields.forEach(field => {
const value = data[field];
if (!value) {
errors[field] = `${field} is required`;
}
});
// Additional validation
if (currentStep === 1 && data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.email = 'Please enter a valid email address';
}
if (currentStep === 2 && data.zipcode && !/^\d{5}$/.test(data.zipcode)) {
errors.zipcode = 'Zip code must be 5 digits';
}
// Clear previous step errors
FormError.clearFormMessages(form);
if (Object.keys(errors).length > 0) {
stepErrors[currentStep] = errors;
FormError.showErrors(errors);
FormError.showGeneralError('Please fix the errors before continuing', form);
return false;
} else {
delete stepErrors[currentStep];
return true;
}
}
// Show confirmation
function showConfirmation() {
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const html = `
<h4>Personal Information</h4>
<p><strong>Name:</strong> ${data.firstName} ${data.lastName}</p>
<p><strong>Email:</strong> ${data.email}</p>
<h4>Address</h4>
<p><strong>Street:</strong> ${data.street}</p>
<p><strong>City:</strong> ${data.city}</p>
<p><strong>Zip Code:</strong> ${data.zipcode}</p>
`;
document.getElementById('confirmationData').innerHTML = html;
}
// Next button
document.getElementById('nextBtn').addEventListener('click', () => {
if (validateCurrentStep()) {
currentStep++;
updateStepUI();
}
});
// Previous button
document.getElementById('prevBtn').addEventListener('click', () => {
currentStep--;
updateStepUI();
// Show previous step errors if any
if (stepErrors[currentStep]) {
FormError.showErrors(stepErrors[currentStep]);
}
});
// Form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
FormError.clearAll(form);
const formData = new FormData(form);
const data = Object.fromEntries(formData);
try {
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
FormError.showSuccess('Registration completed successfully!', form);
setTimeout(() => {
window.location.href = '/dashboard';
}, 2000);
} catch (error) {
FormError.showGeneralError('Submission failed. Please try again.', form);
document.getElementById('submitBtn').disabled = false;
document.getElementById('submitBtn').textContent = 'Submit';
}
});
// Clear field errors on input
form.querySelectorAll('input').forEach(input => {
input.addEventListener('input', () => {
if (FormError.state.errors.has(input.name)) {
FormError.clearFieldError(input.name);
}
});
});
// Initialize
updateStepUI();
</script>
</body>
</html>API Reference
Configuration
FormError.configure(options: {
errorClass?: string; // CSS class for invalid fields (default: 'invalid')
errorMessageClass?: string; // CSS class for error messages (default: 'error')
autoFocus?: boolean; // Auto-focus first error field (default: true)
autoScroll?: boolean; // Auto-scroll to first error (default: true)
scrollOffset?: number; // Scroll offset in pixels (default: 100)
debug?: boolean; // Enable debug logging (default: false)
showErrorsInline?: boolean; // Show inline field errors (default: true)
showErrorsInNotification?: boolean; // Show in notification system (default: false)
autoClearErrors?: boolean; // Auto-clear messages (default: true)
autoClearErrorsDelay?: number; // Auto-clear delay in ms (default: 5000)
defaultErrorContainer?: string; // Default error container ID (default: 'form-message')
defaultSuccessContainer?: string; // Default success container ID (default: 'form-message')
}): voidField Errors
// Show field error
FormError.showFieldError(
field: string, // Field ID or name
message: string | string[], // Error message(s)
form?: HTMLElement | null, // Form element (optional)
options?: {
focus?: boolean; // Override autoFocus
scroll?: boolean; // Override autoScroll
}
): void
// Clear field error
FormError.clearFieldError(
field: string | HTMLElement // Field ID, name, or element
): void
// Show multiple field errors
FormError.showErrors(
errors: { [field: string]: string | string[] },
options?: {
focus?: boolean;
scroll?: boolean;
}
): voidGeneral Messages
// Show general error
FormError.showGeneralError(
message: string, // Error message
form?: HTMLElement | string | null, // Form element, container ID, or null
options?: {} // Reserved for future use
): void
// Show success message
FormError.showSuccess(
message: string, // Success message
form?: HTMLElement | string | null, // Form element, container ID, or null
options?: {} // Reserved for future use
): void
// Clear general error
FormError.clearGeneralError(
containerOrForm?: string | HTMLElement | null
): boolean
// Clear success message
FormError.clearSuccess(
containerOrForm?: string | HTMLElement | null
): booleanBulk Operations
// Clear all messages
FormError.clearAll(
form?: HTMLElement | null // Specific form or all forms
): void
// Clear form-specific messages
FormError.clearFormMessages(
form: HTMLElement | string // Form element or ID
): booleanUtilities
// Scroll to element
FormError.scrollToElement(
element: HTMLElement // Element to scroll to
): void
// Check for errors
FormError.hasErrors(): boolean
// Get all errors
FormError.getErrors(): Array<{
field: string;
element: HTMLElement;
messages: string[];
}>
// Get error count
FormError.getErrorsCount(): number
// Reset all state
FormError.reset(): void
// Get form-specific config
FormError.getFormConfig(
form: HTMLElement // Form element
): ConfigObjectState Properties
FormError.state = {
errors: Map<string, { // Current errors
element: HTMLElement;
messages: string[];
}>,
lastFocusedElement: HTMLElement | null, // Last auto-focused element
originalMessages: Map<string, string>, // Original field messages
originalGeneralMessages: Map<string, { // Original container content
html: string;
className: string;
}>
}
FormError.config = {
// Current configuration (see Configuration section)
}Best Practices
1. Use Proper HTML Structure
<!-- Correct: Proper structure for error display -->
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<div id="result_email" class="comment"></div>
</div>
<!-- Incorrect: Missing error container -->
<div class="form-control">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<!-- No error container -->
</div>2. Clear Errors Appropriately
// Good: Clear field error when user starts typing
input.addEventListener('input', () => {
FormError.clearFieldError(input.name);
});
// Good: Clear all errors before new validation
FormError.clearAll(form);
const errors = validateForm();
if (errors) {
FormError.showErrors(errors);
}
// Bad: Not clearing old errors before showing new ones
FormError.showFieldError('email', 'Error 1');
FormError.showFieldError('email', 'Error 2'); // Overwrites, but should clear first3. Use Events for Integration
// Good: Listen to FormError events for custom behavior
Now.on('form:error', (data) => {
// Track validation errors in analytics
analytics.track('Validation Error', {
field: data.field,
message: data.messages[0]
});
});
// Good: Custom notification integration
Now.on('form:generalError', (data) => {
if (data.config.showErrorsInNotification) {
showToast(data.message, 'error');
}
});4. Configure Per Use Case
// Login form: Don't auto-clear errors
FormError.configure({
autoClearErrors: false
});
// Contact form: Auto-clear after delay
FormError.configure({
autoClearErrors: true,
autoClearErrorsDelay: 4000
});
// Registration form: Disable auto-scroll for long forms
FormError.configure({
autoScroll: false
});5. Preserve User Experience
// Good: Show first error and focus
FormError.showErrors(errors); // First field gets focus
// Good: Don't steal focus on blur validation
FormError.showFieldError('email', 'Invalid', form, {
focus: false,
scroll: false
});
// Bad: Stealing focus during typing
input.addEventListener('input', () => {
FormError.showFieldError(input.name, 'Invalid', form); // Steals focus
});6. Handle Server Errors Gracefully
// Good: Map server errors to fields
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(formData)
});
const result = await response.json();
if (!result.success) {
// Show field errors
if (result.errors) {
FormError.showErrors(result.errors);
}
// Show general error
FormError.showGeneralError(result.message || 'Submission failed', form);
}
} catch (error) {
// Network error
FormError.showGeneralError('Network error. Please try again.', form);
}7. Accessibility Compliance
// Good: FormError automatically adds ARIA attributes
FormError.showFieldError('email', 'Invalid email');
// Result: aria-invalid="true" and aria-errormessage="result_email"
// Good: Use semantic HTML
<div id="result_email" role="alert">Email is required</div>
// Good: Ensure keyboard navigation
FormError.configure({
autoFocus: true // First error field receives focus
});8. Multi-step Form Error Management
// Good: Track errors per step
const stepErrors = {};
function validateStep(stepNumber) {
const errors = validateStepFields(stepNumber);
if (Object.keys(errors).length > 0) {
stepErrors[stepNumber] = errors;
FormError.showErrors(errors);
return false;
} else {
delete stepErrors[stepNumber];
return true;
}
}
// Good: Show previous errors when going back
function goToPreviousStep() {
currentStep--;
if (stepErrors[currentStep]) {
FormError.showErrors(stepErrors[currentStep]);
}
}Common Pitfalls
1. Missing Error Containers
Problem:
<!-- No result_ container -->
<input type="email" id="email" name="email">Solution:
<input type="email" id="email" name="email">
<div id="result_email" class="comment"></div>2. Incorrect Field Identifiers
Problem:
// Field has id="userEmail" but trying to show error for "email"
FormError.showFieldError('email', 'Invalid email'); // Won't workSolution:
// Use correct field ID or name
FormError.showFieldError('userEmail', 'Invalid email');3. Not Clearing Errors Before Validation
Problem:
// Old errors still visible
FormError.showErrors(newErrors); // Mixed with old errorsSolution:
// Always clear before showing new errors
FormError.clearAll(form);
FormError.showErrors(newErrors);4. Focus Stealing During Input
Problem:
// Steals focus while user is typing
input.addEventListener('input', () => {
FormError.showFieldError(input.name, 'Error', form); // autoFocus: true (default)
});Solution:
// Disable focus for real-time validation
input.addEventListener('input', () => {
FormError.showFieldError(input.name, 'Error', form, {
focus: false,
scroll: false
});
});5. Not Handling Original Content
Problem:
// Manually clearing containers loses original content
container.textContent = '';Solution:
// Use FormError methods to preserve/restore content
FormError.clearGeneralError(container);
// Original content is restored automatically6. Forgetting Form Context
Problem:
// Form-specific config not used
FormError.showGeneralError('Error'); // Uses default containerSolution:
// Pass form element to use form-specific config
const form = document.getElementById('myForm');
FormError.showGeneralError('Error', form); // Uses form's error-container7. Not Handling Auto-clear Timing
Problem:
// Form resets before auto-clear fires
FormError.showSuccess('Saved!', form); // auto-clears after 5s
setTimeout(() => {
window.location.href = '/dashboard'; // Navigates after 1s
}, 1000); // User never sees full 5 secondsSolution:
// Coordinate timing or disable auto-clear
FormError.configure({ autoClearErrors: false });
FormError.showSuccess('Saved! Redirecting...', form);
setTimeout(() => {
window.location.href = '/dashboard';
}, 2000); // Give user time to read message8. Incorrect Error Format from Server
Problem:
// Server returns: { error: "Login failed" }
// But FormError expects: { fieldName: "message" }Solution:
const result = await response.json();
if (!result.success) {
// Handle general error
if (result.error) {
FormError.showGeneralError(result.error, form);
}
// Handle field errors
if (result.errors) {
FormError.showErrors(result.errors);
}
}Browser Compatibility
- Modern Browsers: Full support (Chrome, Firefox, Safari, Edge)
- IE11: Requires polyfills for Map, Object.entries
- Mobile: Full support on iOS Safari, Chrome Android
Related Documentation
- FormManager - Form handling and validation system
- FormValidation - Validation rules and custom validators
- ResponseHandler - API response handling
- ElementManager - UI element state management
Migration Guide
From Manual Error Handling
Before:
// Manual error display
document.getElementById('email').classList.add('invalid');
document.getElementById('result_email').textContent = 'Email is required';
document.getElementById('email').focus();After:
// Using FormError
FormError.showFieldError('email', 'Email is required');
// Automatically: adds classes, shows message, focuses, scrollsFrom Custom Error Manager
Before:
myErrorManager.showError({
field: 'email',
message: 'Invalid',
focus: true
});After:
FormError.showFieldError('email', 'Invalid');
// Same behavior with FormError API