// Notes, 29-03-2008:
// 
// Relation and class attributes:
// The relation attribute is not w3 valid, therefore I added possible use of classname as trigger of the field validation.
// This means that a field will be tested if one of the two attributes is present, ex: class="validate.text" or relation="validate.text"
// 
// Radios and checkboxes:
// Now also radios are checked if relation/class attribute is "validate.checked" (same as checkboxes).
// Radios and checkboxes are added a declineFormAlertColor colored border if fail test.
// 
// Extra tests:
// I added two extra tests: cpr and dateoption.
// 'cpr' is the danish social security number.
// 'dateoption' test for user typed date formats: 10-5-2001, 10-5-01. The separators can be ' /.-' (SPACE,SLASH,PERIOD and HYPHEN)
// If dateoption test is passed the field value is replaced with the string format 20010510.
// Actually all non digit characters - as well as multiples - are accepted as separator. Thus a string like 10 a%(5+/*01 will pass and return 20010510
//
// New, 31-03-2008:
// Once validation test fails, the marked fields are tested on the fly, for instance if checkbox is checked the marking disappears instantly.
// There's a test here: http://edit.kost.dk/_html/formvalidate.html
//
// New, 14-05-2008:
// Add optional 'yyyymmdd' format to mustBeDate. Now method 'dateoption' checks for 'yyyymmdd', and method 'date' checks for 'yyyy-mm-dd'.
// Add default alert-variables.
//
// 15-09-2008:
// Add date check methods dd-mm-yyyy and yyyymmdd (the latter is same as dateoption, but the meaning of that name is rather wague).
// Fix a bug in date check. Change the date check method. It now checks against the js Date object instead of checking on string construction.
// This should give a more reliable test.
// Also offer more flexibility when typing year: If year is less than 1900, it is probably typed in as 89 or 07.
// Numbers less than 36 are then seen as after 2000, and the rest as before. Three digits numbers return false.
// Still, when a date passes the test, the prober formatted date is put into the field.

default_declineFormAlert = 'Form could not be sent. Please check the marked fields.';
default_declineFormAlertColor = '#ffcc00';
default_acceptedFormColor = '#ffffff';
now = new Date;
year = now.getFullYear();
year2 = parseInt((''+year).substring(3,4));
month = '0'+now.getMonth();
month = month.substring(month.length-2,month.length);
day = '0'+now.getDate();
day = day.substring(day.length-2,day.length);
validateOnTheFly = false;

// Validating email address
re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
numbs = /[\D ]+/;
notnumbs = /[^\d-]+/;

