/* This file contains many many functions that together (along with some external
   functions that must also be included in the web page) implement a client-side
   form validation program.  First, the other javascript source files that should
   also be included in a web page that uses this program:
   
   - MStack.js
   - changedivcolor.js
   - FillInCurrentDateTime.js (if you use the validation type 'fillintodaysdate'
     A quick note: you should not really use that validation type.  It does not
     really make sense that this functionality be provided by this program, which
     is why the function was moved to another file, and can be used independent
     of this program.  We include that validation type for backward compatibility.
     
BASICS:
   
   First, there is a function in this file called submitForm().  You should not
   use this function any longer.  It was the original entry point into this
   program, and has been superceded by ValidateForm().  There are still forms
   in our site that use the old submitForm() function, so we keep it in the file.
   Eventually, we should remove these, converting them to the new ValidateForm()
   format.  I am not even going to comment submitForm().  I changed the function
   over to the ValidateForm() function because there were some bugs in
   submitForm(), and it was just easier to write a new function and support both
   for a time.
   
   Moving on...  To use this function, first start out by creating your form.
   Then, there are three things that need to be added to the form.  First,
   add to the form's onSubmit event a call to the ValidateForm() function.
   
   <FORM onSubmit="return ValidateForm(this);">
   
   Second, you must add two hidden fields to the form, __Required__ and
   __Validate__.  Note that there are two underscores (_) before and after
   each of those names.  The __Required__ field contains a comma-separated
   list of fields to require in the form.  The __Validate__ field contains a
   comma-separated list of field=validatetype pairs.  This might all be a bit
   more clear with an example.  Let's say we have a form with the following
   fields:
   
   txtName		text	required	validate as first, last name
   txtSSN		text	required	validate as SSN
   intHaveIE	radio	required
   
   To set this up, we would add the following hidden fields to our form:
   <input type=hidden disabled name="__Required__" value="txtName, txtSSN, intHaveIE">
   <input type=hidden disabled name="__Validate__" value="txtName=flname, txtSSN=ssn">
   
   Third, if you want these fields to highlight, you must mark text associated
   with each field.  We do this by adding <SPAN> tags around the text, with a
   special id for the span, that we want to associate with each form field.
   If you had the following HTML:
   
   Your Name: <input type=text name="txtName">
   
   you would change it in the following manner:
   
   <span id="txtNameAtext" style="position:relative">Your Name</span>: <input type=text name="txtName">
   
   Note the 'Atext' (stands for Associated text) appended to the end of the
   input element's name.  This is how the program knows which text to associate
   with which element.
   
   
ADVANCED FEATURES:
   
   Those were the basics.  There are a few advanced things that you can do with the
   program.  First, if you want a quick way to require all fields in a form, set
   the value of the __Required__ field to "__ALL__", like this:
   
   <input type=hidden disabled name="__Required__" value="__ALL__">
   
   More complex requirements are also possible.  It is possible to require field1
   OR field2.  In the __Required__ field, you would set this up like:
   
   <input type=hidden disabled name="__Required__" value="( field1 || field2 )">
   
   You could set up multiple statements like this, each separated by a comma:
   
   <input type=hidden disabled name="__Required__" value="( field1 || field2 ),
   ( field3 && ( field4 || field5 ),
   ( ( field6 || field7 ) && ( field8 || field9 ) )">
   
   Yes, it is necessary to separate each token in these complex statements by whitespace.
   That is how the program separates the tokens.

ODDITIES:

   Dates and Times:
   
   The way we implemented dates and times is strange.  It could be changed, but
   now we are kind of locked into the way our users have become used to it.
   
   To validate a date, you first set up a hidden field that is named the base
   name of the date (probably what the date field is in the database):
   
   <input type=hidden name="dtBirth">
   
   Then, you set up three fields for each of the month, day, and year.  The names
   have to be set up in a specific manner.
   
   <input type=text name="modtBirth"> /
   <input type=text name="dadtBirth"> /
   <input type=text name="yrdtBirth">
   
   In the __Validate__ field, you simply tell the program to validate dtBirth
   as a date type:
   
   <input type=hidden disabled name="__Validate__" value="dtBirth=date">
   
   Time fields are similar.
   
   <input type=hidden name="tmBirth">
   <input type=text name="hrtmBirth"> :
   <input type=text name="mttmBirth"> :
   <input type=text name="sctmBirth">
   <input type=radio name="aptmBirth" value="am"> am
   <input type=radio name="aptmBirth" value="pm"> pm

   You can specify a full time like this, or you can specify just minutes and seconds,
   or you can specify hours and minutes (and am/pm) without seconds.

 */


