<section class="section">
<div class="container">
<div class="notification is-link hide">
<button class="delete"></button>
Stuff happened...
</div>
<form name="someform">
<div class="field">
<label class="label">Name:</label>
<div class="control">
<input name="name" class="input" type="text">
</div>
<ul data-errors="name">
</ul>
</div>
<div class="field">
<label class="label">Email:</label>
<div class="control">
<input name="email" class="input" type="text">
</div>
<ul data-errors="email">
</ul>
</div>
<div class="control">
<button class="button is-info">Send</button>
</div>
</form>
</div>
</section>
.hide {
display: none;
}
function submit(event) {
event.preventDefault();
const input = collect_data(this);
const formdata = validate_form(input);
if (is_valid(formdata)) {
clear_old_errors();
toggle_notification();
} else {
Obj.map(show_errors, formdata);
}
}
function setup() {
document.forms.namedItem("someform").addEventListener("submit", submit);
document
.querySelectorAll("input")
.forEach((el) => el.addEventListener("blur", clear_errors));
document
.querySelector(".notification .delete")
.addEventListener("click", toggle_notification);
}
/*
*
* Fantasy Land Utils
*
*/
const Obj = {
map(fn, data) {
const result = {};
for (let key in data) {
result[key] = fn(data[key]);
}
return result;
},
ap(Fn, Data) {
const result = {};
for (let key in Data) {
result[key] = Fn[key](Data[key]);
}
return result;
}
};
/*
*
* Validations
*
*/
const validate_form = Obj.ap.bind(null, {
name: validate.bind(null, [
[long_enough, "Come on, try again."],
[no_numbers, "Don't get smart. No numbers."]
]),
email: validate.bind(null, [
[long_enough, "Am I a joke to you?"],
[is_email, "Totally not an email."]
])
});
function long_enough(input) {
return input.length >= 2;
}
function is_email(input) {
return input.includes("@");
}
function no_numbers(input) {
return !/\d/.test(input);
}
function validate(validations, field) {
const result = {...field};
result.errors = [];
for (let [validation, msg] of validations) {
result.is_valid = validation(field.value);
if (!result.is_valid) {
result.errors.push(msg);
}
}
return result;
}
function is_valid(formdata) {
return Object.values(formdata).every((field) => field.is_valid);
}
/*
*
* DOM Utilities
*
*/
function collect_data(form_elm) {
const result = {};
const formdata = new FormData(form_elm);
for (let entry of formdata.entries()) {
result[entry[0]] = {
field: entry[0],
value: entry[1],
};
}
return result;
}
function show_errors(input) {
const el = document.querySelector(`[data-errors=${input.field}]`);
el.replaceChildren();
for (let msg of input.errors) {
const li = document.createElement("li");
li.classList.add("help", "is-danger");
li.textContent = msg;
el.appendChild(li);
}
}
function toggle_notification() {
document.querySelector(".notification").classList.toggle("hide");
}
function clear_errors() {
const name = this.getAttribute("name");
document.querySelector(`[data-errors=${name}]`).replaceChildren();
}
function clear_old_errors() {
document.querySelectorAll("input").forEach((el) => clear_errors.call(el));
}
setup();
This Pen doesn't use any external JavaScript resources.