/*
Name: formValidator 1.52,  http://slingfive.com/pages/code/formValidator/
Desc: automates most basic form validation tasks, via custom attributes
Author: Rob Eberhardt, Slingshot Solutions, http://slingfive.com/
Created: ~2002
Revision History:
	2004-03-24: 1st public release; added "friendly" public interface procs; fixed
new minLength bug
	2004-02-20: fixed minlength check when value is null (value+''.length)
	2004-01-26: fixed istime check, improved to handle shorthand times
	2004-01-22: fixed required & minValue/maxValue bugs


====CHECKS====
data types:
	date, datetime, time, boolean, numeric, integer, float, general (little validation)
data formats:
	email, phone, ssn, postal code/zipcode, creditcard, FORBID

====OTHER OPTIONS====
required, minlength, minValue, maxValue



====USAGE====
see formValidation.html demo

1.set datatype attribute on elements:
		<input type="text" name="NumTeeth" datatype="numeric" />

2.set minValue/maxValue attributes on elements:
		<input type="text" name="NumTeeth" datatype="numeric" minValue="0" />

3.set required attribute on required elements:
		<input type="text" name="NumTeeth" datatype="numeric" required="true" />

4.call validateField to check datatype during data entry (optional):
		<input type="text" name="NumTeeth" datatype="numeric"
onchange="validateField(this)" />

5.call validateForm when ready to run full check:
		form1.onsubmit = new function(){
			return validateForm(form1)
		}


*/


/*
================================================================
public interfaces
================================================================
<Form Name="frmTest" onSubmit="return validateForm(frmTest)">
First Name: <Input type=Text Name=FirstName required="True" minlength="2" maxlength="25" title="First Name" value=""><BR><BR>
Last Name:  <Input type=text name=LastName required="True" minlength="2" maxlength="25" title="Last Name" validate="req|2|25|varchar" value=""><BR><BR>
*/

//Cedar
var forbiddenChars = "";
//var forbiddenChars = "~!@#$%^&*()_+|}{:<>?`'-=[]\;,./"



function validateForm(p_oForm){
	return fnValidateForm(p_oForm);
}
function validateField(p_oField){
	return fnCheckValidField(p_oField);
}
function showFailedField(p_oField, p_strErrMessage){
	return showErrField(p_oField, p_strErrMessage);
}





/*
================================================================
private interfaces
================================================================
*/


// loops through form, checks each field with fnCheckRequiredField && fnCheckValidField,
// raises err alerts if(needed, returns overall validation status as boolean
function fnValidateForm(oForm){
	LockButtons(oForm, true)
	for(var e=0; e<oForm.elements.length; e++){
		var elem = oForm.elements[e];
		if(!fnCheckRequiredField(elem)){
			LockButtons(oForm, false)
			return false;
		}else if(!fnCheckValidField(elem)){
			LockButtons(oForm, false)
			return false;
		}else if(!fnCheckMinLengthField(elem)){
			LockButtons(oForm, false)
			return false;
		}else if(!fnCheckValueRange(elem)){
			LockButtons(oForm, false)
			return false;
		}
//Cedar
		if(!fnCheckForbiddenChars(elem)){
			LockButtons(oForm, false)
			return false;
		}
	}
	//alert("submitted successfully");
	LockButtons(oForm, false)
	return true;
}

// check if(required field has any data;
function fnCheckRequiredField(oFld){
	if(oFld.getAttribute("required") && oFld.value == ""){
		showErrField(oFld, "(This field is required)");
		return false;
	}
	return true;
}