// don't use this function anymore
function submitForm(objForm) {
	var i = 0;
	var j = 0;
	var ptrRequired = 0;

	var boolError = 0;

	var strCheckType = "";

	var arrayRequired = new Array;
	var arrayValidate = new Array;
	var arrayTemp = new Array;

	/* extract the type of checking to perform from the input config line */
	strCheckType = objForm.ReqExcVal.value.split(/\s*;\s*/)[0].toUpperCase();
	if (strCheckType == "NORMAL") {
		arrayRequired=objForm.ReqExcVal.value.split(/\s*;\s*/)[1].split(/\s*,\s*/);
		if (arrayRequired[0].indexOf("=") == -1) {
			for (i=0; i<arrayRequired.length; i++) {
				arrayTemp = InfixToPostfix(arrayRequired[i].split(/\s+/));
				arrayRequired[i] = new Array(arrayTemp.length);
				arrayRequired[i] = arrayTemp;
				}
			}
		else {
			arrayRequired.length=0;
			}
		}

	else if (strCheckType == "ALL") {
		/* for each form element, if it is of the right type, include
			it in the required list. */
		for (i=0, ptrRequired=0; i<objForm.length; i++) {
			if ((objForm[i].type == "text" ||
				objForm[i].type == "select-one" ||
				objForm[i].type == "radio" ||
				objForm[i].type == "password") &&
				(objForm[i].name != objForm[i-1].name)) {
				arrayRequired[ptrRequired] = new Array(1);
				arrayRequired[ptrRequired++][0] = objForm[i].name;
				}
			}
			arrayRequired.sort();
		}

	else if (strCheckType == "EXCLUDE") {
		/* just re-using a variable here. */
		arrayValidate = objForm.ReqExcVal.value.split(/\s*;\s*/)[1].split(/\s*,\s*/).sort();
		/* For each form element, if it is of the right type and is
			NOT included in the exclude list, add it to the required
			list. */
		for (i=0, ptrRequired=0; i<objForm.length; i++) {
			if ((objForm[i].type == "text" ||
					objForm[i].type == "select-one" ||
					objForm[i].type == "radio" ||
					objForm[i].type == "password") &&
				(objForm[i].name != objForm[i-1].name) &&
				((FindElementInSortedArray(arrayValidate, objForm[i].name)) == -1)) {
				arrayRequired[ptrRequired] = new Array(1);
				arrayRequired[ptrRequired++][0] = objForm[i].name;
				}
			}
		arrayRequired.sort();
		}

	/* set up the list of fields to validate, with their types of
		validation, in a 2D array.  Finally, using arrayValidate for
		what it was meant.*/
	arrayValidate=objForm.ReqExcVal.value.split(/\s*;\s*/)[objForm.ReqExcVal.value.split(/\s*;\s*/).length - 1].split(/\s*,\s*/).sort();
	if (arrayValidate[0].indexOf("=") != -1) {
		for (i=0; i<arrayValidate.length; i++) {
			arrayTemp = arrayValidate[i].split(/\s*=\s*/);
			arrayValidate[i] = new Array(2);
			arrayValidate[i] = arrayTemp;
			}
		}
	else {
		arrayValidate.length=0;
		}

	if (document.getElementById || document.layers) {
		for (i=0; i<arrayValidate.length; i++) {
			ChangeColor(arrayValidate[i][0] + "Atext", 0);
			}
		for (i=0; i<arrayRequired.length; i++) {
			for (j=0; j<arrayRequired[i].length; j++) {
				if (objForm[arrayRequired[i][j]]) {
					ChangeColor(arrayRequired[i][j] + "Atext", 0);
					}
				}
			}
		}

	for (i=0; i<arrayValidate.length; i++) {

		if (objForm[arrayValidate[i][0]] && !ValidateField(objForm[arrayValidate[i][0]], arrayValidate[i][1])) {
			ChangeColor(arrayValidate[i][0] + "Atext", "#6666ff");
			boolError = 1;
			}
		}

	for (i=0; i<arrayRequired.length; i++) {
		if (!CheckFieldsCompleted(arrayRequired[i], objForm)) {
			for (j=0; j<arrayRequired[i].length; j++) {
				if (objForm[arrayRequired[i][j]]) {
					ChangeColor(arrayRequired[i][j] + "Atext", "#ffff66");
					}
				}
			boolError = 1;
			}
		}

	/* if there was an error, print message and return false. */
	if (boolError == 1) {
		alert ("One or more sections are missing or incomplete.\n" +
				"If you are using Microsoft Internet Explorer 5+\n" +
				"or Netscape Navigator 4.6+, required but missing\n" +
				"sections should be highlighted in yellow, while incomplete\n" +
				"sections should be highlighted in blue.");
		return false;
		}
	else {
		return true;
		}
	}

/******************************************************
 * NEW Validation function.  Hopefully we will convert over to using this function
 * on all of our forms.  For now, we retain the older one for backwards compatibility.
 */

