// $Header: /home/cvsroot/clearview/js/formValidationLib.js,v 1.1 2003/08/22 10:41:32 diko Exp $
//
// Form Validation functions
// Usage:
//		<Document usage here>
//

var IE4 = (document.all) ? 1 : 0;

var vdataArray = new Array();
var vdataLength = 0;
var ATT_ELEMNAME = 0;
var ATT_DISPLAYNAME = 1;
var ATT_TYPE = 2;
var ATT_ALWAYS = 3;
var ATT_REQUIRED = 4;
var ATT_NOFOCUS = 5;
var ATT_MINVAL = 6;
var ATT_MAXVAL = 7;
var ATT_INVALIDCHARS = 8;
var ATT_MINDECIMALS = 9;
var ATT_MAXDECIMALS = 10;
var ATT_Last = 10;
// Added by Diko 05/27/03
var ATT_DATEFORMAT = "MM/dd/yyyy";

function GenNumberOfDecimals(data) {
var strVal;
var nRet = 0;
var ndx;
var bDotFound = false;

	strVal = new String(data);

	for (ndx = 0; ndx < strVal.length; ndx++)
	{
		if (strVal.charAt(ndx) == '.') {
			bDotFound = true;
			continue;
		}

		if (bDotFound)
			nRet++;
	}

	return nRet;
}


// Validate the prepared form. Returns true if successful, false otherwise
function validateForm(form) {
	var i, element;

	if (form == null) {
		validateError(null, "Usage: validateForm(<Form object>)");
		return false;
	}

	for (i = 0; i < vdataLength; i++) {
		if (null == vdataArray[i])
			continue;

		element = form.elements[vdataArray[i][ATT_ELEMNAME]];

		if (null != element && !validateElement(element))
			return false;
	}
	return true;
}

// Validate an individual form element
function validateElement(element) {
	var value, datatype, always, required, i;
	var message = '';

	if (element == null) {
		validateError(element, "Usage: validateElement(<Element object>)");
		return false;
	}

	value = elementValue(element);

	datatype = getValidationAttribute(element, ATT_TYPE);
	always = (getValidationAttribute(element, ATT_ALWAYS) != null);
	required = getValidationAttribute(element, ATT_REQUIRED);

	if (datatype == 'autodate') {
		eval('message = validate_' + datatype.toLowerCase() + '(element, value)');
		if (required && IsEmptyField(element) && !element.disabled) {
			message = "Required value is empty.";
		}
	} else {
		if (!IsEmptyField(element) || always) {
			if (datatype && null != datatype) {
				eval('message = validate_' + datatype.toLowerCase() + '(element, value)');
			}
		}
		if (required && IsEmptyField(element) && !element.disabled) {
			message = "Required value is empty.";
		}
	}
	if (message != '') {
		validateError(element, message);
		return false;
	}
	return true;
}

// Get the value of any kind of element
function elementValue(element) {
var elemLength;

	switch (elementType(element))
	{
	case 'select-multiple':
	case 'select-one':
		return element.options[element.selectedIndex].value;
	case 'radio':
	case 'checkbox':
		elemLength = elementLength(element);
		if (elemLength != null) 
		{
			for (i = 0; i < elemLength; i++) 
			{
				if ((element[i]).checked)
					return (element[i]).value;
			}
		}
		else
		{
			if (element.checked)
				return element.value;
		}
		return null;
	default:
		return element.value;
	}
}

// Test if a given element name has an empty value
function IsEmptyField(field) {
	var blank_exp = /^\s*$/;
	var message = '';

	if (null == field)
		return true;

	var value = elementValue(field);
	return (value == null || value.search(blank_exp) != -1);
}