//check if field's data matches its datatype, also checks max/min values;
function fnCheckValidField(oFld){
	var strFldDataType, bReturn, strMsgDetail;
	strFldDataType = oFld.getAttribute("datatype");
	bReturn = true;
	if(strFldDataType && strFldDataType != "" && oFld.value && oFld.value != ""){
		switch(strFldDataType.toLowerCase()) {
			case "float":
			case "numeric":	{bReturn = isNumeric(oFld.value);
				strMsgDetail = '\r\n' + "(value must be numeric)";
				break;
			}
			case "integer":	{bReturn = isInteger(oFld.value);
				strMsgDetail = '\r\n' + "(value must be an integer [whole number])";
				break;
			}
			case "date":
			case "datetime": {bReturn = isDate(oFld.value);
				strMsgDetail = '\r\n' + "(value must be a date)";
				if(bReturn && (oFld.type == "text" || oFld.tagName ==
"TEXTAREA")){oFld.value = FormatShortDate(cDate(oFld.value))}
				break;
			}
			case "time": {bReturn = isTime(oFld.value);
				strMsgDetail = '\r\n' + "(value must be a time)";
				if(bReturn && (oFld.type == "text" || oFld.tagName ==
"TEXTAREA")){oFld.value = cTime(oFld.value)}
				break;
			}
			case "boolean": {bReturn = isBoolean(oFld.value);
				strMsgDetail = '\r\n' + "(value must be boolean [true/false])";
				if(bReturn && (oFld.type == "text" || oFld.tagName ==
"TEXTAREA")){oFld.value = cBool(oFld.value)}
				break;
			}
			case "email": {bReturn = isEmail(oFld.value);
				strMsgDetail = '\r\n' + "(value must be in format 'UserName@DomainName.xxx')";
				break;
			}
			case "phone": {bReturn = isPhone(oFld.value);
				strMsgDetail = '\r\n' + "(these formats will work: ###.###.####, (###) ###-####)";
				break;
			}
			case "ssn":		{bReturn = isSSN(oFld.value);
				strMsgDetail = '\r\n' + "(format as ###-##-#### or #########)";
				break;
			}
			case "creditcard":		{bReturn = isCreditCard(oFld.value);
				strMsgDetail = '\r\n' + "(enter a valid number formatted as ####-####-####-####)";
				break;
			}
			case "postalcode":
			case "zipcode":	{bReturn = isPostalCode(oFld.value); break;}
		}

		if(!fnCheckValueRange(oFld)){return false};	// check min/max values
	}


	if(!bReturn){showErrField(oFld, strMsgDetail)};
	return bReturn;
}



// check that field's data meets its minlength;
function fnCheckMinLengthField(oFld){
	var iMinLength = oFld.getAttribute("minlength");
	if(iMinLength!=null && isNumeric(iMinLength) && (oFld.value+'').length <
cNum(iMinLength)){
		showErrField(oFld, "\r\n\(Enter at least " + iMinLength + " characters)");
		return false;
	}
	return true;
}


// check field's value against max/min values allowed
// no real datatype checking, simply rely on javascript's type assumptions
function fnCheckValueRange(oFld){
	if(oFld.value && oFld.value!=""){
		var minValue = oFld.getAttribute("minValue");
		var maxValue = oFld.getAttribute("maxValue");

		if(minValue && maxValue && (oFld.value < minValue || oFld.value > maxValue)){
// check full range
			showErrField(oFld, "\r\n\(Value must be in the range " + minValue + " to " + maxValue + ")");
			return false;
		}else if(minValue && oFld.value < minValue){	// if checking min val
			showErrField(oFld, "\r\n\(Value must be " + minValue + " or higher)");
			return false;
		}else if(maxValue&& oFld.value > maxValue){	// check max val
			showErrField(oFld, "\r\n\(Value must be " + maxValue + " or lower)");
			return false;
		}
	}
	return true;
}

// Cedar
//check field's value against forbidden character values
// no real datatype checking, simply rely on javascript's type assumptions
function fnCheckForbiddenChars(oFld){
	//alert(oFld.type)
	if(oFld.type != "hidden"){
		//alert("i am in " + oFld.type)
		if(!oFld.forbid){
			for(var i=0;i<oFld.value.length;i++){
				exceptionChars = checkCharException(oFld);
				if(forbiddenChars.indexOf(oFld.value.charAt(i))>0 && exceptionChars.indexOf(oFld.value.charAt(i))==-1){
					showErrField(oFld, "\r\n\(Value contains a forbidden character '" + oFld.value.charAt(i) + "')");
					return false;
				}
			}
		}
	}
	return true;
}

//Cedar
function checkCharException(oFld){
	if(oFld.datatype!=""){
		switch(oFld.datatype) {
			case "ssn":
				return "-"
			case "email":
				return ".@_-"
			case "datetime":
				return "-/:\.,"
			case "date":
				return "-/:\.,"
			case "time":
				return "-/:\.,"
			case "general":
				return ",.?/\':;!@#$%&*()_-+=~"
			default: return "";
		}
	}
}

//=====================
// utils
//=====================



// raise error && explanation to user, focus + highlight + focus field;
function showErrField(oFld, strMsgDetail){
	if(!strMsgDetail){strMsgDetail = ''}
	var strMsg, strElemHandle = fnGetFieldHandle(oFld);
	oFld.style.backgroundColor="#ff3030";
	if(oFld.tagName == "SELECT"){
		strMsg = "Please make a valid selection for '" + strElemHandle + "'." +
'\r\n';
	}else{;
		strMsg = "Please enter valid data for '" + strElemHandle + "'." + '\r\n';
	}
	oFld.focus();
	window.alert(strMsg + strMsgDetail, "Validation Error");
	oFld.style.backgroundColor="";
	window.setTimeout("document.all." + oFld.uniqueID + ".focus()", 0);
}

