//Validation functions for input text fields in tables (error message is added row)
//This code will not work correctly for a <select> box in IE.
 
//Copyright 2008 Brad Scott
//Written: 18Nov2008
//Modified: 21Nov2008 bug fixes
//Modified: 12Dec2008 ValidateLuhn -- remove spaces correctly!
//Author: Brad Scott
//Original Author: Author: Stephen Poley (lifted from http://www.xs4all.nl/~sbpoley/webmatters/formval.html)

//Globals needed for validation functions
//--------

var nbsp = 160;		// non-breaking space char
var node_text = 3;	// DOM text node-type
var emptyString = /^\s*$/ ;
var g_idFocusField;	//  field for timer event
var g_clsstrWarning  = "";  //Warning class to use for error field
var g_clsstrError    = "formerror";  //Error class to use for error field
var g_strFieldErrorColor = 'Pink';
var g_strFieldCorrectColor = 'White';


// Trim leading/trailing blanks off string
// --------------------------------------------
function trim(strText)
{
   return strText.replace(/^\s+|\s+$/g, '');
}


//Set classes for warnings and errors
//-----------------------------------
function setClassWarning (clsstrWarning)
{
   g_clsstrWarning = clsstrWarning;
   
   return;
}

function setClassError (clsstrError)
{
   g_clsstrError = clsstrError;
   
   return;
}


// Delayed focus setting to get around IE bug
// --------------------------------------------
function setFocusDelayed()
{
  g_idFocusField.focus();
}

function setFocus(idField)
{
   // save global variable so available for timer event
   g_idFocusField = idField;
   
   setTimeout( 'setFocusDelayed()', 100 );
}


// Display warning or error message in HTML .
// validatedivBasic presumed successful
// --------------------------------------------
function setdivErrorMessage(idField, stridErrorRow, strFieldClass, strMessage) 
{      
   //Get table element
   var divError;
      
   // If message is empty, remove row if exists
   if (emptyString.test(strMessage)) 
   {      
      //Get Row. If row does not exist, nothing to do
      divError = document.getElementById(stridErrorRow);
      if (divError == null)
      {
         return;
      }
      
      divError.parentNode.removeChild(divError);
      
      return;
   }  
   
   //If error div does not exist. Add it
   divError = document.getElementById(stridErrorRow);
   if (divError == null)
   {
      divError = document.createElement('div');
      divError.id = stridErrorRow;
      divError.setAttribute('class', strFieldClass);
      divError.setAttribute('className', strFieldClass); //for IE
      
      var nodText;      
      nodText = document.createTextNode(strMessage);
      divError.appendChild(nodText);
      idField.parentNode.parentNode.appendChild(divError);
      
      return;
   }
   
   //If row exists, change the text
   var elmText = divError.firstChild;
   while (elmText.nodeName != "#text")
   {
      elmText = elmText.firstChild;
      if (elmText == null)
      {
         break;
      }
   }
   
   if (elmText != null)
   {
      elmText.nodeValue = strMessage;
   }     
      
   return;
}

// Basic validation
// (a) check for older / less-equipped browsers
// (b) check if empty fields are required
// Returns true (validation passed), 
//         false (validation failed) or 
//         proceed (don't know yet)
// --------------------------------------------
var intStatusProceed = 2;  

function validatedivBasic(idField, stridRow, binRequired, strMessageIfRequired)  
{
   // check if validation not available with this browser
   if (!document.getElementById)
   { 
      return true;  
   }
      
   //Check if required but empty
   if (emptyString.test(idField.value))
   {
      if (binRequired) 
      {
         setdivErrorMessage (idField, stridRow, g_clsstrError, strMessageIfRequired);  
         idField.style.background = g_strFieldErrorColor;
         setFocus(idField);
         
         return false;
      }
      else 
      {
         setdivErrorMessage (idField, stridRow, g_clsstrError, "");
         idField.style.background = g_strFieldCorrectColor;
         return true;  
      }
   }
   
   setdivErrorMessage (idField, stridRow, g_clsstrError, "");
   idField.style.background = g_strFieldCorrectColor;
   
   return intStatusProceed;
}


//Validate a checkbox is checked (by definition, it is required)
//--------------------------------------------------------
function validatedivCheckbox(idField, stridRow, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, true, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //If checked, we're good
   if (idField.checked)
   {
      return true;
   }
   
   //Not checked -- show error   
   setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
   setFocus(idField);
      
   return false;
}