// Raise an error popup on a failed element validation
function validateError(element, errorMessage) {
	var alertmessage = errorMessage;
	var datatype;

	if (element != null) {
		var name = getValidationAttribute(element, ATT_DISPLAYNAME);
		if (name != '') {
			datatype = getValidationAttribute(element, ATT_TYPE);
			if (String(datatype.toLowerCase()).search(/^password*$/) == -1)
				errorMessage = "Error in '" + name + "' : " + errorMessage;
			if (datatype && null != datatype) {
				switch (datatype.toLowerCase()) {
					case 'autodate':
					case 'expirydate':
						element = element.form.elements[element.name + 'Month'];
						break;
				}
			}
		}
		var elemType = elementType(element);
		var nofocus = (getValidationAttribute(element, ATT_NOFOCUS) != null);
		if (!nofocus && elemType != 'hidden') {
			element.focus();
		}
	}

	alert(errorMessage);
}

// Conditionally submit a form based on the success of it's validation
function validateSubmit(form) {
	if (validateForm(form)) {
		form.submit();
		return true;
	} else {
		return false;
	}
}

//
// Functions to manage the stored validation information
//

function elementName(element) {
	if (null == element)
		return null;

	if (null != element.name)
		return element.name;
	else if (null != element.length)
		return (element[0]).name;
	else
		return null;
}

function elementType(element) {
	if (null == element)
		return null;

	if (null != element.type)
		return element.type;
	else if (null != element.length)
		return (element[0]).type;
	else
		return null;
}

function elementLength(element) {
	if (element.type == 'select-multiple' ||
		element.type == 'select-one')
		return null;
	else
		return element.length;
}

function find(element) {
	var i;

	for (i = 0; i < vdataLength; i++) {
		if (typeof(vdataArray[i]) == 'object' && 
			vdataArray[i][ATT_ELEMNAME] == elementName(element))
			return i;
	}

	return -1;
}

function addElement(element) {
	var i = find(element);
	if (-1 == i) {
		i = vdataLength++;
		vdataArray[i] = new Array(ATT_Last + 1);
	}
	
	return i;
}

// Shorthand to set name, displayname, type and requirement
function setValidation(element, displayName, type, required) {
	if (null == element)
		return;

var elemName = elementName(element);

	setValidationAttribute(element, ATT_ELEMNAME, elemName);
	setValidationAttribute(element, ATT_DISPLAYNAME, displayName);
	setValidationAttribute(element, ATT_TYPE, type);
	setValidationAttribute(element, ATT_REQUIRED, required);

	if (type == 'autodate') {
		var token = element.form.elements[elemName + 'Month'];
		setValidation(token, displayName + ' Month', 'integer', required);
		setValidationAttribute(token, ATT_MINVAL, 1);
		setValidationAttribute(token, ATT_MAXVAL, 12);

		token = element.form.elements[elemName + 'Day'];
		setValidation(token, displayName + ' Day', 'integer', required);
		setValidationAttribute(token, ATT_MINVAL, 1);
		setValidationAttribute(token, ATT_MAXVAL, 31);

		token = element.form.elements[elemName + 'Year'];
		setValidation(token, displayName + ' Year', 'integer', required);
	}
}

function deleteValidation(element) {
	var i = find(element);
	var j;

	if (-1 != i) {
		vdataLength--;
		for (j = i; j <= vdataLength; j++)
			vdataArray[j] = vdataLength[j + 1];
	}
}

function setValidationAttribute(element, attribute, val) {
	var i = addElement(element);

	vdataArray[i][attribute] = val;
	return true;
}

function setDefaultValidationAttribute(element, attribute, val) {
	if (getValidationAttribute(element, attribute) == null)
		setValidationAttribute(element, attribute, val);
	return true;
}

function getValidationAttribute(element, attribute) {
	var i;

	if (attribute < 0 || attribute > ATT_Last)
		return null;

	i = find(element);

	return (-1 != i) ? vdataArray[i][attribute] : null;
}

//
// Miscellaneous helper functions
//

// returns a float containing the value the money field
function parseMoney(data) {
	var val, ndx;
	var pos1 = -1, pos2 = -1;

	// accepted patterns
	var money_exp1 = /^\s*-?(\d+\,?)*\.?\d*$/;
	var money_exp2 = /^\s*\((\d+\,?)*\.?\d*\)$/;

	if (data.search(money_exp1) == -1 && 
		data.search(money_exp2) == -1)
		return NaN;

	val = '';
	// skip white spaces
	ndx = 0;
	while (ndx < data.length && 
			(data.charAt(ndx) == '(' || data.charAt(ndx) == ' ')) {
		ndx++;
	}
	
	if (data.charAt(ndx - 1) == '(') {
		// negative value
		val += '-';
	}
	while (ndx < data.length) {
		if (data.charAt(ndx) != ',' && data.charAt(ndx) != ')')
			val += data.charAt(ndx);
		ndx++;
	}

	return parseFloat(val);
}