// gets Something to call the field, tries: 1.Title, 2.Name, 3:Id
function fnGetFieldHandle(oFld){
	var strElemHandle = oFld.title;
	if(strElemHandle == ""){strElemHandle = oFld.name;}
	if(strElemHandle == ""){strElemHandle = oFld.id;}
	return strElemHandle;
}




//=====================
// data-type checks
//=====================

function isDate(val){
	var dt = new Date(val);
	var dt1 = new Date(100,1,1);
	dt1.setFullYear(0);	// years <=0 are forced/assumed to 4 digit otherwise
	var dt2 = new Date(9999,12,31);
	return  dt1 < dt && dt < dt2;
}
function isDateTime(val){
	return isDate(val);	// ?
}
function isTime(val){
	var bRet = false;
	//TODO: check that >0
  if(val.length>1 || isInteger(val)){// single alpha chars get through
Date.parse()
		if(val.length<=5 && val.search(/[ ]*[ap]m/gi)!=-1){val = val.replace(/([]*[ap]m)/gi, ':00$1');}	// has am/pm, add minutes

		var dt = new Date();
		var parsedMS = Date.parse('' + FormatShortDate2(dt) + ' ' + val);	// year can't be 1st
		dt.setTime(parsedMS);
		bRet = isDate(dt) || (isInteger(val) && val>=0 && val<24);
	}
	return bRet;
}

function isNumeric(val){
	return !isNaN(val);
}
function isInteger(val){
	return isNumeric(val) && val.indexOf(".")==-1;
}
function isFloat(val){
	return isNumeric(val);
}
function isBoolean(val){	// true/false, 'true/false', #s, y/n
	return val==true || ((''+val).toLowerCase()=="true" ||
(''+val).toLowerCase()=="false") || isNumeric(val) ||
(''+val).toLowerCase()=='y' || (''+val).toLowerCase()=='n';
}






//=====================
// data-format checks
//=====================


function isEmail(val){
// regex based on http://regexlib.com/REDetails.aspx?regexp_id=356
	var re = new RegExp(/^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$/);
	return (re.exec(val)!=null);
}
function isSSN(val){
// regex based on http://www.regexlib.com/REDetails.aspx?regexp_id=535
	var re = new RegExp(/^(?!000)([0-6]\d{2}|7([0-6]\d|7[012]))([-])?(?!00)\d\d\3(?!0000)\d{4}$/);
	return (re.exec(val)!=null);
}
function isPhone(val){
// regex based on http://regexlib.com/REDetails.aspx?regexp_id=245
	var re = new RegExp(/^([0-1]([\s-./\\])?)?(\(?[2-9]\d{2}\)?|[2-9]\d{3})([\s-./\\])?(\d{3}([\s-./\\])?\d{4}|[a-zA-Z0-9]{7})$/);
	return (re.exec(val)!=null);
}
function isPostalCode(val){
	//regex based on http://regexlib.com/REDetails.aspx?regexp_id=23
	var re = new RegExp(/^(\d{5}-\d{4}|\d{5}|[A-Z]\d[A-Z] \d[A-Z]\d)$/);
	return (re.exec(val)!=null);
}
function isCreditCard(val){
// regex based on http://regexlib.com/REDetails.aspx?regexp_id=67 (also considered 455, 49)
// "the four major credit cards"
	var re = new RegExp(/^((4|5)\d{3}-?\d{4}-?\d{4}-?\d{4}|(4|5)\d{15}|(6011)-?\d{4}-?\d{4}-?\d{4}|(6011)-?\d{12}|(3\d{3})-\d{6}-\d{5}|(3\d{14}))$/);
	return (re.exec(val)!=null);
}