function ValidateForm(objForm) {
	var i=0,j=0;
	var ptrRequired=0;
	var boolError=false;

	var strCheckType = "";

	var arrayRequired=new Array;
	var arrayValidate=new Array;
	var arrayTemp=new Array;

	if (objForm.__Required__.value.toUpperCase() == "__ALL__") { // if we are requiring all form elements
		for (i=0, ptrRequired=0; i<objForm.length; i++) { // for each form element
			if ((objForm[i].type == "text" || // if it is of one of the supported types
				objForm[i].type == "textarea" ||
				objForm[i].type == "select-one" ||
				objForm[i].type == "radio" ||
				objForm[i].type == "password") &&
				(objForm[i].name != objForm[i-1].name)) { // and it was not just added (radio buttons, checkboxes)
				arrayRequired[ptrRequired]=new Array(1); // add a new element to array
				arrayRequired[ptrRequired++][0]=objForm[i].name; // add it to the required array
			}
		}
	} else { // otherwise we are requiring only certain fields
		arrayRequired = objForm.__Required__.value.split(/\s*,\s*/); // split the required field on the comma and store the resulting array
		if (objForm.__DynReq__ && objForm.__DynReq__.value) {
			arrayRequired = arrayRequired.concat(objForm.__DynReq__.value.split(/\s*,\s*/));
		}
		for (i=0; i<arrayRequired.length; i++) { // for each element in that array
			arrayTemp=InfixToPostfix(arrayRequired[i].split(/\s+/)); // split on whitespace (for the more complex "logical" requirements), convert to postfix, and store in a temporary array
			arrayRequired[i]=new Array(arrayTemp.length); // make that array element an array itself to hold each of the postfix elements
			arrayRequired[i]=arrayTemp; // save back into arrayRequired
		}
	}

	/* That last bit didn't make any sense at all did it?  Let's give an example:
	   
	   __Required__ is passed in.  Let's say it is equal to:
	   "txtName, intAge, ( intNAS1 || ( Q1 && Q2 && Q3 ) )"
	   
	   First, we split this into three fields, based on the commas:
	      arrayRequired:
		  0   txtName
	      1   intAge
	      2   ( intNAS1 || ( Q1 && Q2 && Q3 ) )
	   
	   Then we iterate through this array, performing the infix to postfix function
	   on each element, and storing the results of that back into the array.  After
	   the first and second iterations, the array will look like this:
	      arrayRequired:
		  0   [txtName]
	      1   [intAge]
	      2   ( intNAS1 || ( Q1 && Q2 && Q3 ) )

	   It looks the same, because a single identifier token (each of txtName and intAge)
	   evaluate to just that in postfix.  The brackets just indicate that the element is
	   now an array, rather than a string.  After the third (and last, in this case)
	   iteration, it looks like this:
	      arrayRequired:
		  0   txtName
	      1   intAge
	      2   [intNAS1, Q1, Q2, Q3, &&, &&, ||]

	   That is the postfix representation of the third element.  This is what the program
	   needs to see to evaluate the expression.
	   
	 */

	arrayValidate=objForm.__Validate__.value.split(/\s*,\s*/); // split arrayValidate on the comma
	for (i=0; i<arrayValidate.length; i++) { // for each element in the array
		arrayTemp=arrayValidate[i].split(/\s*=\s*/); // arrayTemp equals this element split on the equals sign, giving us a {field, validatetype} pair
		arrayValidate[i]=new Array(2); 
		arrayValidate[i]=arrayTemp; // save this pair back into the original array.
	}

	/* this section is simply changing the color of all the associated text blocks to
	   colorless.
	 */
	
	var field, found;
	var re = new RegExp;
	re.compile("^(.+)\\[(\\d+)\\]$");
	
	for (i=0; i<arrayValidate.length; i++) {
		ChangeColor(arrayValidate[i][0] + "Atext", 0);
	}
	for (i=0; i<arrayRequired.length; i++) {
		for (j=0; j<arrayRequired[i].length; j++) {
			field = arrayRequired[i][j];
			found = re.exec(field);
			if (found) {
				field = found[1];
			}
			if (objForm[field]) {
				ChangeColor(field + "Atext", 0);
			}
		}
	}
	if (objForm.__DynReqSetup__ && objForm.__DynReqSetup__.value) {
		var fields;
		fields = new Array();

		arrayTemp = objForm.__DynReqSetup__.value.split(/\s*;\s*/);
		for (i=0; i<arrayTemp.length; i++) {
			fields = arrayTemp[i].split(/\s*,\s*/)[2].split(/\s*:\s*/);
			for (j=0; j<fields.length; j++) {
				field = fields[j];
				found = re.exec(field);
				if (found) {
					field = found[1];
				}
				if (objForm[field]) {
					ChangeColor(field + "Atext", 0);
				}
			}
		}
	}
	
	for (i=0; i<arrayValidate.length; i++) { // for each of the elements in the validation array
		if (objForm[arrayValidate[i][0]] && !ValidateField(objForm[arrayValidate[i][0]], arrayValidate[i][1])) { // if the form element exists and it fails validation
			ChangeColor(arrayValidate[i][0] + "Atext", "#6666ff"); // mark this by changing the associated text to blue
			boolError=true; // mark that there was some kind of error
		}
	}

	for (i=0; i<arrayRequired.length; i++) { // for each element in the required array
		if (!CheckFieldsCompleted(arrayRequired[i], objForm)) { // if it fails the required check
			for (j=0; j<arrayRequired[i].length; j++) { // for each element in the inner array
				field = arrayRequired[i][j];
				found = re.exec(field);
				if (found) {
					field = found[1];
				}
				if (objForm[field]) { // if it exists ('&&' and '||' should never exist
					ChangeColor(field + "Atext", "#ffff66"); // change the color of the associated text to red
				}
			}
			boolError=true;
		}
	}

	/* if there was an error, print message and return false. */
	if (boolError == true) {
		alert ("One or more sections are missing or incomplete.\n" +
				"If you are using Microsoft Internet Explorer 5+\n" +
				"or Netscape Navigator 4.6+, required but missing\n" +
				"sections should be highlighted in yellow, while incomplete\n" +
				"sections should be highlighted in blue.");
		return false;
	}
	else {
		return true;
	}
}

