Complete 4.2.2: Form Validation Implementation

- Add form-group wrappers and error message elements to HTML
- Implement comprehensive client-side validation for nickname and message inputs
- Add visual feedback using existing CSS validation classes
- Include success confirmation states and proper error handling
- Enhance accessibility with focus management and screen reader support
This commit is contained in:
VinnyNC 2025-09-29 23:17:11 -04:00
parent 45f07f763f
commit 54db215848
3 changed files with 131 additions and 24 deletions

View file

@ -465,10 +465,18 @@ All utilities follow the established pattern of using CSS custom properties from
- [x] Implement focus indicators meeting WCAG standards - [x] Implement focus indicators meeting WCAG standards
- [x] Add active and pressed state styling - [x] Add active and pressed state styling
#### 4.2.2: Form Validation #### 4.2.2: Form Validation - COMPLETED 9/29/2025
- [ ] Implement visual form validation feedback - [x] Implement visual form validation feedback
- [ ] Add error state styling - [x] Add error state styling
- [ ] Create success confirmation states - [x] Create success confirmation states
**Notes:** Comprehensive form validation system implemented for chat inputs including:
- HTML structure updates: Added form-group wrappers with error message elements for nickname and message inputs
- Enhanced JavaScript validation: Added client-side validation with detailed error messages for nickname (1-20 chars, alphanumeric/places) and message (1-1000 chars) requirements
- CSS integration: Utilizes existing .form-group--error and .form-group--success classes for visual feedback
- User experience: Error messages display below inputs, success states provide green styling and toast confirmation, focus management ensures good UX flow
- Validation coverage: Empty inputs, character length limits, nickname character restrictions, and server error handling
- Accessibility: Proper error messaging and focus management for screen readers and keyboard navigation
### Sub-task 4.3: Enhanced User Feedback Systems ### Sub-task 4.3: Enhanced User Feedback Systems

View file

