feat(contact-form): enhance validation and accessibility

This commit is contained in:
Derek L. Seitz 2025-08-16 08:09:53 -05:00
parent 2f509982f2
commit d742364f46
4 changed files with 117 additions and 51 deletions

View File

@ -54,31 +54,37 @@ pageScripts:
<legend>Contact Info</legend> <legend>Contact Info</legend>
<div class="first-name"> <div class="first-name">
<label for="first-name">First Name:<span class="req-ask">*</span></label> <label for="first-name">First Name:<span class="req-ask">*</span></label>
<input type="text" id="first-name" name="first-name" placeholder="Ex. Sally" required> <input type="text" id="first-name" name="first-name" placeholder="Ex. Sally" required aria-describedby="first-name-error">
<span class="error-message" id="first-name-error" aria-live="polite"></span>
</div> </div>
<div class="last-name"> <div class="last-name">
<label for="last-name">Last Name:<span class="req-ask">*</span></label> <label for="last-name">Last Name:<span class="req-ask">*</span></label>
<input type="text" id="last-name" name="last-name" placeholder="Ex. Westfield" required> <input type="text" id="last-name" name="last-name" placeholder="Ex. Westfield" required aria-describedby="last-name-error">
<span class="error-message" id="last-name-error" aria-live="polite"></span>
</div> </div>
<div class="organization"> <div class="organization">
<label for="organization">Organization:</label> <label for="organization">Organization:</label>
<input type="text" id="organization" name="organization" placeholder="Optional"> <input type="text" id="organization" name="organization" placeholder="Optional" aria-describedby="organization-error">
<span class="error-message" id="organization-error" aria-live="polite"></span>
</div> </div>
<div class="email"> <div class="email">
<label for="email">Email:<span class="req-ask">*</span></label> <label for="email">Email:<span class="req-ask">*</span></label>
<input type="email" id="email" name="email" placeholder="Ex. user@domain.com" required> <input type="email" id="email" name="email" placeholder="Ex. user@domain.com" required aria-describedby="email-error">
<span class="error-message" id="email-error" aria-live="polite"></span>
</div> </div>
<div class="phone"> <div class="phone">
<label for="phone">Phone:<span class="req-ask">*</span></label> <label for="phone">Phone:<span class="req-ask">*</span></label>
<input type="tel" id="phone" name="phone" placeholder="Ex. 111-333-4444" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" required> <input type="tel" id="phone" name="phone" placeholder="Ex. 111-333-4444" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" required aria-describedby="phone-error">
<span class="error-message" id="phone-error" aria-live="polite"></span>
</div> </div>
<div class="preferred-method"> <div class="preferred-method" role="radiogroup" aria-labelledby="contact-method-label">
<h3>Preferred Contact Method</h3> <h3 id="contact-method-label">Preferred Contact Method</h3>
<div class="radio-group"> <div class="radio-group">
<div class="email-radio"> <div class="email-radio">
<input type="radio" id="contact-email" name="contact-method" value="email" checked required> <input type="radio" id="contact-email" name="contact-method" value="email" checked required>
@ -97,9 +103,13 @@ pageScripts:
<fieldset class="message-field"> <fieldset class="message-field">
<legend>Message</legend> <legend>Message</legend>
<label for="message">Questions or Feedback</label> <label for="message">Questions or Feedback</label>
<textarea id="message" name="message" rows="15" placeholder="What questions or feedback do you have for me?" required></textarea> <textarea id="message" name="message" rows="15" placeholder="What questions or feedback do you have for me?" required aria-describedby="message-error"></textarea>
<span class="error-message" id="message-error" aria-live="polite"></span>
</fieldset> </fieldset>
<div class="submit-reset">
<button type="submit">Send Message</button> <button type="submit">Send Message</button>
<button type="reset">Reset Form</button>
</div>
</div> </div>
</div> </div>
<p class="form-note">* Required fields</p> <p class="form-note">* Required fields</p>

View File

@ -2,6 +2,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
const accordionHeaders = document.querySelectorAll('.accordion-header'); const accordionHeaders = document.querySelectorAll('.accordion-header');
accordionHeaders.forEach(header => { accordionHeaders.forEach(header => {
// Toggle content on click
header.addEventListener('click', () => { header.addEventListener('click', () => {
const content = header.nextElementSibling; const content = header.nextElementSibling;
if (content.style.display === 'block') { if (content.style.display === 'block') {
@ -10,5 +11,13 @@ document.addEventListener('DOMContentLoaded', (event) => {
content.style.display = 'block'; content.style.display = 'block';
} }
}); });
// Toggle content on Enter or Space key press
header.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
header.click();
}
});
}); });
}); });

View File

