// formcheck.js
// a group of form-checking routines
// for use on *.bzzagent.com
// adapted by Greg Bowne


// is the item an array (used for single radio/check
function isArray(obj){
	return(typeof(obj.length)=="undefined")?false:true;
}

// is checkbox or radio checked?
function isChecked(s) {
	if (isArray(s)) {
		for (i=0; i< s.length; i++) {
			if (s[i].checked==true) { return true; }
		}
	} else {
		if (s.checked==true) { return true; }
	}
	return false;
}

// is item in option list selected?
function isSelected(s) {
	if ((s.options[0].value != "") && (s.selectedIndex > -1)) { return true; }
	else if (s.selectedIndex > 0) { return true; }
	return false;
}

// Is the string non-existent (null) or is its length equal to zero?
function isEmpty(s)
{   return ((s == null) || (s.length == 0))
}

var USStateCodeDelimiter = "|";
var USStateCodes = "AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|MP|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY|AA|AE|AP";

function isState (s)
// Is the string a US state abbreviation(CT, VT, az)?
{
    upState = s.toUpperCase();
    return ( (USStateCodes.indexOf(upState) != -1) &&
             (upState.indexOf(USStateCodeDelimiter) == -1) )
}

var ProvinceCodes = "AB|BC|MB|NB|NL|NT|NS|NU|ON|PE|PQ|QC|SK|YT";
function isProvince (s)
// Is the string a Canadian abbreviation(ON, YT, ab)?
{
    upState = s.toUpperCase();
    return ( (ProvinceCodes.indexOf(upState) != -1) &&
             (upState.indexOf(USStateCodeDelimiter) == -1) )
}

var ZIPCodeDelimiters = "- ";
function isZIPCode (s)
// Is the string a valid zipcode, minus the symbols (12345, 123412345)?
{
   return (isInteger(s) &&
            ((s.length == 5) ||
             (s.length == 9)))
}

// Convert a string to zip code format (12345 stays the same, 123456789 becomes 12345-6789)
function reformatZIPCode (ZIPString)
{   if (ZIPString.length == 5) return ZIPString;
    else return (reformat (ZIPString, "", 5, "-", 4));
}

// Is a given field a valid zipcode format?
// Reformat it if it's OK.
// If an alert is requested and the field is invalid then put up an alert.
function checkZIPCode (theField)
{
  var normalizedZIP = stripCharsInBag(theField.value, ZIPCodeDelimiters);
  if (!isZIPCode(normalizedZIP)) { return false;
  } else {
    theField.value = reformatZIPCode(normalizedZIP);
    return true;
  }
}

var digits = "0123456789";
// Is the field a valid Credit Card number following the Luhn formula?
function luhnCheck(s) {
   var CardNumber = stripCharsNotInBag(s,digits)
   var no_digit = CardNumber.length;
   var oddoeven = no_digit & 1;
   var sum = 0;

   for (var count = 0; count < no_digit; count++) {
      var digit = parseInt(CardNumber.charAt(count));
      if (!((count & 1) ^ oddoeven)) {
         digit *= 2;
         if (digit > 9)
         digit -= 9;
      }
      sum += digit;
   }
   if (sum % 10 == 0)
   return true;
   else
   return false;
}

function isCvv2 (s)
// Is the string a valid Card Verification number, i.e. 3 or 4 digits?
{
   return (isInteger(s) &&
            ((s.length == 3) ||
             (s.length == 4)))
}

function checkCredit(theField, isAlert) {
var cleanedCard = stripCharsNotInBag(theField.value, digits);
  if (!luhnCheck(cleanedCard)) { return false;
  } else {
    theField.value = cleanedCard;
    return true;
  }
}

// Is the string a valid Canadian Postal Code, A1B2C3?
var CanadaFormat = /^\s*[a-ceghj-npr-tvxy]\d[a-z](\s)?\d[a-z]\d\s*$/i;
function isCanadaPostalCode(s) {
	if (!s.match(CanadaFormat)) { return false; }
	return true;
}


