diff --git a/static/js/form.js b/static/js/form.js index a63b972..3aece95 100644 --- a/static/js/form.js +++ b/static/js/form.js @@ -35,5 +35,13 @@ function renderForm(fields) { wrapper.appendChild(label); wrapper.appendChild(input); form.appendChild(wrapper); + + if (typeof attachValidation === 'function') { + attachValidation(input, field.validation || {}); + } }); + const submit = document.createElement('button'); + submit.type = 'submit'; + submit.textContent = 'Submit'; + form.appendChild(submit); } diff --git a/static/js/validation.js b/static/js/validation.js new file mode 100644 index 0000000..18e1734 --- /dev/null +++ b/static/js/validation.js @@ -0,0 +1,76 @@ +const fieldErrors = {}; + +function isValidPhone(value) { + const digits = value.replace(/\D/g, ''); + return /^\d{10}$/.test(digits); +} + +function isValidSSN(value) { + return /^\d{3}-\d{2}-\d{4}$/.test(value); +} + +function withinLength(value, max = 15) { + return value.length <= max; +} + +function validateField(input, rules = {}) { + const value = input.value.trim(); + const name = input.name; + const errors = []; + + if (rules.required && !value) { + errors.push(`${name} is required`); + } + + if (value && !withinLength(value)) { + errors.push(`${name} must be at most 15 characters`); + } + + if (name.toLowerCase().includes('phone') && value && !isValidPhone(value)) { + errors.push('Phone number must be 10 digits'); + } + + if (name.toLowerCase().includes('ssn') && value && !isValidSSN(value)) { + errors.push('SSN must match ###-##-####'); + } + + if (rules.minLength && value.length < rules.minLength) { + errors.push(`${name} must be at least ${rules.minLength} characters`); + } + + if (rules.min !== undefined && value !== '' && Number(value) < rules.min) { + errors.push(`${name} must be at least ${rules.min}`); + } + + if (rules.max !== undefined && value !== '' && Number(value) > rules.max) { + errors.push(`${name} must be at most ${rules.max}`); + } + + if (errors.length) { + fieldErrors[name] = errors; + } else { + delete fieldErrors[name]; + } + renderErrors(); +} + +function renderErrors() { + const container = document.getElementById('error-messages'); + if (!container) return; + container.innerHTML = ''; + Object.values(fieldErrors).forEach((errs) => { + errs.forEach((err) => { + const p = document.createElement('p'); + p.textContent = err; + container.appendChild(p); + }); + }); +} + +function attachValidation(input, rules) { + input.addEventListener('input', () => validateField(input, rules)); + input.addEventListener('blur', () => validateField(input, rules)); +} + +// expose to global scope +window.attachValidation = attachValidation; diff --git a/templates/index.html b/templates/index.html index 9738e57..b3877b4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,10 +3,12 @@ Dynamic Form +

Dynamic Form

+