/*

Generic Form Validation.
tboland@gmail.com
http://www.techtoolblog.com
License: Free To Use, If Modified/Added
please send updated source to tboland@gmail.com
 
To Validate a Form Element Add the Custom Attribute:
validate="VALIDATEHOW"

Your Current Options Are:
"not_empty"
"integer"
"number" - decimal allowed
"email"
"phone" - includes international phone numbers

Multiple Validation uses the following syntax:
validate="not_empty|integer"

To Add a Message Add the Custom Attribute:
msg="Name is a required field" 

By Default the Messages are shown next to the form element.
To Show Alert Boxes add a Custom Attribute in the form tag (show_alert):
<form action="mypage.aspx" show_alert="1" onSubmit="return Validate(this);">

Examples:
<html>
<head>
<script language="JavaScript" src="gfv.js" type="text/javascript"></script>
</head>
<body>
<form action="mypage.php" show_alert="1" onSubmit="return Validate(this);">
First Name <input type="Text" name="FirstName" maxlength="25" validate="not_empty" msg="First Name is Required" /><br/>
Email <input type="text" name="Email" maxlength="25" validate="email" msg="Email is Required" /><br/>
<input type="submit" value="Go">
</form></body></html>

ADVANCED USERS:
Add Your Own Function Calls to the validate attribute.
It Must return true or false
example:
<input type="text" name="Age" validate="ValidateAge" />
*/
var ccErrorNo = '';
var ccErrors = new Array ()

ccErrors [0] = "Unknown card type";
ccErrors [1] = "No card number provided";
ccErrors [2] = "Credit card number is in invalid format";
ccErrors [3] = "Credit card number is invalid";
ccErrors [4] = "Credit card number has an inappropriate number of digits";

function Validate(objForm, isFormSubmit) {	
	var arrValidated=new Array();
	

	for (var i=0; i<objForm.elements.length; i++) {
		var element=objForm.elements[i];
		var elName=element.name;
		if ((!elName)||(elName.length == 0)||(arrValidated[elName]))
			continue;
		arrValidated[elName] = true;
		var validationType = element.getAttribute("validate");

		if ((!validationType))
			continue;
		var strMessages=element.getAttribute("msg");
		if (!strMessages)
			strMessages = "";
		var arrMessages = strMessages.split("|");
		
    	var arrValidationTypes = validationType.split("|");

		for (var j=0; j<arrValidationTypes.length; j++) {
			var curValidationType = arrValidationTypes[j];
			var blnValid=true;
			
			if (element.name == 'fCardType') {
			  cardType = element.value;
			}

			switch (curValidationType) {
				case "not_empty":
					blnValid = ValidateNotEmpty(element);
					break;
				case "integer":
					blnValid = ValidateInteger(element);
					break;
				case "number":
					blnValid = ValidateNumber(element);
					break;
				case "email":
					blnValid = ValidateEmail(element);
					break;
				case "phone":
					blnValid = ValidatePhone(element);
					break;
				case "<":
				case ">":
				case "<>":
				case "=":
		          var strMin=element.getAttribute("minlength");
                  var strMax=element.getAttribute("maxlength");			  
					blnValid = ValidateMinMax(element, curValidationType, strMin, strMax);
					break;
				case "creditcard":
					blnValid = ValidateCreditCard(element, cardType);
					break;
				case "radio1":
				    buttonGroup = objForm.fCaseSelect; 					 
	                 if(buttonGroup[0].checked && objForm.fExistingCase.value =='') {
						blnValid = false;			  
					  }									  
					break;
				case "radio2":
				    buttonGroup = objForm.fCaseSelect; 					 
					if(buttonGroup[1].checked && objForm.fNewCase.value == '') {
					  blnValid = false;	
					} 								  
					break;
				default:
					try {
						blnValid = eval(curValidationType+"(element)");
					}
					catch (ex) {
						blnValid = true;
					}
			}
			if (blnValid == false) {				
				if (ccErrorNo =='') {
				   var message="invalid value for "+element.name;	
				   if ((j < arrMessages.length)&&(arrMessages[j].length > 0))
					  message = arrMessages[j];
				      InsertError(element, message);
				 } else {
				   message = ccErrors[ccErrorNo];
				  InsertError(element, message);
				}
				
				if ((typeof element.focus == "function")||(element.focus)) {
					element.focus();
				}
				return false;
			}
			else
				ClearError();
		}
		
	}

	  if(isFormSubmit) {
		objForm.submit();   
	  } else {
	    return true;
	  }
}

//Empty Validation
function ValidateNotEmpty(objElement) {
	var strValue = GetElementValue(objElement);
	var blnResult = true;
	if(allTrim(strValue) == "") //check for nothing
	{
	blnResult = false;
	}
	return blnResult;
}

//Integer Validation
function  ValidateInteger(objElement)
   //  check for valid numeric strings	
   {
   var strString = GetElementValue(objElement);
   var strValidChars = "0123456789";
   var strChar;
   var blnResult = true;
  
   //  test strString consists of valid characters listed above
   for (i = 0; i < strString.length && blnResult == true; i++)
      {
      strChar = strString.charAt(i);
      if (strValidChars.indexOf(strChar) == -1)
         {
         blnResult = false;
         }
      }
   return blnResult;
   }