function checkCanadaPostalCode (theField, isAlert)
{
  var normalizedCPC = stripCharsInBag(theField.value, ZIPCodeDelimiters);
  if (!isCanadaPostalCode(normalizedCPC)) { return false
  } else {
    theField.value = reformat(normalizedCPC.toUpperCase(),"",3," ",3);
    return true;
  }
}

// Is the string a valid UK Postal Code, A1B2C3?
var UKPostalFormat = /^[A-Z]{1,2}[0-9R][0-9A-Z]?[0-9][ABD-HJLNP-UW-Z]{2}$/i;
function isUKPostalCode(s) {
	if (!s.match(UKPostalFormat)) { return false; }
	return true;
}

function checkUKPostalCode (theField, isAlert)
{
  var normalizedUKP = stripCharsInBag(theField.value, ZIPCodeDelimiters).toUpperCase();
  if (!isUKPostalCode(normalizedUKP)) { return false
  } else {
  	len = normalizedUKP.length
  	incode = normalizedUKP.substr(len-3,3); // last 3 characters
	outcode = normalizedUKP.substr(0,normalizedUKP.length-3); // first n characters
    theField.value = outcode+" "+incode;
    return true;
  }
}

var emailFormat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,6})$/;
//list of TLDs garnered from http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains

function isEmail (s)
// Is the string in a valid email address format (x@y.z)?
{
	if (!s.match(emailFormat)) { // now see if they've got something else going on
		return false;
	} else {
		return true;
	}
	// keep old way of doing just in case!
    var i = 1;
    var sLength = s.length;
	while ((i < sLength) && (s.charAt(i) != "@")) {
		i++
 	}
    if ((i >= sLength) || (s.charAt(i) != "@")){
		return false;
	} else i += 2;
    while ((i < sLength) && (s.charAt(i) != ".")) {
		i++
    }
    if ((i >= sLength - 1) || (s.charAt(i) != ".")) {
		return false;
	} else {
		return true;
	}
}

function cleanEmail(s) { // for emails like Greg Bowne <greg@bzzagent.com> returns just greg@bzzagent.com
	if(s.match(/<(.*)>/)) {
	//alert (s);
	doublecheck = s.match(/<(.*)>/);
	//alert(doublecheck[0]);
	s =doublecheck[1];
	//alert(s);
	}
	return s;
}

function isURL (s) { // Is the string in a valid url (http(s)://)?
	var site = s.substring(0, 8);
	if (s.substring(0,1)=='/') { return true; } // it's an internal link
	if ((site.substring(0,7) != "http://") && (site != "https://") ) {
		return false;
	} else {
		dot = s.indexOf('.');
		if (dot > 9 && dot < (s.length - 2))	{
			return true;
		} else {
			return false;
		}
	}
}

var phoneNumberDelimiters = "()-. ";
var validWorldPhoneChars = digits + phoneNumberDelimiters + "+" + "/";

function allCharsInBag (s, bag)
{
	flag=true;
	for (i = 0; i < s.length; i++)
	{
		flag=flag&&(bag.indexOf(s.charAt(i)) != -1);
	}
    return (flag);
}

function isDigit (c)
{   return ((c >= "0") && (c <= "9"))
}

function isLetter (c)
{   return ( ((c >= "a") && (c <= "z")) || ((c >= "A") && (c <= "Z")) )
}

function isInteger (s)
{   var i;

	for (i = 0; i < s.length; i++)
    {
        var c = s.charAt(i);
        if (!isDigit(c)) return false;
    }
        return true;
}

function isNumeric (s)
{   var i;

	for (i = 0; i < s.length; i++)
    {
        var c = s.charAt(i);
        if (!isDigit(c)) return false;
    }
        return true;
}

function noShout (s) // checks to make sure the text isn't all caps
{
	if (s.length > 2 && s.match(/[A-Z]/i) && !s.match(/\d/)) { // make sure there are letters. if letters and numbers, okay.
		uc = s.toUpperCase();
		if (s == uc) {
			return false;
		}
	}
	return true;
}

