form framework
Based on AngularJS
by Richard Hunter
January 05, 2021
HTML
<div class="input-field">
<input id="first-name" class="input" />
<div class="help-text">Put your first name here</div>
<div class="error-message">
An error occurred
</div>
</div>
<div class="input-field">
<input id="last-name" class="input" />
<div class="help-text">Put your last name here</div>
<div class="error-message">
An error occurred
</div>
</div>
CSS
.input {
border: solid 2px grey;
outline: none;
}
.error-message {
color: red;
display: none;
}
.input.is-invalid.is-touched {
border-color: red;
}
.input.is-invalid.is-touched ~ .error-message {
display: block;
}
.input.is-invalid.is-touched ~ .help-text {
display: none;
}
.input.is-valid.is-touched {
border-color: limegreen;
}
.input-field {
padding: 10px;
border: 1px grey solid;
margin: 10px;
}
JavaScript
function createWatcher(watcher, listener, data) {
let value = watcher(data);
listener(value, null, data);
return (data) => {
const newValue = watcher(data);
if (newValue !== value) {
listener(newValue, value);
value = newValue;
}
};
}
class State {
constructor(initial) {
this.data = {
...initial
};
this.watchers = [];
}
watch(watcher, listener) {
this.watchers.push(createWatcher(watcher, listener, this.data));
}
digest() {
this.watchers.forEach(watcher => {
watcher(this.data);
});
}
inputField(key, value, valid) {
this.data = {
...this.data,
[key]: {
...this.data[key],
value,
valid,
},
};
this.digest();
}
blurField(key) {
this.data = {
...this.data,
[key]: {
...this.data[key],
touched: true,
}
};
this.digest();
}
}
const firstNameInitialValue = '';
const lastNameInitialValue = '';
const state = new State({
firstName: {
value: firstNameInitialValue,
valid: isFirstNameValid(firstNameInitialValue),
touched: false,
errorMessage: 'An error occurred',
},
lastName: {
value: lastNameInitialValue,
valid: isLastNameValid(lastNameInitialValue),
touched: false,
errorMessage: 'You must include a last name',
}
});
state.watch((data) => {
return data.firstName.valid;
}, (newValue) => {
const input = document.querySelector('#first-name');
if (!newValue) {
input.classList.remove('is-valid');
input.classList.add('is-invalid');
} else {
input.classList.add('is-valid');
input.classList.remove('is-invalid');
}
});
state.watch((data) => {
return data.firstName.touched;
}, newValue => {
const input = document.querySelector('#first-name');
if (newValue) {
input.classList.add('is-touched');
input.classList.remove('is-untouched');
} else {
input.classList.add('is-untouched');
...