Html:
Here's an approach that uses some approaches that are more modern than inline event handlers (e.g., onchange=""). This is driven by data- attributes, as you will see, and although it is not currently configured to handle anything other than an input[type=text] right now, it could be expanded to work with selects, textareas, etc.
What is below makes no pretenses to work with IE8 and lower, due to those versions using attachEvent instead of addEventListener to setup the event handlers. Again, this could be worked in, but it does work in all other modern browsers. It should work in IE9, although that is untested.
It may look like a lot going on, but take a look at it and see if you can work out how it operates. Feel free to ask me any questions you like, as well.
Here is a fiddle (tested in Chrome and Firefox):
http://jsfiddle.net/ndXTb/
HTML
CSS
#signup {
display: table;
}
#signup form > p {
display: table-row;
}
#signup p > label,
#signup p > span {
display: table-cell;
font-weight: bold;
padding: 5px;
}
#signup p > label {
text-align: right;
width: 150px;
}
.validationerror input {
border: 1px solid #a00;
background-color: #ffd;
padding: 2px 1px;
}
.validationerror:after {
content: '!';
}
Javascript
window.addEventListener('load', function init(){
var signup = document.getElementById('signup'),
fields = signup.getElementsByClassName('required'),
errors = document.getElementById('errors'),
error = '- {error}
',
submitted = false,
errorlog = [],
index = 0,
field,
focusin;
signup.addEventListener('submit', validateform);
while (field = fields[index++]) {
field.addEventListener('blur', validatefield);
field.addEventListener('keyup', validatefield);
}
function validatefield() {
var message = this.dataset['validateError'],
sort = this.dataset['errorSort'],
parent = this.parentNode;
if (this.value === '' && (message && sort)) {
errorlog[sort] = error.replace('{error}', message);
parent.className += ' validationerror';
if (!focusin) {
focusin = this;
}
} else if (this.value !== '' && (message && sort)) {
delete errorlog[sort];
parent.className = parent.className.replace('validationerror', '');
if (focusin == this) {
focusin = null;
}
}
if (!submitted) {
isvalid();
}
}
function validateform(event) {
index = 0;
errorlog = [];
focusin = null;
submitted = true;
while (field = fields[index++]) {
callevt(field, 'focus');
callevt(field, 'blur');
}
submitted = false;
if (!isvalid()) {
if (focusin) {
focusin.focus();
}
focusin = null;
event.preventDefault();
return false;
}
}
function isvalid() {
errors.innerHTML = '';
if (errorlog.length) {
errors.innerHTML = errorlog.join('');
return false;
}
return true;
}
function callevt(el, type) {
var evt = document.createEvent('HTMLEvents');
evt.initEvent(type, true, true);
el.dispatchEvent(evt);
}
});