/*  the OLD checks......

// hugely modified from code found at
http;//www.4guysfromrolla.com/webtech/051999-1.shtml;
// checks validity of email address;
function isEmail2(theAddress){
	var bRet = true;
	var iAt, iLastDot;
	iAt = theAddress.indexOf("@");
	iLastDot = theAddress.lastIndexOf(".");
	//--- chk length; a@b.cd should be the shortest an address could be;
	//--- chk format has at least one "@" (with something before it);
	//--- has Only one "@";
	//--- has at least one ".",  After the "@",  && with something between;
	//--- has no more than 4 chars after last "." (like ".info");
	//--- has at least 2 chars after last "." (like ".us");
	if(theAddress.length<6  || iAt<2  || iAt!=theAddress.lastIndexOf("@")  ||
iLastDot-1<=iAt  || (len(theAddress) - iLastDot) > 4  ||
(theAddress.length-iLastDot)<2){
		bRet = false;
	//--- has no "_" after the "@";
	}else if(theAddress.indexOf("_")!=-1 && theAddress.lastIndexOf("_") >
inStrRev(theAddress, "@")){
		bRet = false;
	}else{ //--- chk validity of each char (alphanumeric, || these; "_.@-" );
		for(var i=1; i< theAddress.length; i++){
			var curChar = theAddress.substring(i, 1);
			if(!isNumeric(curChar) && (curChar.toLowerCase()<"a" || lcase(curChar)>"z")
&&  curChar!="_" && curChar!="." && curChar!="@" && curChar!="-"){
					bRet = false;
					break;
			}
		}
	}
	return bRet;
}

function isZipCode(val){
	//TODO; need validation logic here;
	if(val == ""){return false}
	return (isInteger(val) && val.length==5);
}


*/





//=====================
// data types
//=====================
function cNum(val){
	if(!isNumeric(val)){return val}
	return new Number(val);
}
function cInt(val){
	if(!isNumeric(val)){return val}
	return parseInt(val);
}
function cFloat(val){
	if(!isNumeric(val)){return val}
	return parseFloat(val);
}
function cBool(val){	// true/false, 'true/false', #s, y/n
	return val==true || (''+val).toLowerCase()=='true' ||
(''+val).toLowerCase()=='y' || (isNumeric(val) && val!=0);
}
function cDate(val){
	return new Date(val);	//.getVarDate()
}
function cTime(val){
	if(!isTime(val)){return val};

	if(isInteger(val) && val>=0 && val<24){val += ':00';}	//if single number, add
minutes
	if(val.length<=5 && val.search(/[ ]*[ap]m/gi)!=-1){val = val.replace(/([]*[ap]m)/gi, ':00$1');}	// has am/pm, add minutes

	var dtq = new Date();
	var parsedMS = Date.parse('' + FormatShortDate2(dtq) + ' ' + val);	// year can't be 1st
	dtq.setTime(parsedMS);


	//get parts of time & prep for format
	var strHours = dtq.getHours() % 12;
	var strMinutes = '0'+ dtq.getMinutes();
	var strSeconds = '0'+ dtq.getSeconds();
	strMinutes = (strMinutes).substring(strMinutes.length-2);
	strSeconds = (strSeconds).substring(strSeconds.length-2);
	var strMeridian = (dtq.getHours() >11 ? 'pm' : 'am');
	if(strHours==0){strHours=12};	// fix for our confused clock


	//assemble formatted time
	var strRet = '' + strHours + ':' + strMinutes + (strSeconds !=0 ? ':' + strSeconds : '') + ' ' + strMeridian;

	return strRet;
}




//=====================
// formatting
//=====================


function FormatShortDate2(val) {
	if(!isDate(val)){return val};
	var dt = new Date(val);
//	var nFullyear = (''+val).substring(0,3);
//	if(isInteger(nFullyear)){dt.setFullYear(nFullyear)};	// fix 2 digit assumption
	var strRet = '' + (dt.getMonth()+1) + '/' + dt.getDate() + '/' + dt.getFullYear();
	return strRet;
}
function FormatShortDate(val) {
	if(!isDate(val)){return val};
	var dt = new Date(val);
//	var nFullyear = (''+val).substring(0,3);
//	if(isInteger(nFullyear)){dt.setFullYear(nFullyear)};	
// fix 2 digit assumption
//	var strRet = '' + dt.getFullYear() + '/' + (dt.getMonth()+1) + '/' + dt.getDate();
//	return strRet;
	var strRet = '' + (dt.getMonth()+1) + '/' + dt.getDate() + '/' + dt.getFullYear();
	return strRet;
}


function LockButtons (whichform, boolVar) {
	ua = new String(navigator.userAgent);
	if (ua.match(/IE/g)) {
		for (i=1; i<=whichform.elements.length-1; i++) {
			if (whichform.elements[i].type == 'submit') {
				whichform.elements[i].disabled = boolVar;
			}
		}
	}
	var useragent = navigator.userAgent;
	var bName = (useragent.indexOf('Opera') > -1) ? 'Opera' : navigator.appName;
	if (bName != "Netscape") {
	}
	if(boolVar = true) {
		//document.body.style.cursor = "wait";
	}
}




//=====================
// backwards-compatibility
//=====================
function subShowErrField(oFld, strMsgDetail) {
	return showErrField(oFld, strMsgDetail);
}

