Skip to content

Commit f51daee

Browse files
author
gauthier witkowski
committed
Refactor comments and variable names
1 parent 6187275 commit f51daee

File tree

1 file changed

+87
-67
lines changed

1 file changed

+87
-67
lines changed

AjaxForm.js

Lines changed: 87 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,163 +8,183 @@
88
*/
99

1010
/**
11-
* Public reCAPTCHA v3 site key (frontend only)
12-
* Replace this placeholder with your actual key
11+
* reCAPTCHA v3 public site key (visible in frontend code)
12+
* ⚠️ Replace with your own key from https://www.google.com/recaptcha/admin
1313
* @constant {string}
1414
*/
1515
const RECAPTCHA_SITE_KEY = 'YOUR_RECAPTCHA_SITE_KEY';
1616

1717
/**
18+
* Backend JSON response shape
1819
* @typedef {Object} AjaxResponse
19-
* @property {boolean} success Indicates if backend processing succeeded
20-
* @property {string} message Human readable status or error
21-
* @property {string=} field Optional form field name that failed validation
22-
*/
23-
24-
/**
25-
* @typedef {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} FormControl
20+
* @property {boolean} success - True if message was sent successfully
21+
* @property {string} message - User-facing status message
22+
* @property {string=} field - Name of the form field that failed (optional)
2623
*/
2724

