Files
alrahma_sunday_school/public/assets/js/validation.js
T
2026-02-10 22:11:06 -05:00

172 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
document.addEventListener('DOMContentLoaded', () => {
/* ---------- helpers ---------- */
const form = document.getElementById('registrationForm'); // <— pick the specific form
if (!form) { // <— bail out if not on this page
console.debug('validation.js: no #registrationForm — skipping init');
return;
}
const el = name => form.querySelector(`[name="${name}"]`);
const allInputs = form.querySelectorAll('input, select');
/* ---------- regex rules ---------- */
const nameRE = /^[A-Za-z\s-]{2,30}$/;
const phoneRE = /^\(?\d{3}\)?[-\s]?\d{3}[-\s]?\d{4}$/;
const emailRE = /^(?=.{2,50}$)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
const streetRE = /^[A-Za-z0-9.\s-]{2,30}$/;
const aptRE = /^[A-Za-z0-9.\s-]{0,15}$/;
const cityRE = /^[A-Za-z0-9\s.'-]{2,15}$/;
const zipRE = /^\d{5}$/;
const captchaRE= /^[A-Za-z0-9]{4,10}$/;
const formatHints = {
firstname: "Only letters, spaces, or dashes",
lastname: "Only letters, spaces, or dashes",
email: "Format: user@example.com",
confirm_email: "Must match the email exactly",
cellphone: "Exactly 10 digits (e.g., 1234567890 , (123) 456-4789)",
address_street: "Letters, numbers, dots, dashes",
apt: "Optional - Only letters, spaces, or dashes",
city: "Letters and spaces",
zip: "Exactly 5 digits (e.g., 12345)",
captcha: "410 letters or digits"
};
const validateField = (input) => {
const v = input.value.trim();
let ok = true;
switch (input.name) {
case 'firstname':
case 'lastname':
case 'second_firstname':
case 'second_lastname':
ok = nameRE.test(v); break;
case 'cellphone':
case 'second_cellphone':
ok = phoneRE.test(v); break;
case 'email':
case 'second_email':
ok = emailRE.test(v); break;
case 'confirm_email':
ok = v === (el('email')?.value ?? ''); break; // <— guard
case 'address_street':
ok = streetRE.test(v); break;
case 'apt':
ok = !v || aptRE.test(v); break;
case 'city':
ok = cityRE.test(v); break;
case 'zip':
ok = zipRE.test(v); break;
case 'captcha':
ok = captchaRE.test(v); break;
}
input.classList.toggle('is-invalid', !ok);
return ok;
};
/* ---------- live validation & re-check confirm_email ---------- */
allInputs.forEach(inp => {
const hint = document.getElementById(`${inp.name}-hint`);
inp.addEventListener('input', () => {
if (inp.disabled) return;
validateField(inp);
// Show hint while typing
if (hint) hint.classList.remove('d-none');
// Special revalidation for confirm_email
if (inp.name === 'email') {
const conf = el('confirm_email');
if (conf && !conf.disabled) validateField(conf);
}
});
inp.addEventListener('blur', () => {
const hint = document.getElementById(`${inp.name}-hint`);
if (hint) hint.classList.add('d-none');
});
});
/* ---------- copy / paste blocking with inline warnings ---------- */
const blockPasteConfig = [
{ id: 'confirm_email', warnId: 'copy-warning-email',
msg: 'Copy/paste is disabled for this field. Please type the answer.' },
{ id: 'captcha', warnId: 'captcha-warning',
msg: 'Copy/paste is disabled for this field. Please type the answer.' }
];
blockPasteConfig.forEach(({ id, warnId, msg }) => {
const input = document.getElementById(id);
if (!input) return;
let warnBox = document.getElementById(warnId);
if (!warnBox) {
warnBox = document.createElement('div');
warnBox.id = warnId;
warnBox.className = 'invalid-feedback d-none';
warnBox.textContent = msg;
input.parentElement.appendChild(warnBox);
}
const showWarn = (e) => {
e.preventDefault();
warnBox.classList.remove('d-none');
input.classList.add('is-invalid');
setTimeout(() => {
warnBox.classList.add('d-none');
input.classList.remove('is-invalid');
}, 3000);
};
['paste', 'copy', 'cut'].forEach(evt => input.addEventListener(evt, showWarn));
});
/* ---------- second-parent toggle logic ---------- */
const isParentCheckbox = document.getElementById('is_parent');
const secondSection = document.getElementById('second-parent-section');
const noSecondToggle = document.getElementById('no_second_parent_info');
const secondParentFields = document.getElementById('second-parent-fields');
const setSecondDisabled = (dis) => {
if (!secondParentFields) return; // <— guard
secondParentFields.querySelectorAll('input, select').forEach(f => {
f.disabled = dis;
if (dis) f.classList.remove('is-invalid');
});
};
const refreshSecondUI = () => {
if (!secondSection) return; // <— guard
const parent = !!isParentCheckbox?.checked;
const noInfo = !!noSecondToggle?.checked;
secondSection.style.display = parent ? 'block' : 'none';
setSecondDisabled(!parent || noInfo);
};
isParentCheckbox?.addEventListener('change', refreshSecondUI);
noSecondToggle?.addEventListener('change', refreshSecondUI);
refreshSecondUI();
/* ---------- submit event ---------- */
form.addEventListener('submit', e => {
let ok = true;
allInputs.forEach(inp => {
if (inp.disabled) return;
const inSecond = secondParentFields && inp.closest('#second-parent-fields');
if (inSecond && (!isParentCheckbox?.checked || noSecondToggle?.checked)) return;
if (!validateField(inp)) ok = false;
});
if (!ok) {
e.preventDefault();
form.querySelector('.is-invalid')?.focus({ preventScroll: true });
}
});
});