//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    = "fontError";  //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 table .
// validatetblBasic presumed successful
// --------------------------------------------
function settblErrorMessage(stridTable, stridErrorRow, intColSpan, strFieldClass, strMessage) 
{      
   //Get table element
   var rowError;
   var tblError;
      
   tblError = document.getElementById(stridTable);
   if (tblError == null)
   {
      alert ("settblErrorMessage: scripting error: table " + stridTable + " not found.")
      return;
   }
      
   // If message is empty, remove row if exists
   if (emptyString.test(strMessage)) 
   {      
      //Get Row. If row does not exist, nothing to do
      rowError = document.getElementById(stridErrorRow);
      if (rowError == null)
      {
         return;
      }
      
      tblError.deleteRow(rowError.rowIndex);
      
      return;
   }    
   
   //If row does not exist. Add it
   rowError = document.getElementById(stridErrorRow);
   if (rowError == null)
   {
      rowError = tblError.insertRow(tblError.rows.length);
      rowError.id = stridErrorRow;
      
      var celError;
      var nodText;
    
      celError = rowError.insertCell(0);
      celError.setAttribute('class', strFieldClass);
      celError.setAttribute('className', strFieldClass); //for IE
      celError.align = 'left';
      
      if (intColSpan > 1)
      {
         celError.colSpan = intColSpan;
      }
      
      nodText = document.createTextNode(strMessage);
      celError.appendChild(nodText);
      
      return;
   }
   
   //If row exists, change the text
   var elmText = rowError.cells[0].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 validatetblBasic(idField, stridTable, stridRow, intColSpan, binRequired, strMessageIfRequired)  
{
   // check if validation not available with this browser
   if (!document.getElementById)
   { 
      return true;  
   }
   
   //check if can get to error element -- exists for browser and is text element
   var elmErrorField;
   elmErrorField = document.getElementById(stridTable);
   
   if (elmErrorField == null)
   {
      alert ("scripting error: table id " + stridTable + " not found.")
      return true;
   }
   
   if (!elmErrorField.firstChild)
   {
      return true;
   }
   
   //Check if required but empty
   if (emptyString.test(idField.value))
   {
      if (binRequired) 
      {
         settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, strMessageIfRequired);  
         idField.style.background = g_strFieldErrorColor;
         setFocus(idField);
         
         return false;
      }
      else 
      {
         settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, "");
         idField.style.background = g_strFieldCorrectColor;
         return true;  
      }
   }
   
   settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, "");
   idField.style.background = g_strFieldCorrectColor;
   
   return intStatusProceed;
}


//Validate a checkbox is checked (by definition, it is required)
//--------------------------------------------------------
function validatetblCheckbox(idField, stridTable, stridRow, intColSpan, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatetblBasic (idField, stridTable, stridRow, intColSpan, true, strMessageIfRequired) ;
   if (intStatus != intStatusProceed)
   {
      return (intStatus);
   }
   
   //If checked, we're good
   if (idField.checked)
   {
      return true;
   }
   
   //Not checked -- show error   
   settblErrorMessage (stridTable, stridRow, intColSpan, 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 validatetblCreditCard(idField, stridTable, stridRow, intColSpan, binRequired, intMinLength, intMaxLength, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatetblNumeric (idField, stridTable, stridRow, intColSpan, binRequired, intMinLength, intMaxLength, strErrorMessage, strMessageIfRequired);
   if (!intStatus)
   {
      return (false);
   }
   
   binSuccess = validateLuhn(idField.value);
   if (!binSuccess)
   {
      settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, strErrorMessage);
      idField.style.background = g_strFieldErrorColor;
      setFocus(idField);
      
      return false;
   }
   
   return (true);   
}


// Validates e-mail address
// Returns true if not invalid
// --------------------------------------------
function validatetblEmailAddress(idField, stridTable, stridRow, intColSpan, binRequired, strErrorMessage, strMessageIfRequired) 
{
   var intStatus;
   
   intStatus = validatetblBasic (idField, stridTable, stridRow, intColSpan, 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))) 
   {
      settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, strErrorMessage);
      idField.style.background = g_strFieldErrorColor;
      setFocus(idField);
      
      return false;
   }
   
   //success
   idField.value = trim(idField.value);
   settblErrorMessage (stridTable, stridRow, intColSpan, 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 validatetblName(idField, stridTable, stridRow, intColSpan, binRequired, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatetblBasic (idField, stridTable, stridRow, intColSpan, 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 validatetblNumeric(idField, stridTable, stridRow, intColSpan, binRequired, intMinLength, intMaxLength, strErrorMessage, strMessageIfRequired)
{
   var intStatus;
   
   intStatus = validatetblBasic (idField, stridTable, stridRow, intColSpan, 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)
   {
      settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, "");
      idField.style.background = g_strFieldCorrectColor;
      return true;
   }
   
   //Not numeric or wrong length -- show error   
   settblErrorMessage (stridTable, stridRow, intColSpan, 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 validatetblPhone(idField, stridTable, stridRow, intColSpan, binRequired, strErrorMessage, strMessageIfRequired) 
{
   var intStatus;
   
   intStatus = validatetblBasic (idField, stridTable, stridRow, intColSpan, 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;
      settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, "");
      idField.style.background = g_strFieldCorrectColor;
  
      return true;
   }
 
   //Set errors
   settblErrorMessage (stridTable, stridRow, intColSpan, g_clsstrError, strErrorMessage);
   idField.style.background = g_strFieldErrorColor;
   setFocus(idField);
   
   return false;

}