@ -1,68 +1,94 @@
// Contact Form Submission Script // Contact Form Submission Script
// Select Form Element
const form = document.getElementById("contact-form"); const form = document.getElementById("contact-form");
const resetButton = form.querySelector('button[type="reset"]');
// Event Listener for Submit Event
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent default form submission
// Variable Assignments // Utility: Show error for specific input
const firstName = document.getElementById('first-name').value.trim(); function showError(inputId, message) {
const lastName = document.getElementById('last-name').value.trim(); const input = document.getElementById(inputId);
const organization = document.getElementById('organization').value.trim(); // Fixed typo const errorSpan = document.getElementById(`${inputId}-error`);
const email = document.getElementById('email').value.trim(); if (input) input.classList.add("error");
const phone = document.getElementById('phone').value.trim(); if (errorSpan) errorSpan.textContent = message;
}
// Utility: Clear all errors
function clearErrors() {
document.querySelectorAll(".error-message").forEach(span => span.textContent = "");
document.querySelectorAll("input, textarea").forEach(input => input.classList.remove("error"));
}
form.addEventListener("submit", function(event) {
event.preventDefault(); // Prevent actual form submission
clearErrors(); // Clear previous errors
// Get values
const firstName = document.getElementById("first-name").value.trim();
const lastName = document.getElementById("last-name").value.trim();
const organization = document.getElementById("organization").value.trim();
const email = document.getElementById("email").value.trim();
const phone = document.getElementById("phone").value.trim();
const contactMethod = document.querySelector('input[name="contact-method"]:checked')?.value; const contactMethod = document.querySelector('input[name="contact-method"]:checked')?.value;
const message = document.getElementById('message').value.trim(); const message = document.getElementById("message").value.trim();
const errorMessages = []; let hasErrors = false;
// First name validation // First Name
if (!firstName) { if (!firstName) {
errorMessages.push("Please enter your first name."); showError("first-name", "Please enter your first name.");
hasErrors = true;
} else if (firstName.length < 2) { } else if (firstName.length < 2) {
errorMessages.push("First name must be at least 2 characters."); showError("first-name", "First name must be at least 2 characters.");
hasErrors = true;
} }
// Last name validation // Last Name
if (!lastName) { if (!lastName) {
errorMessages.push("Please enter your last name."); showError("last-name", "Please enter your last name.");
hasErrors = true;
} else if (lastName.length < 2) { } else if (lastName.length < 2) {
errorMessages.push("Last name must be at least 2 characters."); showError("last-name", "Last name must be at least 2 characters.");
hasErrors = true;
} }
// Email validation // Organization (optional)
if (organization.length > 0 && organization.length < 2) {
showError("organization", "Organization name must be at least 2 characters.");
hasErrors = true;
}
// Email
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) { if (!emailPattern.test(email)) {
errorMessages.push("Please enter a valid email address."); showError("email", "Please enter a valid email address.");
hasErrors = true;
} }
// Phone number validation (exactly 10 digits) // Phone — match format: 123-456-7890
const phonePattern = /^\d{10}$/; const phonePattern = /^\d{3}-\d{3}-\d{4}$/;
if (!phonePattern.test(phone)) { if (!phonePattern.test(phone)) {
errorMessages.push("Phone number must be exactly 10 digits (numbers only)."); showError("phone", "Phone number must be in the format 123-456-7890.");
hasErrors = true;
} }
// Message validation // Message
if (message.length < 10) { if (message.length < 10) {
errorMessages.push("Message must be at least 10 characters long."); showError("message", "Message must be at least 10 characters long.");
hasErrors = true;
} }
// Optional: Organization validation (only if provided) if (!hasErrors) {
if (organization.length > 0 && organization.length < 2) {
errorMessages.push("Organization name must be at least 2 characters if provided.");
}
// Display errors or submit
const errorDiv = document.getElementById('errorMessages');
if (errorMessages.length > 0) {
errorDiv.innerHTML = errorMessages.join("<br>");
} else {
errorDiv.innerHTML = "";
alert("Form submitted successfully!"); alert("Form submitted successfully!");
// this.submit(); // Uncomment if actually submitting to a server // form.submit(); // Uncomment when ready to send to server
} }
}); });
// This script isn't finished. form.addEventListener("reset", function (event) {
const confirmed = confirm("Are you sure you want to clear the form?");
if (!confirmed) {
event.preventDefault();
return;
}
clearErrors();
});

View File

@ -137,11 +137,26 @@ legend {
} }
label { label {
display: block; font-weight: 600;
font-size: 0.95rem;
color: #333;
margin-bottom: 0.4rem; margin-bottom: 0.4rem;
display: block;
font-weight: 500; font-weight: 500;
} }
.error-message {
color: red;
font-size: 0.9em;
margin-top: 4px;
display: block;
}
input.error, textarea.error {
border-color: red;
}
input[type="text"], input[type="text"],
input[type="email"], input[type="email"],
input[type="tel"], input[type="tel"],
@ -184,7 +199,13 @@ textarea:focus {
} }
/* Button */ /* Button */
button[type="submit"] { .submit-reset {
display: flex;
gap: 20px;
justify-content: center;
}
button[type="submit"], button[type="reset"] {
margin:20px 0px 20px 0px; margin:20px 0px 20px 0px;
padding: 8px 25px; padding: 8px 25px;
background-color: #ea7e0b; background-color: #ea7e0b;
@ -196,7 +217,7 @@ button[type="submit"] {
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
button[type="submit"]:hover { button[type="submit"]:hover, button[type="reset"]:hover {
background-color: #2e97be; background-color: #2e97be;
} }