function getFullYear(myDate) {
	if (myDate.getFullYear != null) {
		return myDate.getFullYear();
	} else {
		if (myDate.getYear() >= 0 && myDate.getYear() <= 99) {
			return myDate.getYear() + 1900;
		} else {
			return myDate.getYear();
		}
	}
}

function getVBScriptDate(date) {
	var year, month, day, hour, min, sec;
	if (null == date || '' == date)
		return '';

	year = date.getFullYear();
	month = 1 + date.getMonth();
	day = date.getDate();
	hour = date.getHours();
	min = date.getMinutes();
	sec = date.getSeconds();

	return year + '/' +  month + '/' + day + ' ' + hour + ':' + min + ':' + sec;
}

function IsValidDate(year, month, day) {
	if ((year == null || year == '') && (month == null || month == '') && 
		(day == null || day == ''))
		return false;

	if (year < 1753 || year > 9999 || month < 1 || month > 12 || day <= 0 || day > 31)
		return false;

	var dateObj = new Date(year, month - 1, day);

	return (dateObj.getDate() == day && dateObj.getMonth() == (month - 1) && getFullYear(dateObj) == year);
}

function IsValidDate_SkipYear(year, month, day, required) {
	if (!required && (year == null || year == '') && (month == null || month == '') &&
		(day == null || day == ''))
		return true;

	// If year isn't supplied then pick 9999
	if (year == null || year == '')	year = 9999;

	return IsValidDate(year, month, day);
}

function IsValidDate_2DYear(year, month, day) {
	year = (year >= 50) ? "19" + year : "20" + (year < 10 ? '0' + parseInt(year) : year);
	return IsValidDate(year, month, day);
}

// Javascript Quicksort implementation
// written by Achille Hui of Stanford University.
function _pm_array_qsort(vec,lo,up,cmp_fun) {
	var i, j, t;

	while (up > lo) {
		i = lo;
		j = up;
		t = vec[lo];
		while (i < j) {
			while (cmp_fun(vec[j],t) > 0)
				j -= 1;
			vec[i] = vec[j];
			while ((i < j) && (cmp_fun(vec[i],t) <= 0))
				i++;
			vec[j] = vec[i];
		}
		vec[i] = t;
		if (i - lo < up - i) {
			_pm_array_qsort(vec,lo,i-1,cmp_fun); lo = i+1;
		} else {
			_pm_array_qsort(vec,i+1,up,cmp_fun); up = i-1;
		}
	}
}

function _pm_array_defcmp(a,b){
	return (a == b) ? 0 : (a > b) ? 1 : -1;
}

function pm_array_qsort(vec,lo,hi,cmp_fun) {
	if (vec == null) {
		return;
	} else if (cmp_fun == null) {
		_pm_array_qsort(vec,lo,hi,_pm_array_defcmp);
	} else {
		_pm_array_qsort(vec,lo,hi,cmp_fun);
	}
}

// Compare for quicksort to use to sort form elements by tabindex
function tabcmp(x,y){
	var a = (x == null) ? 0 : x.tabIndex;
	var b = (y == null) ? 0 : y.tabIndex;

	return _pm_array_defcmp(a,b);
}

//
// Standard validation functions
//