//Validate a credit card -- all digits, minimum and maximum number of digits, spaces allowed
//Works for Credit card numbers 
//--------------------------------------------------------
function validatedivCreditCard(idField, stridRow, binRequired, intMinLength, intMaxLength, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatedivNumeric (idField, stridRow, binRequired, intMinLength, intMaxLength, strErrorMessage, strMessageIfRequired);
   if (!intStatus)
   {
      return (false);
   }
   
   binSuccess = validateLuhn(idField.value);
   if (!binSuccess)
   {
      setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
      idField.style.background = g_strFieldErrorColor;
      setFocus(idField);
      
      return false;
   }
   
   return (true);   
}


//Validate a date field -- allow mm/dd/yy[yy], dd-mmm-yy[yy], ddmmmyy[yy]
//--------------------------------------------------------
function validatedivDate(idField, stridRow, binRequired, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, binRequired, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   	
   //If valid, we're good
   if (isValidDate(idField.value))
   {
      setdivErrorMessage (idField, stridRow, g_clsstrError, "");
      idField.style.background = g_strFieldCorrectColor;
      return true;
   }
   
   //bad date -- show error   
   strErrorMessage += "\n\nDates must be in the format mm/dd/yy[yy], dd-mmm-yy[yy], or ddmmmyy[yy] where dd is a one or two digit day, mm is a one or two digit month, mmm is a three letter month, and yy[yy] is a two or four digit year";
   
   setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
   idField.style.background = g_strFieldErrorColor;
   setFocus(idField);
      
   return false;
}


// Validates e-mail address
// Returns true if not invalid
// --------------------------------------------
function validatedivEmailAddress(idField, stridRow, binRequired, strErrorMessage, strMessageIfRequired) 
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, binRequired, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //Make copy of email address so can trim, and test it
   var strCopy;
   strCopy = trim(idField.value);  
   
   var strExpEmail = /^[^@]+@[^@.]+\.[^@]*\w\w$/  ;   
   var strExpIllegalChars= /[\(\)\<\>\,\;\:\\\"\[\]]/ ;
   
   if ((!strExpEmail.test(strCopy)) || (strExpIllegalChars.test(strCopy))) 
   {
      setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
      idField.style.background = g_strFieldErrorColor;
      setFocus(idField);
      
      return false;
   }
   
   //success
   idField.value = trim(idField.value);
   setdivErrorMessage (idField, stridRow, g_clsstrError, "");
   idField.style.background = g_strFieldCorrectColor;
   
   return true;
}


//validateLuhn to validate credit card number
//Here's the algorithm used:
//Step 1: Double the value of alternate digits of the primary account number beginning with the second digit from the right (the first right--hand digit is the check digit.)
//Step 2: Add the individual digits comprising the products obtained in Step 1 to each of the unaffected digits in the original number.
//Step 3: The total obtained in Step 2 must be a number ending in zero (30, 40, 50, etc.) for the account number to be validated.
function validateLuhn(strCCNumber)
{
   // Strip any non-digits 
   var strCopy;
   strCopy = strCCNumber;
   strCopy = strCopy.replace(/\D/g, '');
   strCopy = strCopy.replace(/\s/g, '');
   
   //do the above algorithm
   var binToggle;
   var intTotal;
   var intIndex;
   var intDigit;
   
   binToggle = false;
   intTotal = 0;
   
   // Loop through each digit
   for (intIndex=(strCopy.length - 1); intIndex >= 0; intIndex--) 
   {
      intDigit = strCopy.charAt(intIndex) - '0';
      
      if (binToggle) 
      {
         intDigit *= 2;
         
         // If the sum is two digits, add them together and mod (in effect)
         if (intDigit > 9) 
         {
           intDigit -= 9;
         }
      }
      
      binToggle = !binToggle;
         
      intTotal += intDigit;
   }
   
   // If the total mod 10 equals 0, the number is valid
   if ((intTotal % 10) === 0) 
   {
      return true;
   } 
   
   return false;
}