function stripCharsNotInBag (s, bag)
{   var i;
    var returnString = "";

    for (i = 0; i < s.length; i++)
    {
        var c = s.charAt(i);
        if (bag.indexOf(c) != -1) returnString += c;
    }
    return returnString;
}

function stripCharsInBag (s, bag)
{   var i;
    var returnString = "";

    for (i = 0; i < s.length; i++)
    {
        var c = s.charAt(i);
        if (bag.indexOf(c) == -1) returnString += c;
    }
    return returnString;
}

var USPhoneFormat = /^(1\s*[-\/\.]?)?(\((\d{3})\)|(\d{3}))\s*[-\/\.]?\s*(\d{3})\s*[-\/\.]?\s*(\d{4})\s*(([xX]|[eE][xX][tT])\.?\s*(\d+))*$/;
function isUSPhone (s) {
	if (!s.match(USPhoneFormat)) { return false; }
	return true;
}

function reformat (s)
{   var arg;
    var sPos = 0;
    var resultString = "";
    for (var i = 1; i < reformat.arguments.length; i++) {
       arg = reformat.arguments[i];
       if (i % 2 == 1) resultString += arg;
       else {
           resultString += s.substring(sPos, sPos + arg);
           sPos += arg;
       }
    }
    return resultString;
}

function reformatUSPhone (USPhone)
{
	sections = USPhone.match(USPhoneFormat);
	sections[2] = stripCharsInBag(sections['2'],'()');
	if (sections !=  null) {
		// ignore sections[1], the 1 in  1(206)

		newformat = "("+ sections[2]+") "+sections[5]+"-"+sections[6];
		if (sections[9] == '' || undefined === sections[9] || null===sections[9]) { sections[9] = ''; }
		if (sections[9].length >= 1) {
			newformat += " x"+sections[9];
		}
	}
	return (newformat);
}

// Is a given field a valid US phone number format?
// Reformat it if it's OK.
function checkUSPhone (theField, isAlert)
{
  if (!isUSPhone(theField.value)) {
  	return false;
  } else {
    theField.value = reformatUSPhone(theField.value);
    return true;
  }
}

var UKPhoneFormat = /^(\+?[1-9][0-9]{0,3})?([(/ ]?0[0-9]+[)/ ]+)([0-9 -]+)$/; // country code optional
// if this doesnt' work, we might try this: http://regexlib.com/REDetails.aspx?regexp_id=684

function isUKPhone (s)
{
	if (!s.match(UKPhoneFormat)) { return false; }
	if (s.length <= 9) { return false; }
	return true;
}

function reformatUKPhone (UKPhone)
{
	var sections = UKPhone.match(UKPhoneFormat);
	if (sections !=  null) {
		if (sections[1] == '' || undefined === sections[1] || null===sections[1]) { sections[1] = ''; }
		else {
			sections[1] = "+" + sections[1].match(/[0-9]+/)+"/";
		}
		sections[3] = stripCharsInBag(sections[3]," -");
		if (sections[3].length >=7) { // reformat to XXX[X] XXXX
			len = sections[3].length;
			part2 = sections[3].substr(len-4); // last 4 digits
			part1 = sections[3].substr(0,len-4); // first n characters
		    sections[3] = part1+" "+part2;
		} else {  // reformat to XXX XXX
			len = sections[3].length;
			part2 = sections[3].substr(len-3); // last 3 digits
			part1 = sections[3].substr(0,len-3); // first n characters
		    sections[3] = part1+" "+part2;
		}
		newformat = sections[1] + sections[2].match(/[0-9]+/) + " "+ sections[3];
	}
	return (newformat);
}

// Is a given field a valid UK phone number format?
// Reformat it if it's OK.
function checkUKPhone (theField, isAlert)
{
  if (!isUKPhone(theField.value)) { return false;
  } else {
    theField.value = reformatUKPhone(theField.value)
    return true;
  }
}

