function formValidator(formView, {
initialValues = {},
onSubmit = () => {},
onUpdate = () => {},
validate,
updateParams = {
dirty: true,
valid: true,
values: true
},
updateResetView = (formView, element, info) => {
element.disabled = !info.dirty;
},
updateSubmitView = (formView, element, info) => {
element.disabled = !info.valid;
},
updateErrorViews = (formView, errors) => {
[...formView.querySelectorAll(`[data--form-error]`)].forEach(errorElement => {
errorElement.innerHTML = '';
errorElement.classList.remove('error');
errorElement.style.display = 'none'
})
for (let [name, error] of Object.entries(errors)) {
const errorElement = formView.querySelector(`[data--form-error="${name}"]`)
if (!error || !errorElement) continue;
errorElement.innerHTML = typeof error === 'object' ? error.message : error;
errorElement.classList.add('error');
errorElement.style.display = ''
}
},
submitSelector = '[type="submit"]',
resetSelector = '[type="button"]'
} = {}) {
const form = createForm({ onSubmit, initialValues, validate });
function addEventListener(input, event, handler) {
input.addEventListener(event, handler);
return () => input.removeEventListener(event, handler);
}
const addButtonListener = (element, event, handler) => {
return { element, unregister : addEventListener(element, event, handler) }
}
const handleSubmit = event => {
event.preventDefault();
form.submit();
};
const submitRegistratoins = [...formView.querySelectorAll(submitSelector)]
.map(button => addButtonListener(button, 'click', handleSubmit));
const handleReset = (event) => {
event.preventDefault();
form.reset();
}
const resetRegistratoins = [...formView.querySelectorAll(resetSelector)]
.map(button => addButtonListener(button, 'click', handleReset));
const fieldsRegistrations = new Map();
function registerField(input) {
const { name } = input;
if (!name) return ;
form.registerField(
name,
fieldState => {
const { blur, change, error, focus, touched, value } = fieldState;
if (!fieldsRegistrations.has(input)) {
let onInput;
if (input.type === 'radio') {
onInput = (event) => event.target.checked && change(event.target.value);
} else if (input.type === 'checkbox') {
onInput = (event) => change(event.target.checked ? true : undefined);
} else {
onInput = (event) => change(event.target.value)
}
const regs = [
addEventListener(input, 'blur', () => blur()),
addEventListener(input, 'input', onInput),
addEventListener(input, 'focus', () => focus())
];
fieldsRegistrations.set(input, {
element : input,
unregister : () => regs.forEach(r => r())
})
}
// update value
if (input.type === 'checkbox') { input.checked = value; }
else if (input.type === 'radio') { input.checked = value === input.value; }
else { input.value = value === undefined ? '' : value }
},
{
value: true,
error: true,
touched: true
}
)
}
[...formView].forEach(registerField);
// Subscribe to form state updates
let prevDirty, prevValid, prevErrors = {};
const updateHandler = (info) => {
if (prevDirty !== info.dirty || prevValid !== info.valid) {
submitRegistratoins.forEach(({ element }) => updateSubmitView(formView, element, info));
resetRegistratoins.forEach(({ element }) => updateResetView(formView, element, info));
prevDirty = info.dirty;
prevValid = info.valid;
}
const { errors = {} } = form.getState();
updateErrorViews(formView, errors);
onUpdate(info);
}
const unsubscribe = form.subscribe(updateHandler, updateParams);
return () => {
unsubscribe();
submitRegistratoins.forEach(r => r.unregister());
resetRegistratoins.forEach(r => r.unregister());
for (const r of fieldsRegistrations.values()) {
r.unregister();
}
}
}