/*
 * END NEW FUNCTION
 * When we get rid of the old submitForm() function, we can also get rid of
 * FindElementInSortedArray(), below.
 ********************************
 */






/************************************************************************
 * FindElementInSortedArray: This function returns the index of 'element' 
 * in 'array', which must be sorted, if it is found, else it returns -1.
 */

function FindElementInSortedArray(arrayToSearch, elementToFind) {
	var lowerBound = 0;
	var upperBound = arrayToSearch.length - 1;
	var middle;
	var solution = -1;


	while ((solution == -1) && (lowerBound <= upperBound)) {
		middle = Math.floor((lowerBound + upperBound) / 2);

		if ((typeof(arrayToSearch[middle]) == "string" ? arrayToSearch[middle] : arrayToSearch[middle][0]) == elementToFind) {
			solution = middle;
			}
		else if ((typeof(arrayToSearch[middle]) == "string" ? arrayToSearch[middle] : arrayToSearch[middle][0]) < elementToFind) {
			lowerBound = middle + 1;
			}
		else {
			upperBound = middle - 1;
			}
		}
	return solution;
	}

/***********************************************************************************
 * InfixToPostfix function
 * this is an extremely stripped down version of the algorithm.  It only works for:
 * && (and)
 * || (or)
 * () (parentheses)
 */

function InfixToPostfix(arrayInfix) {
	var i=0;
	var arrayPostfix = new Array;
	var opStack = new Array;
	var op;

	for (i=0; i<arrayInfix.length; i++) {
		switch (arrayInfix[i]) {
			case ")" :
				while ((op = opStack.MPop()) != "(") {
					arrayPostfix.MPush(op);
					}
				break;

			case "(" :
				opStack.MPush("(");
				break;
			
			case "&&" :
				opStack.MPush("&&");
				break;

			case "||" :
				opStack.MPush("||");
				break;
			
			default :
				arrayPostfix.MPush(arrayInfix[i]);
			}
		}
	while (opStack.length) {
		arrayPostfix.MPush(opStack.MPop());
		}
	return arrayPostfix;
	}

/* This function is the entry point for required fields checking.  It handles the postfix
 * expressions for the more complex requirments, but normal "single field required"
 * requirements pass through here also.
 */
function CheckFieldsCompleted(arrayFieldList, objForm) {
	var i, opnd1, opnd2, field, found;
	var stack = new Array;
	
	if (!arrayFieldList[0]) {
		return true;
	} else {
		for (i=0; i<arrayFieldList.length; i++) {
			switch (arrayFieldList[i]) {
				case "&&" :
					opnd1 = stack.MPop();
					opnd2 = stack.MPop();
					stack.MPush(opnd1 && opnd2);
					break;
				case "||" :
					opnd1 = stack.MPop();
					opnd2 = stack.MPop();
					stack.MPush(opnd1 || opnd2);
					break;
				default :
					field = arrayFieldList[i];
					found = field.match(/^(.+)\[(\d+)\]$/);
					if (found) {
						field = eval("objForm['" + found[1] + "'][" + found[2] + "]");
					} else {
						field = eval("objForm['" + field + "']");
					}
					stack.MPush(CheckFieldCompleted(field) ? 1 : 0);
			}
		}
		return stack.MPop();
	}
}