//Validate an alphabetic name string -- allow spaces, periods, commas, (), &, ', ", and -
//Any other characters, just remove
//--------------------------------------------------------
function validatedivName(idField, stridRow, binRequired, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, binRequired, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //Keep any chars in this string
   var strexpAlpha = /[A-Za-z0-9\s\.\,\&\(\)\-\'\"]/ ;
   
   var intPos;
	var chrTest;
	var strCopy;
	var strOut;

	strCopy = idField.value;
	strOut = "";

	for (intPos=0; intPos < strCopy.length; intPos++)
	{
		chrTest = strCopy.substr(intPos, 1);
		if (strexpAlpha.test(chrTest))
		{
			strOut += chrTest;
		}
	}
   
   idField.value = strOut;
   
   return true;
}


//Validate a numeric field -- all digits, minimum and maximum number of digits, spaces allowed
//Works for Credit card numbers and 5 digit ZipCodes
//--------------------------------------------------------
function validatedivNumeric(idField, stridRow, binRequired, intMinLength, intMaxLength, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, binRequired, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //If numeric (with spaces), we're good
   var binValid;
   binValid = true;
   
   var strexpNumbers = /[^0-9\s]/ ;
   if (strexpNumbers.test(idField.value))
   {
      binValid = false;
   }
   else
   {
      var strCopy;
      
      strCopy = idField.value;
      strCopy = strCopy.replace(/\s/g , '');
      
      if ((strCopy.length < intMinLength) || (strCopy.length > intMaxLength))
      {
         binValid = false;
      }
   }
   
   //If still valid, we're good
   if (binValid)
   {
      setdivErrorMessage (idField, stridRow, g_clsstrError, "");
      idField.style.background = g_strFieldCorrectColor;
      return true;
   }
   
   //Not numeric or wrong length -- show error   
   setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
   idField.style.background = g_strFieldErrorColor;
   setFocus(idField);
      
   return false;
}


//Validate a password -- allow A-Z, a-z, 0-9, periods, #, $, &, and -
//Must be six to 16 in length
//--------------------------------------------------------
function validatedivPassword(idField, stridRow, binRequired, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, binRequired, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //Keep any chars in this string
   var binValid;
   binValid = true;
   var strexpAlpha = /[^A-Za-z0-9\.\#\&\$\-]/ ;
	
	if (strexpAlpha.test(idField.value))
   {
      binValid = false;
   }
   else
   {      
      if ((idField.value.length < 6) || (idField.value.length > 16))
      {
         binValid = false;
      }
   }
		
   //If still valid, we're good
   if (binValid)
   {
      setdivErrorMessage (idField, stridRow, g_clsstrError, "");
      idField.style.background = g_strFieldCorrectColor;
      return true;
   }
   
   //bad chars or wrong length -- show error   
   strErrorMessage += "\n\nPasswords must be between 6 and 16 characters. Only a to z, A to Z, 0 to 9, and period, #, &, $, and - are accepted.";
   
   setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
   idField.style.background = g_strFieldErrorColor;
   setFocus(idField);
      
   return false;
}


// Validate telephone number
// Returns true if not invalid
// Permits spaces, hyphens, dots, and parens. Formats in standard (nnn) nnn-nnnn xnnnn format
// --------------------------------------------

function validatedivPhone(idField, stridRow, binRequired, strErrorMessage, strMessageIfRequired) 
{
   var intStatus;
   
   intStatus = validatedivBasic (idField, stridRow, binRequired, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //Make copy so can trim, and test it. downcase it so can check extension x or X
   var strCopy;
   strCopy = trim(idField.value); 
   strCopy.toLowerCase();  
     
   //KIll chars we don't want
   var strexpKillExtras = /[\(\)\-\.\s]/g ;
   var strPhoneFormat = /[^0-9]/  ;

   strCopy = strCopy.replace(strexpKillExtras, '');
   
   //Look for extension. If there, divide into before and after
   var strPhone;
   var strExtension;
   var binExtension;
   var intPosExtension;
   var binValid;
   
   binValid = true;
   
   intPosExtension = strCopy.indexOf("x");
   if (intPosExtension < 0)
   {
      binExtension = false;
      strPhone = strCopy;
   }
   else if ((intPosExtension === 0) || (intPosExtension >= (strCopy.length - 1)))
   {
      binValid = false;
   }
   else
   {
      strPhone = strCopy.substr(0, intPosExtension);
      strExtension = strCopy.substr(intPosExtension + 1);
      binExtension = true;
   }
   
   //Be sure numeric and not too long or short (will need to test length later after kill leading 0,1)
   if (binValid)
   {
      if (strPhoneFormat.test(strPhone))
      {
         binValid = false;
      }
      
      if (strPhone.length > 20)
      {
         binValid = false;
      }
      
      if (strPhone.length < 10)
      {
         binValid = false;
      }
   }
   
   if (binValid)
   {      
      //kill off all leading 1's or 0's
      var intPos;
      var intDigit;
      
      for (intPos = 0; intPos < (strPhone.length - 1); intPos++)
      {
         intDigit = strPhone.substr(intPos,1);
         if (intDigit > '1')
         {
            break;
         }
      }
      
      if (intPos > 0)
      {
         if (intPos > (strPhone.length - 2))
         {
            binValid = false;
         }
         else
         {
            strPhone = strPhone.substr(intPos);
         }
      }
   }
   
   
   if (binValid)
   {   
      //Test length
      if (strPhone.length != 10) 
      {
         binValid = false;
      }
      else if (binExtension)
      {
         if (strPhoneFormat.test(strExtension)) 
         {
            binValid = false;
         }
         else if (strExtension.length > 6)
         {
            binValid = false;
         }
      }
   }
   
   //Format if valid. 
   if (binValid)
   {   
      var strFormatted;
      
      strFormatted = strPhone.substr(0,3) + "-" + strPhone.substr(3,3) + "-" + strPhone.substr(6);
      if (binExtension)
      {
         strFormatted += " x" + strExtension;
      }
      
      idField.value = strFormatted;
      setdivErrorMessage (idField, stridRow, g_clsstrError, "");
      idField.style.background = g_strFieldCorrectColor;
  
      return true;
   }
 
   //Set errors
   setdivErrorMessage (idField, stridRow, g_clsstrError, strErrorMessage);
   idField.style.background = g_strFieldErrorColor;
   setFocus(idField);
   
   return false;

}


//This function checks if a string is all numbers (no spaces allowed)
function isNumeric(strIn)
{
   if (emptyString.test(strIn))
   {
      return false;
   }
   
   var strDigits = /[^0-9]/  ; 
   return (!strDigits.test(strIn));
}


//This function checks if a date is valid -- allow mm/dd/yy[yy], dd-mmm-yy[yy], ddmmmyy[yy]
function isValidDate(strDate)
{
   //Check the date
   var binValid;
   var strCopy;
   var strYear;
   var intYear;
   
   binValid = true;

   //See if two or four digit year
   strCopy = trim(strDate);
	if (strCopy.length < 6)
	{
      return false;
   }
    
   strYear = strCopy.substr(strCopy.length - 4, 4);
   
   if (isNumeric(strYear)) 
   {
      intYear = parseInt(strYear, 10);
   }
   else
	{
		//two digit year -- expand to 4
		strYear = strCopy.substr(strCopy.length - 2, 2);

		if (!isNumeric(strYear)) // if still something other than numbers, bad date
		{
         return false;
      }
              
      intYear = parseInt(strYear, 10);
      if (intYear <= 38)
      {
         intYear += 2000;
      }
      else
      {
         intYear += 1900;
      }
	}
	
	//Validate year
	if ((intYear < 1901) || (intYear > 2038))
	{
	     return false;
   }
   
   //Extract day as numeric from string based on type of date.
   //Start back with original string
   var intPos;
   var strDay;
   var strMonth;
   var intDay;
   var intMonth;
   var binLeapYear;
   var arystrMonth = new Array("jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec");
   
   strCopy = trim(strDate);
   intPos = strCopy.indexOf("/");
	if (intPos < 0)
	{
		//No slashes, must be in form DD-MMM-YY or DDMMMYY[YY]
		intPos = strCopy.indexOf("-");
		if (intPos < 0)
		{
			//Format is DDMMMYY (or invalid), see if one or two digit day
			if (isNumeric(strCopy.charAt(1)))
			{
            strDay = strCopy.substr(0, 2);
            strMonth	= strCopy.substr(2, 3);
			}
			else
			{
				strDay = strCopy.charAt(0);
            strMonth	= strCopy.substr(1, 3);
         }
      }
      else
      {
         //Format is DD-MMM-YY[YY]
		   strDay = strCopy.substr(0, intPos);
		   strMonth = strCopy.substr(intPos + 1, 3);
      }
      
      //Validate month and convert to integer
      var binFound;
      binFound = false;
      
      strMonth = strMonth.toLowerCase();
      for (intMonth = 1; intMonth <= 12; intMonth++)
      {
         if (strMonth === arystrMonth[intMonth - 1])
         {
            binFound = true;
            break;
         }
      }
      
      if (!binFound)
      {
         return false;
      }      
   }
   else
   {
      //format is mm/dd/yyyy. Get month
      strMonth = strCopy.substr(0, intPos);
      if (!isNumeric(strMonth))
   	{
   	  return (FALSE);
      }
   
      intMonth = parseInt(strMonth, 10);
      if ((intMonth < 1) || (intMonth > 12))
   	{
   	  return (FALSE);
      }
      
      //Get day
      strDay = strCopy.substr(intPos + 1);
      intPos = strDay.indexOf("/");
   	if (intPos < 0)
   	{
   	  return false;
      }
      
      strDay = strDay.substr(0, intPos);
   }
   
   if (!isNumeric(strDay))
	{
   	  return false;
   }
   
   //Validate the days in the month
   intDay = parseInt(strDay, 10);
   
   var aryintMonthDays = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
   
   if ((intDay < 1) || (intDay > aryintMonthDays[intMonth - 1]))
   {
      binLeapYear = ((Math.floor(intYear / 4) * 4) === intYear);  //2000 is a leap year, so no need to worry about 1900, 2100 in our calculations since they are out of range
      if ((!binLeapYear) || (intMonth !== 2) || (intDay != 29))
      {
         return false;
      }
   } 
   
   return true;
}