function validate_integer(element, value) {
	var int_exp = /^\s*-?\d+\s*$/;
	var message = '';

	if (value.search(int_exp) == -1) {
		message = "Value must be an integer.";
	} else {
		var val = parseInt(value, 10);
		var minval = parseInt(getValidationAttribute(element, ATT_MINVAL));
		var maxval = parseInt(getValidationAttribute(element, ATT_MAXVAL));

//		alert("minval = " + minval + "; maxval = " + maxval);
		if (!isNaN(maxval) && !isNaN(minval)) {
			if ((val < minval) || (val > maxval)) {
				message = "Value must be an integer between " + minval + " and " + maxval + ".";
			}
		} else {
			if (!isNaN(maxval)) {
				if (val > maxval) {
					message = "Value must be an integer less than or equal to " + maxval + ".";
				}
			} else if (!isNaN(minval)) {
				if (val < minval) {
					message = "Value must be an integer greater than or equal to " + minval + ".";
				}
			}
		}
	}

	return message;
}

function validate_float(element, value) {
	var float_exp = /^\s*-?(\d+\.\d*|\d*\.\d+|\d+)\s*$/;
	var message = '';

	if (value.search(float_exp) == -1) {
		message = "Value is not a valid number.";
	} else {
		var val = parseFloat(value);
		var dec = GenNumberOfDecimals(value);
		var minval = parseFloat(getValidationAttribute(element, ATT_MINVAL));
		var maxval = parseFloat(getValidationAttribute(element, ATT_MAXVAL));
		var mindec = parseInt(getValidationAttribute(element, ATT_MINDECIMALS));
		var maxdec = parseInt(getValidationAttribute(element, ATT_MAXDECIMALS));

		if (!isNaN(minval) && !isNaN(maxval)) {
			if ((val < minval) || (val > maxval)) {
				message = "Value must be between " + minval + " and " + maxval + " inclusive.";
			}
		} else {
			if (!isNaN(maxval)) {
				if (val > maxval) {
					message = "Value must be less than or equal to " + maxval + ".";
				}
			} else if (!isNaN(minval)) {
				if (val < minval) {
					message = "Value must be greater than or equal to " + minval + ".";
				}
			}
		}

		if (!isNaN(mindec) && !isNaN(maxdec)) {
			if ((dec < mindec) || (dec > maxdec)) {
				message = "Value must contain more than " + mindec + " and less than " + maxdec + " decimal places.";
			}
		} else {
			if (!isNaN(maxdec)) {
				if (dec > maxdec) {
					message = "Value must contain less than " + maxdec + " decimal places.";
				}
			} else if (!isNaN(minval)) {
				if (dec < mindec) {
					message = "Value must contain more than " + mindec + " decimal places.";
				}
			}
		}
	}

	return message;
}

function validate_password(element, value) {
	var message = '';

	if (value.length < 6) {
		message = "Value is less than 6 characters in length.";
	} else if (value.length > 20) {
		message = "Value is more than 20 characters in length.";
	} else if (value.search(/[^A-Za-z0-9]/) != -1) {
		message = "Value contains non-alphanumeric characters.";
	} else if (value.search(/^[A-Za-z]/) == -1) {
		message = "Value does not start with an alphabetic character.";
	}

	return message;
}

function validate_string(element, value) {
	var message = '';
	var val = '' + value;
	var vlength = val.length;

	var minval = parseInt(getValidationAttribute(element, ATT_MINVAL));
	var maxval = parseInt(getValidationAttribute(element, ATT_MAXVAL));
	var invalidchars = getValidationAttribute(element, ATT_INVALIDCHARS);

	if (!isNaN(maxval) && !isNaN(minval)) {
		if ((vlength < minval) || (vlength > maxval)) {
			message = "Value must be between " + minval + " and " + maxval + " characters in length.";
		}
	} else {
		if (!isNaN(maxval)) {
			if (vlength > maxval) {
				message = "Value must be at most " + maxval + " characters long.";
			}
		} else if (!isNaN(minval)) {
			if (vlength < minval) {
				message = "Value must be at least " + minval + " characters long.";
			}
		}
	}

	if (invalidchars != null) {
		for (var i = 0; i < invalidchars.length; i++) {
			if (val.indexOf(invalidchars.charAt(i)) != -1) {
				message = "Value contains invalid character: '" + invalidchars.charAt(i) + "'.";
			}
		}
	}

	return message;
}