//var intPhoneFormat = /^(\+[0-9]{1,3})[(/ ]?([0-9])+[)/ ]?([0-9- ]+)$/; // country code required
var intPhoneFormat = /^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,3})|(\(?\d{2,3}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/;
function isInternationalPhone (s)
// Do all the characters in the string fit in the bag of valid world phone number characters?
{
    return (allCharsInBag(s, validWorldPhoneChars));
}
function checkInternationalPhone (theField, isAlert)
{
  if (!isInternationalPhone(theField))
    {
      if (isAlert) warnInvalid (theField, iWorldPhone);
      return false;
    }
    else
    {
      return true;
    }
}

// check to see if item is a floating
function checkMoney(theField) {
	num = theField.value.toString().replace(/\$|\,/g,'');
	if(isNaN(num)) { return false; }
	sign = (num == (num = Math.abs(num)));
	num = Math.floor(num*100+0.50000000001);
	cents = num%100;
	num = Math.floor(num/100).toString();
	if(cents<10)
	cents = "0" + cents;
	for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++)
	num = num.substring(0,num.length-(4*i+3))+','+
	num.substring(num.length-(4*i+3));
	theField.value = (((sign)?'':'-') + num + '.' + cents);
	return true;
}

var defaultEmptyOK = false;
// *******************************************************
// Does the string represent a valid signed integer (+7, -111)?
function isSignedInteger (s)
{   if (isEmpty(s))
       if (isSignedInteger.arguments.length == 1) return defaultEmptyOK;
       else return (isSignedInteger.arguments[1] == true);
    else {
        var startPos = 0;
        var secondArg = defaultEmptyOK;
        if (isSignedInteger.arguments.length > 1)
            secondArg = isSignedInteger.arguments[1];
                if ( (s.charAt(0) == "-") || (s.charAt(0) == "+") )
           startPos = 1;
        return (isInteger(s.substring(startPos, s.length), secondArg))
    }
}
// *******************************************************
// Does the string represent a positive integer (7, 111, +9)?
function isPositiveInteger (s)
{   var secondArg = defaultEmptyOK;
    if (isPositiveInteger.arguments.length > 1)
        secondArg = isPositiveInteger.arguments[1];

    return (isSignedInteger(s, secondArg)
         && ( (isEmpty(s) && secondArg)  || (parseInt (s, 10) > 0) ) );
}
// *******************************************************
// Does the string represent a non-negative integer (7, 111, +9, 0)?
function isNonnegativeInteger (s)
{   var secondArg = defaultEmptyOK;
    if (isNonnegativeInteger.arguments.length > 1)
        secondArg = isNonnegativeInteger.arguments[1];

    return (isSignedInteger(s, secondArg)
         && ( (isEmpty(s) && secondArg)  || (parseInt (s, 10) >= 0) ) );
}
// *******************************************************
// Does the string represent a negative integer (-9)?
function isNegativeInteger (s)
{   var secondArg = defaultEmptyOK;
    if (isNegativeInteger.arguments.length > 1)
        secondArg = isNegativeInteger.arguments[1];

    return (isSignedInteger(s, secondArg)
         && ( (isEmpty(s) && secondArg)  || (parseInt (s, 10) < 0) ) );
}
// *******************************************************
// Does the string represent a non-positive integer (-9, 0)?
function isNonpositiveInteger (s)
{   var secondArg = defaultEmptyOK;
    if (isNonpositiveInteger.arguments.length > 1)
        secondArg = isNonpositiveInteger.arguments[1];

    return (isSignedInteger(s, secondArg)
         && ( (isEmpty(s) && secondArg)  || (parseInt (s, 10) <= 0) ) );
}
// *******************************************************
function isYear (s)
// Is the string a valid positive year (43, 1911, 2011)?
{   if (isEmpty(s))
       if (isYear.arguments.length == 1) return defaultEmptyOK;
       else return (isYear.arguments[1] == true);
    if (!isNonnegativeInteger(s)) return false;
    return (s.length == 4);
}
// *******************************************************
// Is the integer in the range a to b?
function isIntegerInRange (s, a, b)
{   if (isEmpty(s))
       if (isIntegerInRange.arguments.length == 1) return defaultEmptyOK;
       else return (isIntegerInRange.arguments[1] == true);
            if (!isInteger(s, false)) return false;
                    var num = parseInt (s, 10);
    return ((num >= a) && (num <= b));
}

var decimalPointDelimiter=".";
// Is the number a float?
function isFloat (s)
{   var i;
    var seenDecimalPoint = false;
    if (isEmpty(s))
       if (isFloat.arguments.length == 1) return defaultEmptyOK;
       else return (isFloat.arguments[1] == true);
    if (s == decimalPointDelimiter) return false;

    for (i = 0; i < s.length; i++)
    {
                var c = s.charAt(i);
        if ((c == '.') && !seenDecimalPoint) seenDecimalPoint = true;
        else if (!isDigit(c)) return false;
    }
        return true;
}
// *******************************************************
// Does the string represent a signed floating-point number (-9.1, +0.5)?
function isSignedFloat (s)
{   if (isEmpty(s))
       if (isSignedFloat.arguments.length == 1) return defaultEmptyOK;
       else return (isSignedFloat.arguments[1] == true);
    else {
        var startPos = 0;
        var secondArg = defaultEmptyOK;
        if (isSignedFloat.arguments.length > 1)
            secondArg = isSignedFloat.arguments[1];
                if ( (s.charAt(0) == "-") || (s.charAt(0) == "+") )
           startPos = 1;
        return (isFloat(s.substring(startPos, s.length), secondArg))
    }
}

// *******************************************************
// Is the float in the range a to b?
function isFloatInRange (s, a, b)
{   if (isEmpty(s))
       if (isFloatInRange.arguments.length == 1) return defaultEmptyOK;
       else return (isFloatInRange.arguments[1] == true);
            if (!isFloat(s, false)) return false;
                    var num = parseFloat (s);
    return ((num >= a) && (num <= b));
}

// if the date isn't in SQL date format, then try to fix it.
function checkSQLDate(fieldName) {
	var tbd = /tbd/i;
	var sqlDate = /(\d+){4,4}-(\d+){2,2}-(\d+){2,2}/;
	if (fieldName.value.match(sqlDate)) {
		return true;
	}
	if (fieldName.value.match(tbd)) {
		fieldName.value="TBD";
		return true;
	}
	if (fieldName.value.match(/\./)) {
		fieldName.value = fieldName.value.replace(/\./g,"/");
	}
	dateInfo = Date.parse(fieldName.value);
	var d = new Date(dateInfo);
	var curr_date = d.getDate();
	if (isNaN(curr_date)) { return false; }
	var curr_month = d.getMonth();
	curr_month++;
	var curr_year = d.getFullYear();
	if (curr_year < 2000) { curr_year += 100; }
	fieldName.value = (curr_year+"-");
	if (curr_month < 10) {fieldName.value += "0"; }
	fieldName.value += (curr_month + "-");
	if (curr_date < 10) {fieldName.value += "0"; }
	fieldName.value += (curr_date);
	return true;
}

// if the date + time isn't in SQL date format, then try to fix it.
function checkSQLDateTime(fieldName) {
	var tbd = /--/i;
	var sqlDate = /(\d+){4,4}-(\d+){2,2}-(\d+){2,2} (\d+){2,2}:(\d+){2,2}:(\d+){2,2}/;
	if (fieldName.value.match(sqlDate)) {
		return true;
	}
	if (fieldName.value.match(tbd)) {
		fieldName.value="";
		return true;
	}
	if (fieldName.value.match(/\./)) {
		fieldName.value = fieldName.value.replace(/\./g,"/");
	}
	dateInfo = Date.parse(fieldName.value);
	var d = new Date(dateInfo);
	var curr_date = d.getDate();
	if (isNaN(curr_date)) { return false; }
	var curr_month = d.getMonth();
	curr_month++;
	var curr_year = d.getFullYear();
	if (curr_year < 2000) { curr_year += 100; }
	fieldName.value = (curr_year+"-");
	if (curr_month < 10) {fieldName.value += "0"; }
	fieldName.value += (curr_month + "-");
	if (curr_date < 10) {fieldName.value += "0"; }
	fieldName.value += (curr_date);
	return true;
}

// Make a blank array of size n (1..n), populated with zeroes
function makeArray(n) {
   for (var i = 1; i <= n; i++) {
      this[i] = 0;
   }
   return this;
}

// Make an array called daysInMonth
var daysInMonth = makeArray(12);
daysInMonth[1] = 31;
daysInMonth[2] = 29;
daysInMonth[3] = 31;
daysInMonth[4] = 30;
daysInMonth[5] = 31;
daysInMonth[6] = 30;
daysInMonth[7] = 31;
daysInMonth[8] = 31;
daysInMonth[9] = 30;
daysInMonth[10] = 31;
daysInMonth[11] = 30;
daysInMonth[12] = 31;
// *******************************************************
function isMonth (s)
// Is the string a month value (1..12)?
{
    return isIntegerInRange (s, 1, 12);
}
// *******************************************************
// Is the string a day of the month (1..31)?
function isDay (s)
{
    return isIntegerInRange (s, 1, 31);
}
// *******************************************************
// Given a year, return the number of days February will have.
function daysInFebruary (year)
{           return (  ((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0) ) ) ? 29 : 28 );
}
// *******************************************************
// Given year, month and day strings (1917, 11, 4) are they valid dates?
function isDate (year, month, day)
{       if (! (isYear(year, false) && isMonth(month, false) && isDay(day, false))) return false;
            var intYear = parseInt(year);
    var imonth = parseInt(month);
    var intDay = parseInt(day);
        if (intDay > daysInMonth[imonth]) return false;
    if ((imonth == 2) && (intDay > daysInFebruary(intYear))) return false;
    return true;
}