function validateForm(f) {
	acceptedFormColor=(typeof(acceptedFormColor)=='undefined')?default_acceptedFormColor:acceptedFormColor;
	declineFormAlert=(typeof(declineFormAlert)=='undefined')?default_declineFormAlert:declineFormAlert;
	declineFormAlertColor=(typeof(declineFormAlertColor)=='undefined')?default_declineFormAlertColor:declineFormAlertColor;
	if (f&&typeof(validateFormObj)=='undefined') validateFormObj=f
	resulttext = '';
	result = 0;
	fields = validateFormObj.elements;
	validateRadios = new Array();
	for (i=0;i<fields.length;i++) {
		if (fields[i].type=='radio') {
			try {
				if (validateRadios.indexOf(fields[i].name)>-1) continue;
				else validateRadios.push(fields[i].name);
			} catch(err) {
				inarray = false;
				for (a=0;a<validateRadios.length;a++) { if (fields[i].name==validateRadios[a]) inarray = true; break; }
				if (!inarray) {validateRadios.push(fields[i].name);}
				else continue;
				}
			}
		rel = fields[i].className;
		method = false;
		methods = false;
		nullValue = false;
		if (rel) { 
			methods = rel.split('.');
			if ((methods[0]=='validate')&&(methods.length>1)) method = methods[1];
			}
		if ((!method)&&(fields[i].getAttribute('relation'))) {
			methods = fields[i].getAttribute('relation').split('.');
			if ((methods[0]=='validate')&&(methods.length>1)) method = methods[1];
			}
		if (method&&methods) {
			if (methods.length==3) nullValue = methods[2];
			if (method=='text') result += alertField(fields[i],!isEmpty(fields[i].value));
			else if (method=='notnull') result += alertField(fields[i],mustBeSomething(fields[i]));
			else if (method=='integer') result += alertField(fields[i],mustBeInteger(fields[i]));
			else if (method=='integer_or_empty') result += alertField(fields[i],mustBeIntegerOrEmpty(fields[i]));
			else if (method=='float') result += alertField(fields[i],mustBeFloat(fields[i]));
			else if (method=='float_or_empty') result += alertField(fields[i],mustBeFloatOrEmpty(fields[i]));
			else if (method=='passwd') result += alertField(fields[i],mustBePasswd(fields[i].value));
			else if (method=='checked') result += (mustBeChecked(fields[i]))?0:1;
			else if (method=='email') result += alertField(fields[i],mustBeEmail(fields[i].value));
			else if ((method=='select')&&!(!nullValue)) result += alertField(fields[i],!(isEmpty(fields[i].value)||(fields[i].value==nullValue)));
			else if (method=='select') result += alertField(fields[i], !(isEmpty(fields[i].value)));
			else if (method=='other') result += alertField(fields[i], mustBeOther(fields[i])); // Must de defined in local JavaScript
			else if (method=='cpr') result += alertField(fields[i],testcpr(fields[i]));
			else if (method=='dateoption') result += alertField(fields[i],mustBeDate(fields[i],1,'yyyymmdd'));
			else if (method=='dd-mm-yyyy') result += alertField(fields[i],mustBeDate(fields[i],1,'dd-mm-yyyy'));
			else if (method=='yyyymmdd') result += alertField(fields[i],mustBeDate(fields[i],1,'yyyymmdd'));
			else if (method=='date') result += alertField(fields[i],mustBeDate(fields[i],1));
			}
		}
	if (result>0&&!validateOnTheFly) {
		alert(declineFormAlert);
		return false;
		}
	return true;
	}
function mustBeEmail(v){
	// From JavaScript for the WWW, ISBN0-201-73517-2, page 162
	return re.test(v);
	}
function mustBeChecked(cb) {
	radio2check = false;
	if (cb.type=='radio') {
		radio2check = cb.form[cb.name];
		mbc = false;
		if (radio2check.length) {
			for (r=0;r<radio2check.length;r++) {
				if (radio2check[r].checked==true) { mbc=true; break; }
			}}
		else if (radio2check.checked==true) { mbc=true; }
		}
	else mbc = cb.checked;
	if (radio2check) {
		radio2checks = new Array();
		// Since we clone the radio2ckeck element and delete the original the list radio2check is changes
		// during the marking. Thus we need to collect them in a list that doesn't change.
		if (radio2check.length) {
			for (xr=0;xr<radio2check.length;xr++) radio2checks.push(radio2check[xr]);
			for (xr=0;xr<radio2checks.length;xr++) _fv_markCheckboxAndRadio(mbc,radio2checks[xr]);
			}
		else {
			radio2checks.push(radio2check);
			_fv_markCheckboxAndRadio(mbc,radio2checks[0]);
			}
		
		}
	else _fv_markCheckboxAndRadio(mbc,cb);
	return (mbc)?1:0;
	}
function _fv_fieldValidateOnTheFly(e) {
	var targ;
	if (!e) var e = window.event;
	if (e.target) targ = e.target;
	else if (e.srcElement) targ = e.srcElement;
	if (targ.nodeType == 3) // defeat Safari bug
		targ = targ.parentNode;
	validateOnTheFly = true;
	res = validateForm(targ.form);
	validateOnTheFly = false;
	}