/***************************************************************************
 * CheckFieldCompleted: This function, passed a form field, checks to see
 * if it has *some information in it.  The types currently supported are:
 *  - text fields (text)
 *  - select boxes (select-one)
 *  - radio buttons (radio)
 *  - password fields (password)
 */
		
function CheckFieldCompleted(field) {
	var radioChecked = 0;
	var radioGroupName = "";

	if ((field.type == "text" || field.type == "password" || field.type == "textarea" || field.type == "file") && field.value == "") {
		return false;
	}

	else if (field.type == "select-one" && (field.options[field.selectedIndex].value == "" || field.options[field.selectedIndex].value == "255")) {
		return false;
	}

	else if (field[0] && (field[0].type == "radio" || field[0].type == "checkbox")) {
		var radioChecked=0;
		var i=0;

		for (i=0; i<field.length; i++) {
			if (field[i].checked) {
				radioChecked=1;
				break;
			}
		}

		if (!radioChecked) {
			return false;
		}
	}

	else if ((field.type == "radio" || field.type == "checkbox") && !field.checked) {
		return false;
	}

	return true;
}

/************************************************************************
 * ValidateField: This function, given a form field and a type of validation
 * to perform, will check to see that that field conforms to certain criteria.
 * Those criteria are provided by functions for each type.
 */

function ValidateField(field, type) {
	if (field.value.length || field.type == "hidden") {

		if (type == "email" && !ValidateEmail(field.value)) {
			return false;
		}

		else if (type == "alphabetonly" && !ValidateTextOnly(field.value, "")) {
			return false;
		}

		else if (type == "flname" && !ValidateTextOnly(field.value, " `-,.")) {
			return false;
		}

		/* min 5, max 5, no non-digit chars */
		else if (type == "zip" && !ValidateZip(field.value)) {
			return false;
		}
		/* min 10, max 10, allow non-digit chars, and filter them */
		else if (type == "phone" && !ValidateNumDigits(field, 10, 10, 2)) {
			return false;
		}
		/* min 7, max 10, allow non-digit chars, and filter them */
		else if (type == "phonelax" && !ValidateNumDigits(field, 7, 10, 1)) {
			return false;
		}
		/* min 9, max 9, allow not-digit chars, and filter them */
		else if (type == "ssn" && !ValidateNumDigits(field, 9, 9, 2)) {
			return false;
		}
		/* between 1 and 100 digits only */
		else if (type == "digitsonly" && !ValidateNumDigits(field, 1, 100, 0)) {
			return false;
		}
		
		/* is a dollar amount */
		else if (type == "dollar" && !ValidateDollar(field)) {
			return false;
		}

		/* conforms to SQL Server tinyint range */
		else if (type == "SQLtinyint" && !ValidateNumber(field, 0, 255)) {
			return false;
		}

		/* conforms to SQL Server tinyint range, also greater than zero */

		else if (type == "SQLtinyintGTZ" && !ValidateNumber(field, 1, 255)) {
			return false;
		}
		
		/* conforms to SQL Server smallint range */
		else if (type == "SQLsmallint" && !ValidateNumber(field, -32768, 32767)) {
			return false;
		}
		
		/* conforms to SQL Server int range */
		else if (type == "SQLint" && !ValidateNumber(field, -2147483648, 2147483647)) {
			return false;
		}
					
		else if (type.match(/^date(.*)/) && !ValidateDate(field, RegExp.$1)) {
			return false;
		}

		else if (type == "exccomma" && !ValidateExcComma(field)) {
			return false;
		}

		else if (type == "time" && !ValidateTime(field)) {
			return false;
		}	

		else if (type == "timeHM" && !ValidateTimeHM(field, false)) {
			return false;
		}

		else if (type == "timeHMS" && !ValidateTimeHM(field, true)) {
			return false;
		}

		else if (type == "timeMil" && !ValidateTimeMil(field)) {
			return false;
		}	

		else if (type == "timeMS" && !ValidateTimeMS(field.value)) {
			return false;
		}	

		else if (type == "filejpeg" && !ValidateFile(field.value, 'jpg')) {
			return false;
		}

		else if (type == "filltodaysdate") {
			FillInCurrentDateTime(field);
		}
	}
	return true;
}

/************************************************************************
 * ValidateEmail: This function, when passed a string containing an email
 * address, will perform some basic checks.  It will check to see that there
 * is one, and only one '@' symbol, that there is at least one '.' symbol
 * after the '@', and that there are at least two characters after the last '.'
 */