//*****************************************************
//checks forms dynamically
// requires class field to be filled with the following data
// type [required] [Field Name in Text] [min value] [max value]
// email required Contact.Email
// money
// required
// integer required
// ZIP optional 'ZIP Code'
// integer
function checkForm(thisForm) {
	var undefined;
	var extraMessage = "";
	if (arguments[1] != undefined) {
		extraMessage = arguments[1] + "\n";
	}
	for ( z = 0 ; z < thisForm.elements.length; z++) {
		var iclass = thisForm.elements[z].className;
		var iname = thisForm.elements[z].name;
		var iID = thisForm.elements[z].id;
		var itype = 'text';
		var required = false;
		var ifield;
		if (iclass.length ==0) { // no class info means no checking;
			continue;
		} else {
			// decide what reference to use. if the 'name' begins with # and the id is there, use it.
			//alert("z='"+z+"' class='"+iclass+"' name='"+iname+"' id= '"+iID+"'");
			if (!iname.match(/^\d/)) {
				ifield = thisForm[iname];
				//alert("Using Name="+iname);
			} else if (!iID.match(/^\d/)) {
				ifield = thisForm[iID];
				//alert ("Using ID "+ iID+" name="+iname);
			} else {
				//alert ("Field not addressable id="+iID+" name="+iname);
				continue;
			}
			var check_info = iclass.split(' ');
			if (check_info[1] == 'required') { required = true; }
			if (check_info[0] == 'required') {
				required = true;
				itype = thisForm.elements[z].type;
			} else {
				itype = check_info[0];
			}
			if (check_info[2] != undefined && check_info[2] != '') { ititle = check_info[2].replace(/\./g,' '); } // change dots to spaces '
			else { ititle = false; }
			if (check_info[3] != undefined && check_info[3] != '') { imin = check_info[3]; }
			else { imin = false; }
			if (check_info[4] != undefined && check_info[4] != '') { imax = check_info[4]; }
			else { imax = false; }
		}
		// first make sure not empty, checked, etc.
		valid = true;
		focusable = true;
		if (required) {
			switch(itype) {
				case "checkbox" :
				case "checkbox-matrix" :
				case "radio"    :
				case "radio-matrix" : if (!isChecked(ifield)) { valid = false; focusable = false;}
								break
				case "select-multiple" :
				case "select-one" : if (!isSelected(ifield)) { valid = false;}
								break
				case "textarea" :
				case "password" :
				case "TEXT"		: // allows for allcaps
				case "text"		: if (isEmpty(ifield.value)) { valid = false;}
								break
				default 	    : if (isEmpty(ifield.value)) { valid = false; }
			}
		}

		if (!valid) {
			if (ititle != '') { msg = extraMessage+ititle+" may not be left blank." }
			else { msg = extraMessage+"You have left a required field blank"; }
			if (itype == 'radio' || itype == 'checkbox' || itype.match(/select/)) {
				msg = extraMessage+"You have left a required field blank.\nPlease make a selection";
				if (ititle != '') { msg= extraMessage+"You have left the field "+ititle+" blank\nPlease make a selection"; }
			}
			if (itype == 'radio-matrix' || itype == 'checkbox-matrix') {
				msg = extraMessage+"Each row in a matrix question requires an answer.\nPlease make a selection";
				if (ititle != '') { msg= extraMessage+"Each row in a matrix question requires an answer.\nPlease make a selection for "+ititle; }
			}
			if (focusable) {alert(msg); ifield.focus(); }
			else { alert(msg);
				if (isArray(ifield)) { ifield[0].focus(); } // if there's only one, it's not an array.
				else { ifield.focus(); }
			}
			scrollBy(0,-45);
			return false;
		}
		// then validate on type
		switch(itype) {
			case "textarea":
			case "text": if (!isEmpty(ifield.value) && !noShout(ifield.value)) {
							valid=false;
							msg2 = "should be mixed case, not ALL CAPS.\nPlease make sure your Caps-Lock is not on.";
							}
							break;
			case "TEXT": if (isEmpty(ifield.value) && required) {
							valid=false;
							msg2 = "must be text.";
							}
							break;
			case "integer" : if (!isEmpty(ifield.value) && !isInteger(ifield.value)) {
							valid = false;
							msg2 = "must be an integer\n 1, 2, 3, etc.";
							break; }
							if (!isNaN(imin)) {
								if (!isNaN(imax) && imax > imin) {
									if (ifield.value > imax || ifield.value < imin) {
										valid = false;
										msg2 = "must be between "+ imin + " and " + imax;
									}
								} else {
									if (ifield.value < imin) {
										valid = false;
										msg2 = "must be greater than "+ imin;
									}
								}
							}
							break;
			case "ZIP"     :
			case "zip" 	   : if (!isEmpty(ifield.value) && !checkZIPCode(ifield)) {
							valid = false;
							msg2 = "must be a US ZIP code, 5 or 9 digits\n 12345 or 12345-6594, etc."; }
							break;
			case "canada"  : if (!isEmpty(ifield.value) && !checkCanadaPostalCode(ifield)) {
							valid = false;
							msg2 = "must be a Canadian Postal Code, A1B 2V4, etc."; }
							break;
			case "postal" : if (!isEmpty(ifield.value) && !checkCanadaPostalCode(ifield) && !checkZIPCode(ifield)) {
							valid = false;
							msg2 = "must be a US ZIP code, 5 or 9 digits, 12345 or 12345-6594\n-OR-\na Canadian Postal Code, A1B 2V4"; }
							break;
			case "ukpostal" :
			case "ukpost"  : if (!isEmpty(thisForm[iname].value) && !checkUKPostalCode(thisForm[iname])) {
							valid = false;
							msg2 = "must be a UK Postcode, HP13 6DG, etc."; }
							break;
			case "state" :  if (!isEmpty(ifield.value) && !isState(ifield.value)) {
							valid = false;
							msg2 = "must be a US state abbreviation\ne.g., NY, MA, UT"; }
							break;
			case "province" :  if (!isEmpty(ifield.value) && !isProvince(ifield.value)) {
							valid = false;
							msg2 = "must be a Canadian province abbreviation\ne.g., ON, QC, YT"; }
							break;
			case "stateprov" : if (!isEmpty(ifield.value) && !isState(ifield.value) && !isProvince(ifield.value)) {
							valid = false;
							msg2 = "must be a US state or Canadian province abbreviation\ne.g., NY, MA, ON, QC"; }
							break;
			case "e-mail":
			case "email" : // first clear out any extraneous crap
							ifield.value=cleanEmail(ifield.value);
							if (!isEmpty(ifield.value) && !isEmail(ifield.value)) {
							valid = false;
							msg2 = "must be an email address\ne.g., bobby@gmail.com"; }
							break;
			case "URL":
			case "url" : if (!isEmpty(ifield.value) && !isURL(ifield.value)) {
							valid = false;
							msg2 = "must be a URL or Web address\ne.g., http://www.piedpipertravel.com"; }
							break;
			case "phone" : if (!isEmpty(ifield.value) && !checkUSPhone(ifield)) {
							valid = false;
							msg2 = "must be a phone number with area code,\ne.g., (617) 555-1212. (optional extension)"; }
							break;
			case "ukphone" : if (!isEmpty(ifield.value) && !checkUKPhone(ifield)) {
							valid = false;
							msg2 = "must be a valid UK phone number with city code,\ne.g., (020) 4690 1212."; }
							break;
			case "intphone" : if (!isEmpty(thisForm[iname].value) && !isInternationalPhone(thisForm[iname].value)) {
							valid = false;
							msg2 = "must be an international phone number with country & city codes,\ne.g., +44 (020)55364332."; }
							break;
			case "anyphone" : if (!isEmpty(ifield.value) && !(checkUSPhone(ifield) || isInternationalPhone(thisForm[iname].value))) {
							valid = false;
							msg2 = "must be a US/Canada telephone phone number with area code\n or an international telephone mumber with country & city codes,\ne.g., +44 (020)55364332."; }
							break;
			case "date" :  if (!isEmpty(ifield.value) && !checkSQLDate(ifield)) {
							valid = false;
							msg2 = "must be a date in SQL format,\ne.g., 2006-05-22 (YYYY-MM-DD)."; }
							break;
			case "datetime" :  if (!isEmpty(ifield.value) && !checkSQLDateTime(ifield)) {
							valid = false;
							msg2 = "must be a date and time in SQL format,\ne.g., 2006-05-22 18:30:00 (YYYY-MM-DD HH:MM:SS)."; }
							break;
			case "money" : if (!isEmpty(ifield.value) && !checkMoney(ifield)) {
							valid = false;
							msg2 = "must be number representing currency,\ne.g., 25000.00.";
							break; }
							if (!isNaN(imin)) {
								if (!isNaN(imax) && imax > imin) {
									if (thisForm[iname].value > imax || thisForm[iname].value < imin) {
										valid = false;
										msg2 = "must be between "+ imin + " and " + imax;
									}
								} else {
									if (thisForm[iname].value < imin) {
										valid = false;
										msg2 = "must be greater than "+ imin;
									}
								}
							}
							break;
			case "credit" : // luhn is a standard mathematical formula for credit cards involving a check-digit.
			case "luhn"   : if (!isEmpty(ifield.value) && !checkCredit(ifield)) {
							valid = false;
							msg2 = "must be a valid card number.\nPlease check the number and order of digits."; }
							break;
			case "cvv2"  :  if (!isEmpty(ifield.value) && !isCvv2(ifield.value)) {
							valid = false;
							msg2 = "must be a 3- or 4-digit card verification number."; }
							break;
			case "digits" : if (!isEmpty(ifield.value) && !(isNumeric(ifield.value))) {
							valid = false;
							msg2 = "must be all numbers."; }
							break;
			case "number" : if (!isEmpty(ifield.value) && !(isFloat(ifield.value) || isInteger(ifield.value))) {
							valid = false;
							msg2 = "must be number, integer or decimal."; }
							break;
			//default
		}
		if (!valid) {
			if (ititle) { msg = extraMessage+ititle+" "+msg2; }
			else { msg = extraMessage+"This field "+msg2; }
			msg.replace(/<br>/,"\n");
			alert(msg);
			if (focusable) { thisForm[iname].focus(); }
			return false;
		}
	}
	return true;
}