function _fv_markCheckboxAndRadio(mbc,cb) {
	failname = cb.name+'_failvalidate';
	if (mbc&&cb.parentNode.id==failname) {
		spanObj = cb.parentNode
		clone = cb.cloneNode(false)
		clonecheck = cb.checked;
		addObjectEventListener('click', _fv_fieldValidateOnTheFly,clone)
		spanObj.parentNode.replaceChild(clone,spanObj);
		clone.checked = clonecheck;
		}
	else if (!mbc&&cb.parentNode.id!=failname) {
		spanObj = document.createElement('span')
		spanObj.style.backgroundColor=declineFormAlertColor;
		spanObj.style.padding="2px";
		spanObj.id=failname;
		clone = cb.cloneNode(false);
		clonecheck = cb.checked;
		spanObj.appendChild(clone);
		cb.parentNode.replaceChild(spanObj,cb);
		clone.checked = clonecheck;
		addObjectEventListener('click', _fv_fieldValidateOnTheFly,clone)
		}
	}
function addObjectEventListener(event, f, object) {
	if(!object) { object=window; }
	if (navigator.platform == "Win32" && navigator.appName == "Microsoft Internet Explorer" && object.attachEvent) object.attachEvent("on"+event, f);
	else object.addEventListener(event,f,false);
	}

function mustBeIntegerOrEmpty() {
	// @@@ Clode doubles partly with mustBeInteger(). Clean up later
	args = mustBeIntegerOrEmpty.arguments;
	v = args[0];
	if (isEmpty(v.value)) return true;
	val = v.value;
	while ((val.charAt(0)=='0')&(val!='0')) val = val.substring(1,val.length);
	valint = parseInt(val);
	if (isNaN(valint)||valint!=val) {
		v.value = ''
		return false;
		};
	val = ''+valint;
	if((!val)&&(val!='0')) val='';
	if ((args.length==2)&(val!='')) {
		val = numbs.substring(0,4-val.length) + val;
		}
	v.value = val;
	if (v.value == val) return true;
	return false;
	}

function mustBeSomething() { 
	args = mustBeSomething.arguments;
	v = args[0];
	if (isEmpty(v.value)) return false;
	val = v.value;
	valint = parseInt(val);
	if (isNaN(valint)||valint==0||valint!=val) return false;
	return true;
	}

function mustBeInteger() { 
	args = mustBeInteger.arguments;
	v = args[0];
	if (isEmpty(v.value)) return false;
	val = v.value;
	while ((val.charAt(0)=='0')&(val!='0')) val = val.substring(1,val.length);
	valint = parseInt(val);
	if (isNaN(valint)||valint!=val) {
		v.value = ''
		return false;
		};
	val = ''+valint;
	if((!val)&&(val!='0')) val='';
	if ((args.length==2)&(val!='')) {
		val = numbs.substring(0,4-val.length) + val;
		}
	v.value = val;
	if (v.value == val) return true;
	return false;
	}

function mustBeFloatOrEmpty(field,recalc) {
	args = mustBeFloatOrEmpty.arguments;
	if (!field) return true;
	val=field.value;
	if (isEmpty(val)) return true;
	val = isFloat(val);
	if (!val) return true;
	if (val == field.value) return true;
	field.value = val;
	return true;
	}
	
function mustBeFloat(field,recalc) {
	args = mustBeFloat.arguments;
	if (!field) return false;
	val=field.value;
	if (isEmpty(val)) return false;
	val = isFloat(val);
	if (!val) return false;
	if (val == field.value) return true;
	field.value = val;
	return false;
	}

function mustBePasswd(pw) {
	if (!pw) return false;
	if ((pw == null) || (pw.length < 8)) return false;
	return true;
	}

function isEmpty(s){
	return ((s == null) || (s.length == 0))
}

function isFloat(v){
	if (parseFloat(v)+'' == v) return v;
	dotpos = val.indexOf('.');
	dotcomma = val.indexOf(',');
	if (dotcomma > dotpos) {
		v = v.replace('.','');
		v = v.replace(',','.');
		}
	else {
		v = v.replace(',','.');
		v = v.replace('.','');
		}
	if (parseFloat(v)+'' == v) return v;
	return false;
	}