2825
document.addEventListener('DOMContentLoaded', () => {
2926
'use strict';
3027

31-
/** @type {HTMLFormElement|null} */
28+
// 1. Find form and required DOM elements
3229
const form = document.querySelector('.needs-validation');
33-
if (!form) return;
30+
if (!form) return; // Exit if form not found on page
3431

35-
/** @type {HTMLElement|null} */
3632
const spinner = document.getElementById('loading-spinner');
37-
/** @type {HTMLButtonElement|null} */
3833
const submitButton = form.querySelector('button[type="submit"]');
39-
/** @type {HTMLElement|null} */
4034
const alertContainer = document.getElementById('alert-status');
41-
/** @type {boolean} */
42-
let inFlight = false;
35+
36+
let isSubmitting = false; // Prevent duplicate submissions
4337

44-
// Live validation
38+
// 2. Setup live validation (show green/red feedback as user types)
4539
form.querySelectorAll('input, select, textarea').forEach((field) => {
46-
const eventName = field.tagName === 'SELECT' ? 'change' : 'input';
47-
field.addEventListener(eventName, () => {
48-
if (!field.value.trim()) {
40+
const eventType = field.tagName === 'SELECT' ? 'change' : 'input';
41+
42+
field.addEventListener(eventType, () => {
43+
const isEmpty = !field.value.trim();
44+
const isValid = field.checkValidity();
45+
46+
// Remove all validation classes if field is empty
47+
if (isEmpty) {
4948
field.classList.remove('is-valid', 'is-invalid');
50-
} else if (field.checkValidity()) {
49+
}
50+
// Show green checkmark if valid
51+
else if (isValid) {
5152
field.classList.add('is-valid');
5253
field.classList.remove('is-invalid');
53-
} else {
54+
}
55+
// Show red error if invalid
56+
else {
5457
field.classList.add('is-invalid');
5558
field.classList.remove('is-valid');
5659
}
5760
});
5861
});
5962

60-
// Handle form submission with AJAX
63+
// 3. Handle form submission
6164
form.addEventListener('submit', async (event) => {
6265
event.preventDefault();
6366
event.stopPropagation();
6467

65-
if (inFlight) return;
68+
// Ignore if already submitting
69+
if (isSubmitting) return;
6670

71+
// Reset any previous validation styling
6772
form.classList.remove('was-validated');
68-
form.querySelectorAll('.is-valid, .is-invalid').forEach((el) => el.classList.remove('is-valid', 'is-invalid'));
73+
form.querySelectorAll('.is-valid, .is-invalid').forEach((el) => {
74+
el.classList.remove('is-valid', 'is-invalid');
75+
});
6976

70-
// Native HTML5 validity check
77+
// Check HTML5 built-in validation (required, email format, etc.)
7178
if (!form.checkValidity()) {
7279
form.classList.add('was-validated');
73-
form.querySelector(':invalid')?.focus();
80+
form.querySelector(':invalid')?.focus(); // Focus first invalid field
7481
return;
7582
}
7683

84+
// Prepare form data for sending
7785
const formData = new FormData(form);
78-
const endpoint = 'AjaxForm.php';
86+
const backendURL = 'AjaxForm.php';
7987

80-
// Show loading spinner and disable form
88+
// Disable form to prevent changes during submission
8189
if (spinner) spinner.classList.remove('d-none');
8290
if (submitButton) submitButton.disabled = true;
83-
form.querySelectorAll('input, select, textarea, button').forEach((el) => {
84-
if (el !== submitButton) el.disabled = true;
91+
form.querySelectorAll('input, select, textarea, button').forEach((element) => {
92+
if (element !== submitButton) element.disabled = true;
8593
});
86-
inFlight = true;
94+
isSubmitting = true;
8795

8896
try {
97+
// Step 1: Verify reCAPTCHA is loaded
8998
if (!RECAPTCHA_SITE_KEY || RECAPTCHA_SITE_KEY === 'YOUR_RECAPTCHA_SITE_KEY') {
9099
throw new Error('⚠️ Missing reCAPTCHA site key.');
91100
}
92101
if (typeof grecaptcha === 'undefined' || !grecaptcha?.ready) {
93-
throw new Error('⚠️ reCAPTCHA not loaded.');
102+
throw new Error('⚠️ reCAPTCHA script not loaded.');
94103
}
95104

96-
// Wait for reCAPTCHA to be ready and get the token
97-
const token = await new Promise((resolve, reject) => {
105+
// Step 2: Get reCAPTCHA token (proves user is human)
106+
const recaptchaToken = await new Promise((resolve, reject) => {
98107
try {
99108
grecaptcha.ready(() => {
100-
grecaptcha.execute(RECAPTCHA_SITE_KEY, { action: 'submit' }).then(resolve).catch(reject);
109+
grecaptcha.execute(RECAPTCHA_SITE_KEY, { action: 'submit' })
110+
.then(resolve)
111+
.catch(reject);
101112
});
102-
} catch (e) {
103-
reject(e);
113+
} catch (error) {
114+
reject(error);
104115
}
105116
});
106117

107-
// Append token to input form
108-
formData.append('recaptcha_token', token);
118+
// Add token to form data
119+
formData.append('recaptcha_token', recaptchaToken);
109120

110-
// Send data using Fetch API (AJAX)
111-
const response = await fetch(endpoint, {
121+
// Step 3: Send form data to backend
122+
const response = await fetch(backendURL, {
112123
method: 'POST',
113124
body: formData,
114125
headers: { Accept: 'application/json' },
115126
});
116-
if (!response.ok) throw new Error(`⚠️ Network error: ${response.status}`);
117127

128+
if (!response.ok) {
129+
throw new Error(`⚠️ Network error: ${response.status}`);
130+
}
131+
132+
// Step 4: Parse JSON response
118133
/** @type {AjaxResponse} */
119-
let result;
134+
let data;
120135
try {
121-
result = await response.json();
136+
data = await response.json();
122137
} catch {
123-
throw new Error('⚠️ Invalid JSON response.');
138+
throw new Error('⚠️ Invalid JSON response from server.');
124139
}
125140

126-
const success = !!result?.success;
127-
const message = result?.message || (success ? 'Success.' : 'An error occurred.');
128-
const field = result?.field;
141+
const wasSuccessful = !!data?.success;
142+
const statusMessage = data?.message || (wasSuccessful ? 'Success.' : 'An error occurred.');
143+
const invalidFieldName = data?.field;
129144

130-
// Highlight the invalid field
131-
if (field) {
132-
const target = form.querySelector(`[name="${CSS.escape(field)}"]`);
133-
if (target) {
134-
target.classList.add('is-invalid');
135-
target.focus();
145+
// Highlight specific field if backend indicates validation error
146+
if (invalidFieldName) {
147+
const invalidField = form.querySelector(`[name="${CSS.escape(invalidFieldName)}"]`);
148+
if (invalidField) {
149+
invalidField.classList.add('is-invalid');
150+
invalidField.focus();
136151
form.classList.remove('was-validated');
137152
}
138153
}
139154

155+
// Show success or error alert
140156
if (alertContainer) {
141-
alertContainer.className = `alert alert-${success ? 'success' : 'danger'} fade show`;
142-
alertContainer.textContent = message;
157+
alertContainer.className = `alert alert-${wasSuccessful ? 'success' : 'danger'} fade show`;
158+
alertContainer.textContent = statusMessage;
143159
alertContainer.classList.remove('d-none');
144160
alertContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
145161
}
146162

147-
// If the form was submitted successfully, reset it
148-
if (success) {
163+
// Reset form on success
164+
if (wasSuccessful) {
149165
form.reset();
150166
form.classList.remove('was-validated');
151-
form.querySelectorAll('.is-valid, .is-invalid').forEach((el) => el.classList.remove('is-valid', 'is-invalid'));
167+
form.querySelectorAll('.is-valid, .is-invalid').forEach((el) => {
168+
el.classList.remove('is-valid', 'is-invalid');
169+
});
152170
}
153-
} catch (err) {
154-
console.error(err);
171+
} catch (error) {
172+
console.error(error);
173+
174+
// Show error alert
155175
if (alertContainer) {
156176
alertContainer.className = 'alert alert-danger fade show';
157-
alertContainer.textContent = err?.message || 'Unexpected error.';
177+
alertContainer.textContent = error?.message || 'Unexpected error.';
158178
alertContainer.classList.remove('d-none');
159179
}
160180
} finally {
161-
// Hide loading spinner and enable form
181+
// Re-enable form
162182
if (spinner) spinner.classList.add('d-none');
163183
if (submitButton) submitButton.disabled = false;
164-
form.querySelectorAll('input, select, textarea, button').forEach((el) => {
165-
if (el !== submitButton) el.disabled = false;
184+
form.querySelectorAll('input, select, textarea, button').forEach((element) => {
185+
if (element !== submitButton) element.disabled = false;
166186
});
167-
inFlight = false;
187+
isSubmitting = false;
168188
}
169189
});
170190
});

0 commit comments

Comments
 (0)