10.5 Forms and User Input
π Table of Contents
- β’Form Basics
- β’Accessing Form Elements
- β’Reading Form Values
- β’Form Validation
- β’FormData API
- β’Input Events
- β’Best Practices
Form Basics
Forms are the primary way to collect user input on the web.
Form Structure
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β <form> β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <label> <input type="text"> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <label> <input type="email"> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <label> <textarea> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <button type="submit">Submit</button> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Common Input Types
| Type | Purpose | Example |
|---|
| text | Single line text | <input type="text"> |
| password | Hidden text input | <input type="password"> |
| email | Email with validation | <input type="email"> |
| number | Numeric input | <input type="number" min="0" max="100"> |
| checkbox | Boolean true/false | <input type="checkbox"> |
| radio | One of many options | <input type="radio" name="group"> |
| select | Dropdown selection | <select><option>...</option></select> |
| textarea | Multi-line text | <textarea rows="4"></textarea> |
| date | Date picker | <input type="date"> |
| file | File upload | <input type="file"> |
| range | Slider control | <input type="range" min="0" max="100"> |
| color | Color picker | <input type="color"> |
Accessing Form Elements
By Document Methods
const form = document.getElementById('myForm');
const forms = document.forms;
const myForm = document.forms['myForm'];
const firstForm = document.forms[0];
Accessing Form Fields
const form = document.getElementById('registrationForm');
const usernameField = form.elements['username'];
const emailField = form.elements['email'];
const firstField = form.elements[0];
const passwordField = form.querySelector('input[type="password"]');
Form Hierarchy
document.forms
βββ form[0] (document.forms['formName'])
β βββ elements[0]
β βββ elements['fieldName']
β βββ elements.length
βββ form[1]
βββ ...
Reading Form Values
Different Input Types
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Reading Form Values β
ββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β Input Type β How to Get Value β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β text, email, β element.value β
β password, etc. β β Returns string β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β checkbox β element.checked β
β β β Returns boolean β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β radio β Find checked: querySelector(':checked') β
β β β Then get .value β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β select β select.value (selected option's value) β
β β select.selectedIndex (index number) β
β β select.options[i] (specific option) β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β select multiple β Get all selected options β
β β [...select.selectedOptions] β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β file β input.files β FileList β
β β input.files[0] β First File β
ββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
Code Examples
const name = document.getElementById('name').value;
const isSubscribed = document.getElementById('subscribe').checked;
const selectedGender = document.querySelector(
'input[name="gender"]:checked'
)?.value;
const country = document.getElementById('country').value;
const selectedOptions = [
...document.getElementById('skills').selectedOptions,
].map((option) => option.value);
const files = document.getElementById('avatar').files;
if (files.length > 0) {
const firstFile = files[0];
console.log(firstFile.name, firstFile.size, firstFile.type);
}
Form Validation
HTML5 Built-in Validation
<input type="text" required />
<input type="text" pattern="[A-Za-z]{3,}" title="3+ letters" />
<input type="text" minlength="3" maxlength="20" />
<input type="number" min="0" max="100" step="5" />
<input type="email" required />
Validation Flow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Form Submission Flow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β User clicks β
β Submit button β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Browser runs β
β HTML5 validationβ
ββββββββββ¬βββββββββ
β
ββββββββββββββββ΄βββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Validation β β Validation β
β FAILS β β PASSES β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Show error β β 'submit' event β
β (no event) β β is fired β
βββββββββββββββββββ ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β JavaScript β
β handler runs β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββ΄ββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Custom β β Call β
β validation β β e.preventDefaultβ
β passes β β to stop submit β
ββββββββββ¬βββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Form submitted β
β or AJAX sent β
βββββββββββββββββββ
Constraint Validation API
const input = document.getElementById('email');
input.validity.valid;
input.validity.valueMissing;
input.validity.typeMismatch;
input.validity.patternMismatch;
input.validity.tooShort;
input.validity.tooLong;
input.validity.rangeUnderflow;
input.validity.rangeOverflow;
input.validity.stepMismatch;
input.validity.customError;
input.checkValidity();
input.reportValidity();
input.setCustomValidity(msg);
Validity States Table
| Property | Triggers When |
|---|
| valueMissing | Required field is empty |
| typeMismatch | Email/URL doesn't match format |
| patternMismatch | Value doesn't match pattern regex |
| tooShort | Value shorter than minlength |
| tooLong | Value longer than maxlength |
| rangeUnderflow | Number below min |
| rangeOverflow | Number above max |
| stepMismatch | Number doesn't match step increments |
| badInput | Browser can't convert input |
| customError | setCustomValidity() was called with msg |
Custom Validation
const form = document.getElementById('registrationForm');
const password = document.getElementById('password');
const confirmPassword = document.getElementById('confirmPassword');
function validatePassword() {
if (password.value !== confirmPassword.value) {
confirmPassword.setCustomValidity("Passwords don't match");
} else {
confirmPassword.setCustomValidity('');
}
}
password.addEventListener('input', validatePassword);
confirmPassword.addEventListener('input', validatePassword);
form.addEventListener('submit', function (e) {
e.preventDefault();
if (form.checkValidity()) {
console.log('Form is valid!');
} else {
form.reportValidity();
}
});
FormData API
Creating FormData
const form = document.getElementById('myForm');
const formData = new FormData(form);
const formData = new FormData();
formData.append('username', 'john_doe');
formData.append('email', 'john@example.com');
FormData Methods
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FormData Methods β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββ€
β Method β Description β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββ€
β append(name, value) β Add a new value (allows duplicates) β
β set(name, value) β Set value (replaces existing) β
β get(name) β Get first value for name β
β getAll(name) β Get all values for name as array β
β has(name) β Check if name exists β
β delete(name) β Remove all values for name β
β entries() β Iterator of [name, value] pairs β
β keys() β Iterator of all keys β
β values() β Iterator of all values β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββ
Using FormData
const form = document.getElementById('contactForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(form);
console.log(formData.get('name'));
console.log(formData.get('email'));
for (const [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
const data = Object.fromEntries(formData);
console.log(data);
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
});
if (response.ok) {
console.log('Form submitted successfully!');
}
} catch (error) {
console.error('Submission failed:', error);
}
});
FormData with Files
const form = document.getElementById('uploadForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(form);
const file = formData.get('avatar');
console.log('File name:', file.name);
console.log('File size:', file.size);
console.log('File type:', file.type);
formData.append('uploadedAt', new Date().toISOString());
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
});
FormData vs JSON
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FormData vs JSON Comparison β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ€
β FormData β JSON β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ€
β Supports file uploads β Files need Base64 encoding β
β multipart/form-data β application/json β
β Server sees like form β Server needs JSON parser β
β Multiple same-name keys β Last value wins (in object) β
β Automatic encoding β Manual JSON.stringify β
βββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββ
const form = document.getElementById('myForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
});
Input Events
Event Types
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Form Input Events β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Event β When It Fires β
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββ€
β input β Value changes (typing, paste, etc.) β
β change β Value changes AND element loses focus β
β focus β Element receives focus β
β blur β Element loses focus β
β submit β Form is submitted β
β reset β Form reset button clicked β
β invalid β Element fails validation on submit β
β select β Text is selected in input/textarea β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββ
Event Timeline
User types "Hello" and tabs away:
H e l l o [Tab]
β β β β β β
βΌ βΌ βΌ βΌ βΌ βΌ
ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ
βinput β βinput β βinput β βinput β βinput β βblur β βchangeβ
β"H" β β"He" β β"Hel" β β"Hell"β β"Helloβ β β β β
ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ
Note: 'change' fires AFTER 'blur' when value has changed
Input vs Change
const input = document.getElementById('search');
input.addEventListener('input', function (e) {
console.log('Input event:', e.target.value);
});
input.addEventListener('change', function (e) {
console.log('Change event:', e.target.value);
});
Focus Events
const emailInput = document.getElementById('email');
emailInput.addEventListener('focus', function () {
this.parentElement.classList.add('focused');
showHint('Enter your email address');
});
emailInput.addEventListener('blur', function () {
this.parentElement.classList.remove('focused');
validateEmail(this.value);
});
Form Submit and Reset
const form = document.getElementById('myForm');
form.addEventListener('submit', function (e) {
e.preventDefault();
console.log('Form submitted!');
});
form.addEventListener('reset', function (e) {
if (!confirm('Clear all fields?')) {
e.preventDefault();
}
});
form.submit();
form.reset();
Best Practices
1. Always Use Labels
<label>
Name:
<input type="text" name="name" />
</label>
<label for="email">Email:</label>
<input type="email" id="email" name="email" />
2. Provide Good UX Feedback
const input = document.getElementById('username');
const feedback = document.getElementById('usernameFeedback');
input.addEventListener('input', function () {
if (this.validity.valid) {
feedback.textContent = 'β Looks good!';
feedback.className = 'feedback success';
} else if (this.validity.valueMissing) {
feedback.textContent = 'Username is required';
feedback.className = 'feedback error';
} else if (this.validity.tooShort) {
feedback.textContent = `At least ${this.minLength} characters needed`;
feedback.className = 'feedback error';
}
});
3. Debounce Input Events
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const searchInput = document.getElementById('search');
searchInput.addEventListener(
'input',
debounce(function (e) {
performSearch(e.target.value);
}, 300)
);
4. Accessibility Considerations
<fieldset>
<legend>Payment Method</legend>
<label>
<input type="radio" name="payment" value="credit" />
Credit Card
</label>
<label>
<input type="radio" name="payment" value="paypal" />
PayPal
</label>
</fieldset>
<input type="email" id="email" aria-describedby="emailError" />
<span id="emailError" role="alert" aria-live="polite"></span>
5. Prevent Double Submission
const form = document.getElementById('checkoutForm');
const submitBtn = form.querySelector('button[type="submit"]');
form.addEventListener('submit', async function (e) {
e.preventDefault();
submitBtn.disabled = true;
submitBtn.textContent = 'Processing...';
try {
await submitOrder(new FormData(form));
showSuccess('Order placed!');
} catch (error) {
showError(error.message);
submitBtn.disabled = false;
submitBtn.textContent = 'Place Order';
}
});
Quick Reference
Form Handling Checklist
β‘ preventDefault() on submit
β‘ Validate before processing
β‘ Handle all input types correctly
β‘ Provide visual feedback
β‘ Show loading state
β‘ Handle errors gracefully
β‘ Prevent double submission
β‘ Consider accessibility
Value Extraction Cheat Sheet
const text = input.value;
const checked = checkbox.checked;
const radio = form.querySelector(':checked').value;
const select = select.value;
const multi = [...select.selectedOptions].map((o) => o.value);
const files = fileInput.files;
const formData = new FormData(form);
const obj = Object.fromEntries(new FormData(form));
Summary
| Concept | Key Points |
|---|
| Accessing Forms | document.forms, form.elements, querySelector |
| Reading Values | .value for most, .checked for checkboxes |
| Validation | HTML5 attributes + Constraint Validation API |
| FormData | new FormData(form), works with fetch |
| Input Event | Real-time, fires on every change |
| Change Event | Fires on blur when value changed |
| Best Practices | Labels, feedback, debounce, accessibility |