function alertField(field,option) { 
	if (option) { field.style.backgroundColor=acceptedFormColor; return 0;}
	else { 
		field.style.backgroundColor=declineFormAlertColor;
		addObjectEventListener('change', _fv_fieldValidateOnTheFly, field);
		return 1;
		}
	}

function mustBeDate(f,optional,format) {
	d = _testdateFormat(f.value,optional,format);
	if (d) {
		if (format=='yyyymmdd') f.value = d[0] + d[1] + d[2];
		else if (format=='dd-mm-yyyy') f.value = d[2] +'-'+ d[1] +'-'+ d[0];
		else f.value = d[0] +'-'+ d[1] +'-' + d[2];
		return true;
		}
	return false;
	}

function _testdateFormat(v,optional,format) {
	if (optional&&!v) return true;
	if (format=='yyyymmdd') {
		if (v.length!=8) return false;
		d = [v.substr(0,4),v.substr(4,2),v.substr(6,2)]
		}
	else if (format=='dd-mm-yyyy') {
		d = v.split('-');
		if (d.length!=3) return false;
		else d.reverse()
		}
	else if (!format) {
		d = v.split('-');
		if (d.length!=3) return false;
		}
	while (d[0].charAt(0)=='0') d[0] = d[0].substring(1);
	while (d[1].charAt(0)=='0') d[1] = d[1].substring(1);
	while (d[2].charAt(0)=='0') d[2] = d[2].substring(1);
	if (d[0]=='') d[0]=0;
	if (d[1]=='') d[1]=0;
	if (d[2]=='') d[2]=0;
	d[0]=parseInt(d[0]);
	d[1]=parseInt(d[1]);
	d[2]=parseInt(d[2]);
	if (d[0]<1900) {
		// ... then years probably typed in as 89 or 07.
		// numbers less than 36 are then seen as after 2000,
		// and the rest as before. Numbers greater than 99 
		// are considered as an error, though.
		if (d[0]>99) return false
		else d[0]=(d[0]<36)?2000+d[0]:1900+d[0];
		}
	testdate = new Date();
	testdate.setYear(d[0]);
	testdate.setMonth(d[1]-1);
	testdate.setDate(d[2]);
	
	if (testdate.getFullYear()==d[0]&&(testdate.getMonth()+1)==d[1]&&testdate.getDate()==d[2]) {
		d[1]=(d[1]<10)?'0'+d[1]:d[1];
		d[2]=(d[2]<10)?'0'+d[2]:d[2];
		return d;
		}
	return false;
	}

function testcpr(field) {
	if (!field.value) return false;
	fstr = "4327654321";
	if (!field.length) {
		cstr = _stripStringChars(field.value,numbs,'')
		c = ''; ctl = 1;
		check = 0;
		for (n=0;n<10;n++) {
			if (cstr=='') { ctl = 0; return false; }
			check = check + (fstr.charAt(n) * cstr.charAt(n));
			}
		a = Math.round(check/11.0);
		b = check/11.0;

		field.value = cstr;

		if ((a!=b)||(ctl==0)) {
			alert('Skemaet blev ikke sendt:\nDer er noget galt med et cpr-nummer');
			return false;
			}
		}
	else {
		cf = field.length
		for (m=0;m<cf;m++) {
			cstr = _stripStringChars(field[m].value,numbs,'')
			c = ''; ctl = 1;
			check = 0;
			for (n=0;n<10;n++) {
				if (cstr=='') { ctl = 0; }
				check = check + (fstr.charAt(n) * cstr.charAt(n));
				}
			a = Math.round(check/11.0);
			b = check/11.0;
			field[m].value = cstr;
			if ((a!=b)||(ctl==0)) {
				alert('Skemaet blev ikke sendt:\nDer er noget galt med et cpr-nummer');
				return false;
				}
			}
		}
	return true;
	}

function _stripStringChars(s,replacePattern,replaceWith) {
	while (replacePattern.test(s)) s = s.replace(replacePattern,replaceWith);
	return s;
	}

