Published
Edited
Apr 28, 2021
1 fork
1 star
Insert cell
Insert cell
formData.values
Insert cell
Insert cell
initialValues = ({
// color: '#0000FF'
fruit : 'none',
firstName : 'John',
lastName : 'Smith',
message : 'Hello, there!',
color: '#FF0000',
employed : true
})
Insert cell
function validate(values) {
const errors = {}
if (!values.firstName) {
errors.firstName = 'Required'
}
if (!values.lastName) {
errors.lastName = 'Required'
}
if (values.color === '#00FF00') {
errors.color = 'Gross! Not green! 🤮'
}
if (!(values.message || '').trim()) {
errors.message = 'Message should not be empty!';
}
if (!values.fruit || values.fruit === 'none') {
errors.fruit = 'You should eat fruits!';
}
if (!values.letters || (Object.keys(values.letters).length !== 2)) {
errors.letters = { message : 'You have to choose two letters.' };
}
if (Object.keys(errors).length) {
errors.formError = 'Form is not valid!';
}
return errors;
}
Insert cell
// This method visualizes error.
// Additionally to a default error visualization method
// it activates a red border around invalid forms.
function 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 = ''
}
formView.style.border = errors.formError ? '1px solid red' : '1px solid transparent';
}
Insert cell
Insert cell
formData = Generators.observe(notify => {
let lastInfo = {};
return formValidator(formView, {
initialValues,
validate,
updateErrorViews,
onSubmit() {
lastInfo.submit = true;
notify(lastInfo);
},
onUpdate(info) {
lastInfo = info;
lastInfo.submit = false;
notify(lastInfo);
}
});
});

Insert cell
createForm = {
const { createForm } = await import('final-form');
return createForm;
}
Insert cell
function formValidator(formView, {
initialValues = {},
onSubmit = () => {},
onUpdate = () => {},
validate,
updateParams = {
// The list of values you want to be updated about
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();
}
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more