//Number Validation
function  ValidateNumber(objElement)
   //  check for valid numeric strings	
   {
   var strString = GetElementValue(objElement);
   var strValidChars = ".0123456789"; //decimal ok
   var strChar;
   var blnResult = true;

   //  test strString consists of valid characters listed above
   for (i = 0; i < strString.length && blnResult == true; i++)
      {
      strChar = strString.charAt(i);
      if (strValidChars.indexOf(strChar) == -1)
         {
         blnResult = false;
         }
      }
   return blnResult;
   }
   
   //Email Validation
   function ValidateEmail(objElement) {
	//  Will check for @, period after @ and text in between
	var strValue = GetElementValue(objElement);
	var in_space = strValue.indexOf(" ");
	if (in_space != -1)
	{ return false;  }

	var len = strValue.length;
	var alpha = strValue.indexOf("@");
	var last_alpha = strValue.lastIndexOf("@");

	if (alpha != last_alpha)
	 { return false; }

	// No @, in first position, or name too short
	if (alpha == -1 || alpha == 0 || len<6 )
	 { return false; }

	var last_p = strValue.lastIndexOf(".");
			// Be sure period at least two spaces after @, but not last char.
			
	if (last_p - alpha < 2 || last_p == (len - 1) )
		{ return false; }
	}
	

	
	//Valid PhoneNumber
	function ValidatePhone(objElement){
	// non-digit characters which are allowed in phone numbers
	var phoneNumberDelimiters = "()- ";
	// characters which are allowed in international phone numbers
	// (a leading + is OK)
	var validWorldPhoneChars = phoneNumberDelimiters + "+";
	// Minimum no of digits in an international phone no.
	var minDigitsInIPhoneNumber = 10;
	
	var strValue = GetElementValue(objElement);
	s=stripCharsInBag(strValue,validWorldPhoneChars);
	return (ValidateInteger(s) && s.length >= minDigitsInIPhoneNumber);
	}

//Min Max Validation
function  ValidateMinMax(objElement, Checktype, strMin, strMax) {
   var strString = GetElementValue(objElement);
   var strLen = strString.length;
   var blnResult = true;

switch (Checktype) {
	   case "<":
	     if (strMax < strLen) { blnResult = false; }
	   break;
	   case ">":
         if (strMin > strLen) { blnResult = false; }
	   break;
	   case "<>":
         if ((strMin > strLen) && (strMax > strLen)) { blnResult = false; }
	   break;
	   case "=":
         if ((strLen != strMax)) { blnResult = false; }
	   break;
	}
   return blnResult;
   }
   