function validate_money_2d(element, value) {
	var val;
	var valsplit;
	var message = '';

	if (isNaN(val = parseMoney(value))) {
		message = "'" + value + "' is not a valid number.";
	} else {
		valsplit = val.toString().split(".");
		if (valsplit.length > 1 && valsplit[1].length > 2)  {
			message = "Value may have only two decimal places.";
		} else {
			var minval = parseFloat(getValidationAttribute(element, ATT_MINVAL));
			var maxval = parseFloat(getValidationAttribute(element, ATT_MAXVAL));
			if (!isNaN(maxval)) {
				if (val > maxval) {
					message = "Value must be less than or equal to " + maxval + ".";
				}
			}
			if (!isNaN(minval)) {
				if (val < minval) {
					message = "Value must be greater than or equal to " + minval + ".";
				}
			}
		}
	}

	return message;
}

function validate_money_0d(element, value) {
	var val;
	var message = '';

	if (isNaN(val = parseMoney(value))) {
		message = "Value is not a valid number.";
	} else if (Math.ceil(val) != val) {
		message = "Value must be a whole number.";
	} else {
		var minval = parseFloat(getValidationAttribute(element, ATT_MINVAL));
		var maxval = parseFloat(getValidationAttribute(element, ATT_MINVAL));
		if (!isNaN(maxval)) {
			if (val > maxval) {
				message = "Value must be less than or equal to " + maxval + ".";
			}
		}
		if (!isNaN(minval)) {
			if (val < minval) {
				message = "Value must be greater than or equal to " + minval + ".";
			}
		}
	}

	return message;
}

function validate_date(element, value) {
  if (element == null)
    return "Validation element can't be null!";
  if (value == null || value == "")
    return "Value can't be null or empty!";
  var common_msg =  "Date format is (" + ATT_DATEFORMAT + ")";
  var month = 0;
  var day = 0;
  var year = 0;
  var pattern = ATT_DATEFORMAT.split("/");
  var date_values = value.split("/");
  if (date_values == null || date_values.length < 3)
    return "Invalid date '" + value + "'. " + common_msg;
  for (i = 0; i < pattern.length; i++) {
        if (pattern[i].toLowerCase() == "mm") {
          month = date_values[i];
        } else
        if (pattern[i].toLowerCase() == "dd") {
          day = date_values[i];
        } else
        if (pattern[i].toLowerCase() == "yy" || pattern[i].toLowerCase() == "yyyy") {
          year = date_values[i];
        }
  }
//  alert("Month=" + month + " Day=" + day + "Year=" + year);
  var res = false;
  if (year.length < 4) {
    res = IsValidDate_2DYear(year, month, day);
  } else {
    res = IsValidDate(year, month, day);
  }
  var message = '';
  if (!res)
    message = "Invalid date: '" + value + "'. " + common_msg;
  return message;
/*
	var val = ('' + value);

	if (isNaN(new Date(val))) {
		return '';
	} else {
		return '';
	}
	*/
}

function validate_name(element, value) {
	var name_exp = /^\s*[a-zA-Z\.\ ]+\s*$/;
	var message = '';

	if (value.search(name_exp) == -1) {
		message = "Value is not a valid name.";
	}

	return message;
}

function validate_login(element, value) {
	var name_exp = /^\s*[a-zA-Z0-9]+\s*$/;
	var message = '';

	if (value.search(name_exp) == -1) {
		message = "Value is not a valid login name.";
	}

	return message;
}

function validate_phone(element, value) {
	// no validation necessary
	var message = '';

	if (!IsValidInputField(element)) {
		message = "Value is not a valid phone number.";
	}

	return message;
}

function validate_email(element, value) {
	var email_has = /^\s*.+@.+\..{2,}\s*$/;
	var message = '';

	if (value.search(email_has) == -1) {
		message = "Value is not a valid email address.";
	}

	return message;
}

function validate_creditcard_number(element, value) {
	var creditcard_number_has = /^\s*(\d\s*){15,16}$/;
	var message = '';

	if (value.search(creditcard_number_has) == -1) {
		message = "Value must be a valid 15 or 16 digit card number.";
	}

	return message;
}