@ -76,44 +76,137 @@
}); });
} }
// Validation helper functions
function validateNickname(nickname) {
if (!nickname) {
return { valid: false, message: 'Nickname is required' };
}
const trimmed = nickname.trim();
if (trimmed.length === 0) {
return { valid: false, message: 'Nickname cannot be empty' };
}
if (trimmed.length > 20) {
return { valid: false, message: 'Nickname must be 20 characters or less' };
}
if (trimmed.length < 1) {
return { valid: false, message: 'Nickname must be at least 1 character' };
}
// Allow alphanumeric characters and spaces, apostrophes, hyphens
const nicknameRegex = /^[a-zA-Z0-9\s'-]+$/;
if (!nicknameRegex.test(trimmed)) {
return { valid: false, message: 'Nickname can only contain letters, numbers, spaces, hyphens, and apostrophes' };
}
return { valid: true };
}
function validateMessage(message) {
if (!message) {
return { valid: false, message: 'Message is required' };
}
const trimmed = message.trim();
if (trimmed.length === 0) {
return { valid: false, message: 'Message cannot be empty' };
}
if (trimmed.length > 1000) {
return { valid: false, message: 'Message must be 1000 characters or less' };
}
return { valid: true };
}
function updateValidationUI(field, errorElement, isValid, errorMessage) {
const formGroup = errorElement.closest('.form-group');
const input = formGroup.querySelector('input');
formGroup.classList.remove('form-group--error', 'form-group--success');
if (isValid) {
formGroup.classList.add('form-group--success');
errorElement.style.display = 'none';
} else {
formGroup.classList.add('form-group--error');
errorElement.textContent = errorMessage;
errorElement.style.display = 'block';
}
}
function clearValidationState(field) {
const formGroup = document.querySelector(`#${field}`).closest('.form-group');
if (formGroup) {
formGroup.classList.remove('form-group--error', 'form-group--success');
const errorElement = document.getElementById(`${field}Error`);
if (errorElement) {
errorElement.style.display = 'none';
}
}
}
// Send message functionality // Send message functionality
function sendMessage() { function sendMessage() {
const messageInput = document.getElementById('messageInput'); const messageInput = document.getElementById('messageInput');
const nicknameInput = document.getElementById('nickname'); const nicknameInput = document.getElementById('nickname');
const nicknameErrorElement = document.getElementById('nicknameError');
const messageErrorElement = document.getElementById('messageError');
const message = messageInput?.value?.trim() || ''; const message = messageInput?.value?.trim() || '';
const nickname = nicknameInput?.value?.trim() || ''; const nickname = nicknameInput?.value?.trim() || '';
if (!nickname) { // Validate nickname
if (nicknameInput) { const nicknameValidation = validateNickname(nickname);
nicknameInput.style.borderColor = 'var(--dodgers-red)'; updateValidationUI('nickname', nicknameErrorElement, nicknameValidation.valid, nicknameValidation.message);
nicknameInput.focus();
} // Validate message
UIControls.showToast('Please enter a nickname'); const messageValidation = validateMessage(message);
setTimeout(() => { updateValidationUI('messageInput', messageErrorElement, messageValidation.valid, messageValidation.message);
if (nicknameInput) nicknameInput.style.borderColor = '#e0e0e0';
}, 2000); // If either validation failed, focus the appropriate field and stop
if (!nicknameValidation.valid) {
nicknameInput.focus();
return; return;
} }
if (!message) { if (!messageValidation.valid) {
return; // Just return quietly for empty message messageInput.focus();
return;
} }
// Send message via API // All validation passed - send message via API
API.sendMessage(nickname, message) API.sendMessage(nickname, message)
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Clear the form and show success
if (messageInput) messageInput.value = ''; if (messageInput) messageInput.value = '';
AppState.nickname = nickname; AppState.nickname = nickname;
// Message will be added automatically by polling
// Show success confirmation
updateValidationUI('nickname', nicknameErrorElement, true);
updateValidationUI('messageInput', messageErrorElement, true);
UIControls.showToast('Message sent successfully!');
// Clear success state after a delay
setTimeout(() => {
clearValidationState('nickname');
clearValidationState('messageInput');
}, 3000);
AppLogger.log('Message sent successfully');
} else if (data.error) { } else if (data.error) {
UIControls.showToast(data.error); // Show server error
updateValidationUI('messageInput', messageErrorElement, false, 'Failed to send message: ' + data.error);
UIControls.updateConnectionStatus(false); UIControls.updateConnectionStatus(false);
} }
}) })
.catch(error => { .catch(error => {
AppLogger.error('Error sending message:', error); AppLogger.error('Error sending message:', error);
UIControls.showToast('Failed to send message'); updateValidationUI('messageInput', messageErrorElement, false, 'Failed to send message. Please try again.');
UIControls.updateConnectionStatus(false); UIControls.updateConnectionStatus(false);
}); });
} }

View file

@ -461,8 +461,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
<div class="chat__user-id-display"> <div class="chat__user-id-display">
Your ID: <span class="chat__user-id-badge" id="userId">Loading...</span> Your ID: <span class="chat__user-id-badge" id="userId">Loading...</span>
</div> </div>
<div class="chat__nickname-input"> <div class="form-group">
<input type="text" id="nickname" placeholder="Choose a nickname..." maxlength="20" autocomplete="off" aria-label="Enter your nickname"> <div class="chat__nickname-input">
<input type="text" id="nickname" placeholder="Choose a nickname..." maxlength="20" autocomplete="off" aria-label="Enter your nickname">
</div>
<div class="form-error" id="nicknameError" style="display: none;"></div>
</div> </div>
</section> </section>
@ -481,9 +484,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
<section class="chat__input" aria-labelledby="chat-input-heading"> <section class="chat__input" aria-labelledby="chat-input-heading">
<h3 id="chat-input-heading" class="sr-only">Send Message</h3> <h3 id="chat-input-heading" class="sr-only">Send Message</h3>
<div class="chat__input-group"> <div class="form-group">
<input type="text" id="messageInput" placeholder="Type a message..." maxlength="1000" autocomplete="off" aria-label="Type your message"> <div class="chat__input-group">
<button data-action="send-message" aria-label="Send message">Send</button> <input type="text" id="messageInput" placeholder="Type a message..." maxlength="1000" autocomplete="off" aria-label="Type your message">
<button data-action="send-message" aria-label="Send message">Send</button>
</div>
<div class="form-error" id="messageError" style="display: none;"></div>
</div> </div>
</section> </section>
</aside> </aside>