function ValidateCreditCard (objElement, cardname) {

  cardnumber = GetElementValue(objElement);
  ccErrorNo = '';
  // Array to hold the permitted card characteristics
  var cards = new Array();

  // Define the cards we support. You may add addtional card types.
  
  //  Name:      As in the selection box of the form - must be same as user's
  //  Length:    List of possible valid lengths of the card number for the card
  //  prefixes:  List of possible prefixes for the card
  //  checkdigit Boolean to say whether there is a check digit
  
  cards [0] = {name: "V", 
               length: "13,16", 
               prefixes: "4",
               checkdigit: true};
  cards [1] = {name: "M", 
               length: "16", 
               prefixes: "51,52,53,54,55",
               checkdigit: true};
  cards [2] = {name: "DinersClub", 
               length: "14,16", 
               prefixes: "300,301,302,303,304,305,36,38,55",
               checkdigit: true};
  cards [3] = {name: "CarteBlanche", 
               length: "14", 
               prefixes: "300,301,302,303,304,305,36,38",
               checkdigit: true};
  cards [4] = {name: "A", 
               length: "15", 
               prefixes: "34,37",
               checkdigit: true};
  cards [5] = {name: "D", 
               length: "16", 
               prefixes: "6011,650",
               checkdigit: true};
  cards [6] = {name: "JCB", 
               length: "15,16", 
               prefixes: "3,1800,2131",
               checkdigit: true};
  cards [7] = {name: "enRoute", 
               length: "15", 
               prefixes: "2014,2149",
               checkdigit: true};
  cards [8] = {name: "Solo", 
               length: "16,18,19", 
               prefixes: "6334, 6767",
               checkdigit: true};
  cards [9] = {name: "Switch", 
               length: "16,18,19", 
               prefixes: "4903,4905,4911,4936,564182,633110,6333,6759",
               checkdigit: true};
  cards [10] = {name: "Maestro", 
               length: "16", 
               prefixes: "5020,6",
               checkdigit: true};
  cards [11] = {name: "VisaElectron", 
               length: "16", 
               prefixes: "417500,4917,4913",
               checkdigit: true};
               
  // Establish card type
  var cardType = -1;
  for (var i=0; i<cards.length; i++) {

    // See if it is this card (ignoring the case of the string)
    if (cardname.toLowerCase () == cards[i].name.toLowerCase()) {
      cardType = i;
      break;
    }
  }
  
  // If card type not found, report an error
  if (cardType == -1) {
     ccErrorNo = 0;
     return false; 
  }
   
  // Ensure that the user has provided a credit card number
  if (cardnumber.length == 0)  {
     ccErrorNo = 1;
     return false; 
  }
  
  // Check that the number is numeric, although we do permit a space to occur  
  // every four digits. 
  var cardNo = cardnumber
  var cardexp = /^([0-9]{4})\s?([0-9]{4})\s?([0-9]{4})\s?([0-9]{1,4})$/;
  if (!cardexp.exec(cardNo))  {
     ccErrorNo = 2;
     return false; 
  }
    
  // Now remove any spaces from the credit card number
  cardexp.exec(cardNo);
  cardNo = RegExp.$1 + RegExp.$2 + RegExp.$3 + RegExp.$4;
       
  // Now check the modulus 10 check digit - if required
  if (cards[cardType].checkdigit) {
    var checksum = 0;                                  // running checksum total
    var mychar = "";                                   // next char to process
    var j = 1;                                         // takes value of 1 or 2
  
    // Process each digit one by one starting at the right
    var calc;
    for (i = cardNo.length - 1; i >= 0; i--) {
    
      // Extract the next digit and multiply by 1 or 2 on alternative digits.
      calc = Number(cardNo.charAt(i)) * j;
    
      // If the result is in two digits add 1 to the checksum total
      if (calc > 9) {
        checksum = checksum + 1;
        calc = calc - 10;
      }
    
      // Add the units element to the checksum total
      checksum = checksum + calc;
    
      // Switch the value of j
      if (j ==1) {j = 2} else {j = 1};
    } 
  
    // All done - if checksum is divisible by 10, it is a valid modulus 10.
    // If not, report an error.
    if (checksum % 10 != 0)  {
     ccErrorNo = 3;
     return false; 
    }
  }  

  // The following are the card-specific checks we undertake.
  var LengthValid = false;
  var PrefixValid = false; 
  var undefined; 

  // We use these for holding the valid lengths and prefixes of a card type
  var prefix = new Array ();
  var lengths = new Array ();
    
  // Load an array with the valid prefixes for this card
  prefix = cards[cardType].prefixes.split(",");
      
  // Now see if any of them match what we have in the card number
  for (i=0; i<prefix.length; i++) {
    var exp = new RegExp ("^" + prefix[i]);
    if (exp.test (cardNo)) PrefixValid = true;
  }
      
  // If it isn't a valid prefix there's no point at looking at the length
  if (!PrefixValid) {
     ccErrorNo = 3;
     return false; 
  }
    
  // See if the length is valid for this card
  lengths = cards[cardType].length.split(",");
  for (j=0; j<lengths.length; j++) {
    if (cardNo.length == lengths[j]) LengthValid = true;
  }
  
  // See if all is OK by seeing if the length was valid. We only check the 
  // length if all else was hunky dory.
  if (!LengthValid) {
     ccErrorNo = 4;
     return false; 
  };   
  
  // The credit card is in the required format.
  return true;
} 
   
function GetElementValue(objElement) {
	var result="";
	switch (objElement.type) {
		case "text":
		case "hidden":
		case "textarea":
		case "password":
			result = objElement.value;
			break;
		case "select-one":
		case "select":
			if (objElement.selectedIndex >= 0)
				result = objElement.options[objElement.selectedIndex].value;
			break;
		case "radio":
		case "checkbox":
			for (var i=0; i<objElement.form.elements.length; i++) {
				if (objElement.form.elements[i].name == objElement.name) {
					if (objElement.form.elements[i].checked)
						result += objElement.form.elements[i].value+",";
				}
			}
			break;
	}
	return result;
}

function InsertError(element, strMessage) {
	if (element != '') {
	   if ((element.form.getAttribute("show_alert")) && (element.form.getAttribute("show_alert") != "0")) {
		alert(strMessage);
		return;
	   }
	}
	ClearError();
	var objSpan = document.getElementById('div_error');
    objSpan.innerHTML = '<img src="/images/icon_error_pulse.gif" width="16" height="16">&nbsp;' + strMessage;
}

function ClearError() {
    var objSpan = document.getElementById('div_error');
	if (objSpan) {
		objSpan.innerHTML = "";
	}
}

function allTrim(cValue){
 var lDone=false;
 while (lDone==false){
  if (cValue.length==0) {return cValue;}
  if (cValue.indexOf(' ')==0){cValue=cValue.substring(1);lDone=false; continue;}
  else {lDone=true;}
  if (cValue.lastIndexOf(' ')==cValue.length-1){cValue=cValue.substring(0, cValue.length-1);lDone=false;continue;}
  else {lDone=true;}
 }
 return cValue;
}

function stripCharsInBag(s, bag)
{   var i;
    var returnString = "";
    // Search through string's characters one by one.
    // If character is not in bag, append to returnString.
    for (i = 0; i < s.length; i++)
    {   
        // Check that current character isn't whitespace.
        var c = s.charAt(i);
        if (bag.indexOf(c) == -1) returnString += c;
    }
    return returnString;
}