function ValidateEmail(email) {
	var reEmail = /^[a-zA-Z0-9._-]*[a-zA-Z0-9_-]@([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4}$/;
	return reEmail.test(email);
}

/************************************************************************
 * ValidateZip: Check that the field looks vaguely like a valid US or CA zip code.
 */
function ValidateZip(zip) {
	var reZipUS = /^\d{5}(-\d{4})?$/;
	var reZipCA = /^[a-z]\d[a-z][- ]?\d[a-z]\d$/i;
	if (reZipUS.test(zip) || reZipCA.test(zip)) {
		return true;
	} else {
		return false;
	}
}
  
/************************************************************************
 * ValidateTextOnly: This function will check that a field contains only
 *					 alpha characters, and any additional characters that
 *					 are passed in.  For instance, on a txtNameSuffix field
 *					 the period ('.') character should probably be included.
 */

function ValidateTextOnly(textString, extraChars) {
	var i = 0;

	for (i=0; i<textString.length; i++) {
		if (((textString.charAt(i) < 'A') ||
			(textString.charAt(i) > 'z') ||
			(textString.charAt(i) > 'Z' && textString.charAt(i) < 'a')) &&
			(extraChars.indexOf(textString.charAt(i)) == -1)) {
			return false;
			}
		}
	return true;			
	}

/************************************************************************
 * ValidateNumDigits: This function checks that the input text contains at
 * least 'min', but not more than 'max' ( [min, max] ) digits ('0' - '9')
 * in the input field.  The allowRemoveOtherChars parameter determines
 * whether other characters are allowed in the input at all, and whether to
 * remove them from the field upon processing.
 *		allowRemoveOtherChars		effect
 *							0		do not allow non-digit chars.
 *							1		allow non-digit chars, but leave them
 *							2		allow non-digit chars, and remove them
 *									concatenating the digits into one block.
 */
 
function ValidateNumDigits(textStringField, min, max, allowRemoveOtherChars) {
	var numDigits = 0;
	var digitsOnly = "";
	var i=0;

	for (i=0; i<textStringField.value.length; i++) {
		if (textStringField.value.charAt(i) >= '0' && textStringField.value.charAt(i) <= '9') {
			numDigits++;
			digitsOnly += textStringField.value.charAt(i);
			}
		else if (!allowRemoveOtherChars) {
			return false;
			}
		}

	if ((numDigits < min) || (numDigits > max)) {
		return false;
		}
	else {
		if (allowRemoveOtherChars == 2) {
			textStringField.value = digitsOnly;
			}
		return true;
		}
	}

/************************************************************************
 * ValidateNumber: This function checks to see that a number is between
 * a specified minimum and maximum.
 */

function ValidateNumber(numField, min, max) {
	var pnum;
	
	pnum = parseInt(numField.value, 10);
	
	if (!isNaN(pnum) && pnum >= min && pnum <= max) {
		numField.value = pnum;
		return true;
	} else
		return false;
}

/************************************************************************
 * ValidateDate: This function checks to see that a date is in the form
 * mm/dd/yyyy, and checks to see that the date once existed, exists today,
 * or will exist in the future.
 *
 * First, the old version of the function is listed, commented out.
 * The new version, below, is much more robust and versatile.
 */

/*
function ValidateDate(hiddenDateField) {
	var month = 0;
	var day = 0;
	var year = 0;

	eval ("month = document." + hiddenDateField.form.name + ".mo" + hiddenDateField.name + ".value");
	eval ("day = document." + hiddenDateField.form.name + ".da" + hiddenDateField.name + ".value");
	eval ("year = document." + hiddenDateField.form.name + ".yr" + hiddenDateField.name + ".value");

	if (month || day || year) {	
		if ((month < 1) || (month > 12)) {
			return false;
			}
		if ((day < 1) || (day > 31)) {
			return false;
			}

		if (year.length == 1)
			year="0"+year;
		if (year.length == 2)
			if (parseInt(year) < 30)
				year="20"+year;
			else
				year="19"+year;
				
//		if ((year < 1900) || (year > 2099)) {
//			return false;
//			}

		if (((month == 4) || (month == 6) || (month == 9) || (month == 11)) 
			&& day == 31) {
			return false;
			}
		if (month == 2) {
			if (day > 29) {
				return false;
				}
			if ((day == 29) && ((year % 4) && (!(year % 100) || (year % 400)))) {
				return false;
				}
			}
	
		if (month.length == 1) {
			month = "0" + month;
			}
		if (day.length == 1) {
			day = "0" + day;
			}
	
		hiddenDateField.value = month + "/" + day + "/" + year;
		}
	return true;
	}
*/

function ValidateDate(dateField, option) {
	var month = 0;
	var day = 0;
	var year = 0;
	var aDate1, aDate2;
	var reDate1 = /^\s*(\d{1,2})\s*[\/\-]\s*(\d{1,2})\s*[\/\-]\s*(\d{2,4})\s*$/;
	var reDate2 = /^\s*(\d{4})\s*[\/\-]\s*(\d{1,2})\s*[\/\-]\s*(\d{1,2})\s*$/;
	
	if (dateField.type == "hidden")
		eval ("dateField.value = document.forms['" + dateField.form.name + "'].mo" + dateField.name + ".value + '/' + " +
								"document.forms['" + dateField.form.name + "'].da" + dateField.name + ".value + '/' + " + 
								"document.forms['" + dateField.form.name + "'].yr" + dateField.name + ".value;");
	
	aDate1 = dateField.value.match(reDate1);
	aDate2 = dateField.value.match(reDate2);
	if (aDate1 || aDate2) {
		if (aDate1) {
			month = aDate1[1];
			day = aDate1[2];
			year = aDate1[3];
		} else if (aDate2) {
			year = aDate2[1];
			month = aDate2[2];
			day = aDate2[3];
		}

		if ((month < 1) || (month > 12))
			return false;

		if ((day < 1) || (day > 31))
			return false;

		if (((month == 4) || (month == 6) || (month == 9) || (month == 11)) && day == 31)
			return false;

		if (year.length == 1)
			year="0"+year;
		if (year.length == 2)
			if (parseInt(year) < 30)
				year="20"+year;
			else
				year="19"+year;

		if (month == 2) {
			if (day > 29)
				return false;

		/* this should work, but it doesn't.  But it doesn't matter much because the only
		 * "date" we allow that is affected by it is 2/29/1900.  If someone puts
		 * that in and gets a database error, oh well...
		 */
		if ((day == 29) && ((year % 4) && ((!(year % 100)) || (year % 400))))
				return false;
		}

		if (year < 1900 || year > 2079)
			return false;

		if (month.length == 1)
			month = "0" + month;

		if (day.length == 1)
			day = "0" + day;
		
		if (aDate1) {
			if (option == "8601fix") {
				dateField.value = year + "-" + month + "-" + day;
			} else {
				dateField.value = month + "/" + day + "/" + year;
			}
		} else if (aDate2) {
			dateField.value = year + "-" + month + "-" + day;
		}
		return true;
	} else 
		return false;
}

/************************************************************************
 * ValidateTime: This function checks to see that a time exists.
 * After it, ValidateTimeHM and ValidateTimeMS are defined.  These
 * allow us to validate a time without all the hidden field stuff.
 */
 
function ValidateTime(hiddenTimeField) {
	if (eval("document." + hiddenTimeField.form.name + ".hr" + hiddenTimeField.name)) {
		eval ("hours = document." + hiddenTimeField.form.name + ".hr" + hiddenTimeField.name + ".value");
		if ((hours < 1) || (hours > 12)) {
			return false;
			}
		if (hours.length == 1) {
			hours = "0" + hours;
			}
		}

	eval ("minutes = document." + hiddenTimeField.form.name + ".mt" + hiddenTimeField.name + ".value");
	if ((minutes < 0) || (minutes > 59)) {
		return false;
		}
	if (minutes.length == 1) {
		minutes = "0" + minutes;
		}

	if (eval("document." + hiddenTimeField.form.name + ".sc" + hiddenTimeField.name)) {
		eval ("seconds = document." + hiddenTimeField.form.name + ".sc" + hiddenTimeField.name + ".value");
		if ((seconds < 0) || (seconds > 59)) {
			return false;
			}
		if (seconds.length == 1) {
			seconds = "0" + seconds;
			}
		}

	if (eval("document." + hiddenTimeField.form.name + ".ap" + hiddenTimeField.name)) {
		if (eval ("document." + hiddenTimeField.form.name + ".ap" + hiddenTimeField.name + "[0].checked")) {
			eval("ampm = document." + hiddenTimeField.form.name + ".ap" + hiddenTimeField.name + "[0].value");
			}
		else if (eval ("document." + hiddenTimeField.form.name + ".ap" + hiddenTimeField.name + "[1].checked")) {
			eval("ampm = document." + hiddenTimeField.form.name + ".ap" + hiddenTimeField.name + "[1].value");
			}
		}

	if (window.hours && window.minutes && !window.ampm) {
		return false;
		}

	hiddenTimeField.value = (window.hours ? hours + ":" : "") +
							minutes +
							(window.seconds ? ":" + seconds : "") +
							(window.ampm ? " " + ampm : "");
	return true;
	}

function ValidateTimeHM(timeField, seconds) {
	var aTime;
	var reTimeHM;
	
	if (seconds) {
		reTimeHM = /^\s*(\d{1,2})\s*:\s*(\d{1,2})\s*:\s*(\d{1,2})\s*([aApP])\.?[mM]?\.?\s*$/;
	} else {
		reTimeHM = /^\s*(\d{1,2})\s*:\s*(\d{1,2})\s*:?\s*(\d{0,2})\s*([aApP])\.?[mM]?\.?\s*$/;
	}

	aTime = timeField.value.match(reTimeHM);
	if (aTime) {
		if (aTime[1] < 1 || aTime[1] > 12 || aTime[2] < 0 || aTime[2] > 59 || aTime[3] < 0 || aTime[3] > 59)
			return false;
		else {
			timeField.value = aTime[1] + ":" + aTime[2] + (aTime[3] ? ":" + aTime[3] : "") + " " + aTime[4];
			return true;
		}
	} else
		return false;
}

function ValidateTimeMil(timeField) {
	var aTime;
	var reTimeHM = /^\s*(\d{1,2})\s*:\s*(\d{1,2})\s*:?\s*(\d{0,2})\s*[aApP]?\.?[mM]?\.?\s*$/;

	aTime = timeField.value.match(reTimeHM);
	if (aTime) {
		if (aTime[1] < 0 || aTime[1] > 23 || aTime[2] < 0 || aTime[2] > 59 || aTime[3] < 0 || aTime[3] > 59)
			return false;
		else {
			timeField.value = aTime[1] + ":" + aTime[2] + (aTime[3] ? ":" + aTime[3] : "");
			return true;
		}
	} else
		return false;
}

function ValidateTimeMS(timeValue) {
	var aTime;
	var reTimeMS = /^\s*(\d{1,2})\s*[\:\.]\s*(\d{1,2})\s*$/;

	aTime = timeValue.match(reTimeMS);
	if (aTime) {
		if (aTime[1] < 0 || aTime[2] < 0 || aTime[2] > 59)
			return false;
		else
			return true;
	} else
		return false;
}

function ValidateFile(fn, ext) {
	var reFile = new RegExp("\." + ext + "$", "i");

	if (fn.search(reFile) == -1) {
		return false;
	} else {
		return true;
	}
}

function ValidateDollar(df) {
	if (df.value.search(/,/) > -1) {
		return (df.value.search(/^\s*\$?\d{1,3}(,\d{3})*(\.\d{2})?\s*$/) != -1);
	} else {
		return (df.value.search(/^\s*\$?\d+(\.\d{2})?\s*$/) != -1);
	}
}

function ValidateExcComma(e) {
	if (e.value.match(/,/)) {
		return false;
	} else {
		return true;
	}
}


/************************************************************************
 * ProperCase: This function, when passed a string, capitalizes all letters
 * that are followed by a space or are at the beginning of the string, and
 * returns the new string.
 */

function ProperCase(inStr) {
	var strlen;
	var i;
	var retString;
	var leftHalf;
	var rightHalf;
	
	strlen = inStr.length
	retString = inStr.substring(0,1).toUpperCase();
	retString += inStr.substring(1,strlen);
	for (i = 0; i < strlen; i++) {
		if (retString.charAt(i)==" " || retString.charAt(i) == "`") {
			leftHalf = retString.substring(0,i+1);
			rightHalf = retString.substring(i+1,strlen);
			rightHalf = rightHalf.substring(0,1).toUpperCase()+rightHalf.substring(1,strlen);
			retString=leftHalf+rightHalf;
		}
	}
	return retString;
}

/**************************************************************************
 * SetDynReq: This function sets up the __DynReq__ form variable to contain
 * any fields that need to be required based on how the form is filled out.
 */

function SetDynReq(frm) {
	var i, j, k, adr, adrs, faf, field, answers, fields;
	adr = new Array();		// dynamic required fields
	adrs = new Array();		// dynamic required fields setup
	faf = new Array();		// field/answers/fields
	answers = new Array();
	fields = new Array();
	
	if (frm.__DynReqSetup__.value.length > 0) {
		adrs = frm.__DynReqSetup__.value.split(/\s*;\s*/);
		
		for (i=0; i<adrs.length; i++) {
			faf = adrs[i].split(/\s*,\s*/);
			field = faf[0];
			answers = faf[1].split(/\s*:\s*/);
			fields = faf[2].split(/\s*:\s*/);
			
			if (frm[field][0]) {
				for (j=0; j<frm[field].length; j++) {
					if (frm[field][j].checked) {
						for (k=0; k<answers.length; k++) {
							if (frm[field][j].value == answers[k]) {
								adr.MPush(fields);
							}
						}
						break;
					}
				}
			} else {
				if (frm[field].checked) {
					for (k=0; k<answers.length; k++) {
						if (frm[field].value == answers[k]) {
							adr.MPush(fields);
						}
					}
				}
			}
		}

		frm.__DynReq__.value = adr.join(",");
	}
}
