// See end of document for tips on how to use this set of functions.

// BEGIN MASKS
/*
note:
required attributes: .re, .mask
recommended attributes: .maskReference
-  .re is a straightforward regular expression
-  .mask is a string description of the mask for error output
-  .valid does cleanup and evaluation not doable inside a RE, allowing pretty free reign.
-> 
*/

// Used in form checking code to find out if an item's RE matches its value.
function REValid (e) {
  var REData = eval(e.mask);
  var pattern = REData.re;

  // Force an exact match.
  // If you get an error that '0' is not an object, your RE probably has an error.
  if (!pattern.test(e.value)) {
    return false;
  }
  else {
    if (e.mask.valid != null) {
      if (e.mask.valid(e)) {
		return true;
      }
      else {
       return false;
      }
    }
  }
  return true;
}

var maskFullPhone = new Object();
maskFullPhone.re = /^(\(\d{3}\))\s*(\d{3})\s*(\-)\s?(\d{4})\s*$/;
// Clean up field because we can.
maskFullPhone.valid = function(e) {
  e.value = RegExp.$1 + RegExp.$2 + RegExp.$3 + RegExp.$4;
  return true;
}
maskFullPhone.mask = '(123)456-7890';

var maskLongPhone = new Object();
maskLongPhone.re = /^(\d{3}\-)\s*(\d{3})\s*(\-)\s?(\d{4})\s*$/;
// Clean up field because we can.
maskLongPhone.valid = function(e) {
  e.value = RegExp.$1 + RegExp.$2 + RegExp.$3 + RegExp.$4;
  return true;
}
maskLongPhone.mask = '123-456-7890';

var maskShortPhone = new Object();
maskShortPhone.re = /^\s*(\d{3})\s*\-*\s*(\d{4})\s*$/;
// Clean up field because we can.
maskShortPhone.valid = function(e) {
  e.value = RegExp.$1 + "-" + RegExp.$2;
  return true;
}
maskShortPhone.mask = '456-7890';

var maskZipPostal = new Object();
maskZipPostal.re = /^((\d{5})\s*-*\s*(\d{4}){0,1}|(\w\d\w)\s*\-*\s*(\d\w\d))\s*$/;
maskZipPostal.mask = "Zip [12345 or 12345-1234] PC [A2A-2A2]";
maskZipPostal.valid = function(e) {
	e.value = e.value.toUpperCase();
	return true;
}
// EMAIL
var maskeMail = new Object();
maskeMail.re = /^([\w-_\.]+)@[\w-_]+(\.[\w-_]+){1,}\s*$/;
maskeMail.mask = "email address e.g. abc@def.com";
maskeMail.valid = function(e) {
	return true;
}

var maskInteger = new Object();
maskInteger.re = /^(\d+)\s*$/;
maskInteger.mask = "Integer (A number with no decimals).";
maskInteger.valid = function(e) {
	var re = /^([^\s]+)\s*$/;
	e.value.replace(re, RegExp.$1);
}

// Custom masks for this form
var maskThreeDigits = new Object();
maskThreeDigits.re = /^\s*(\d{3})\s*$/;
maskThreeDigits.mask = "3 Digits (e.g. 123).";
maskThreeDigits.valid = function(e) {
	e.value = RegExp.$1;
	return true;
}
var maskFourDigits = new Object();
maskFourDigits.re = /^\s*(\d{4})\s*$/;
maskFourDigits.mask = "4 Digits (e.g. 1234).";
maskFourDigits.valid = function(e) {
	e.value = RegExp.$1;
	return true;
}

var maskAxis = new Object();
maskAxis.re = /^\d{1}\d{1}\d{1}\s*$/;
maskAxis.valid = function(e) {
	if (e.value > 180) return false;
	return true;
}
maskAxis.mask = "Must be between 000 and 180";

var maskPupil = new Object();
maskPupil.re = /^(\d{2})\.\d{1}\s*$/;
maskPupil.valid = function(e) {
	if (RegExp.$1 < 1 || RegExp.$1 > 15) return false;
	return true;
}
maskPupil.mask = "Must be between 01 and 15";

var maskPachymetry = new Object();
maskPachymetry.re = /^\d{3}\s*$/;
maskPachymetry.valid = function(e) {
	return true;
}
maskPachymetry.mask = "Must be between 000 and 999";

var maskSphereCylinder = new Object();
maskSphereCylinder.re = /^[+-]{1}\d{1}\d{1}\.\d{1}\d{1}\s*$/;
maskSphereCylinder.valid = function(e) {
	return true;
}
maskSphereCylinder.mask = "[+ or -]99.99";

// Call validate function by making a fake form object out of the element
// passed and tacking into an array which is itself tacked onto a parent
// object (the "pseudo-form").
function validateElement(e) {
  f = new Object();
  f.elements = new Array(e);
  f.length = f.elements.length;
  val = verify(f);
  //if (val == false) o.focus();
  return val;
}

// This version of validateElement allows specification of whether or not to put
// focus on an invalid field. It just calls validateElement(e) which callls validate(f).
function validateElementRet(e, ret) {
  val = validateElement(e);
  if (val == false && ret) {
      e.focus();
  }
  return val;
}

// END MASKS

/*
javascript seems to be very permissive with date entry, in that if a month or day
is out of range it will just add to the next higher date. This is not desirable
if one wants to carefully check a date. Therefore, this function was written. 

The function takes an object (e.g. textbox) reference, looks at the value, parses
it if it is all numbers (e.g. 1/1/1999), then tries to make a date object. If a
date is in mixed format (e.g. 1 jan, 1999 or jan 1, 1999) the day-of-month value is
checked against the day-of-month value when the string is turned into a date object.
If a date object is created successfully, return that, else return null.
*/
function getValidDate(e) {
  // Javascript does not like '.' or '-' as timepart delimiters, so replace them with '/'.
  e.value = e.value.replace(/[-.]/gi, "/");
  var day, month, year;
  // Represents a delimited numbered date string (e.g. 1/1/1999).
  var REdt = /^(\d+)[ ,\/](\d+)[ ,\/](\d+)/;
  var result = e.value.match(REdt);
  var validDate = true;
  // Continue if we have a date string in number format (e.g. 1999-12-30).
  // We'll deal with mixed format dates (e.g. 1 jan, 1999) later.
  if (result != null) {
    var tempDt = new Date(e.value);
    // Find out if we have a year first format.
    // Assume that we are entering a year >= 1000.
    if (RegExp.$1 >= 1000) {
      // Match day of month against what Javascript thought day of month.
      if (RegExp.$3 != tempDt.getDate()) {
	validDate = false;
      }
      else {
	alert(new Date(e.value));
	year = RegExp.$1; 
	day = RegExp.$3; 
	month = RegExp.$2;
      }
    }
    // .. or if we have a year last format.
    else if (RegExp.$3 >= 1000) {
      if (RegExp.$2 != tempDt.getDate()) {
	validDate = false;
      }
      else {
	alert(new Date(e.value));
	year = RegExp.$3; 
	day = RegExp.$2; 
	month = RegExp.$1; 
      }
    }
    // Default to error. Not sure if this is necessary.
    else validDate = false;
  }
  // ******** Return a date object if it had a number format (e.g. 1/1/1999).
  if (validDate && (result != null)) {
    return new Date(year, (month - 1), day);
  }

  // ******** Return a date object if it had a string format (e.g. 1 jan, 1999)
  if (validDate && (result == null)) {
    dt = new Date(e.value);
    // If we have a malformed date string, return null (error).
    if (isNaN(dt.valueOf())) {
      return null;
    } 
    // Make sure we havn't experienced a date rollover (e.g. jan 31, 1999 becomes feb 1, 1999).
    // Format: jan 1, 1999
    var REdt = /^\s*(\w+\s+(\d+)\s*[,]*\s+\d+)/;
    var result = e.value.match(REdt);
    if (RegExp.$2 == "") {
      // Format: 1 jan, 1999 - In IE the '|' operator doesn't seem to work, so have split check.
      REdt = /^\s*((\d+)\s+\w+\s*[,]*\s+\d+)/;
      result = e.value.match(REdt);
    }
    // Here, we see if there is a match with the date info submitted by the user and the date
    // info interpreted by Javascript (if there is a rollover they will not match).
    if (RegExp.$2 != dt.getDate()) {
      return null;
    }
    // Set date string to clean version.
    e.value = RegExp.$1;
    // Return date object (non-null therefore non-error).
    return dt;
  }
  else return null;
}

// Helper functions

// A utility function that returns true if a string contains only 
// whitespace characters.
function isBlank(t)
{
  var s = t.toString();
  for(var i = 0; i < s.length; i++) {
    var c = s.charAt(i);
    if ((c != ' ') && (c != '\n') && (c != '\t')) return false;
  }
  return true;
}

function errorLookFurther(e) {
  if ((! isBlank(e.value)) && (e.optional == false)) return true;
  return false;
}

function isEmpty (txt) {
  if ((txt == null) || (txt == "") || isBlank(txt)) {
    return true;
  }
  return false;
}

// This is the function that performs form verification. It will be invoked
// from the onSubmit() event handler. The handler should return whatever
// value this function returns.
function verify(f)
{
  var isText = false;
  var isSelect = false;
  var errorNo = 0;
  var erorr_s = "";
  var this_these = "";
  var msg;
  var empty_fields = "";
  var invalid_dates = "";
  var strings_too_long = "";
  var mask_errors = "";
  var errors = "";
  var dt;

  // Loop through the elements of the form, looking for all 
  // text and textarea elements that don't have an "optional" property
  // defined. Then, check for fields that are empty and make a list of them.
  // Also, if any of these elements have a "min" or a "max" property defined,
  // then verify that they are numbers and that they are in the right range.
  // Put together error messages for fields that are wrong.
  for(var i = 0; i < f.length; i++) {
    var e = f.elements[i];

    // Set some properties for each element. This allows for more readable code later.
    e.isText = ((e.type == "text") || (e.type == "textarea"));
    e.isSelect = ((e.type == "select-one") || (e.type == "select-multiple"));
    e.optional = ((e.optional == null) || (e.optional == true)); 

    // Allow use of "label" instead of "title".
    if (! e.title) {
      if (e.label) {
      	e.title = e.label;
      }
      else e.title=e.name;
    }

    // Check fields that are select boxes or text or textarea type.	
    if ((e.isText || e.isSelect)/* && (e.optional == false || ((e.mask != null) && (e.mask != "")) ) */ ) 
      {
	e.continueValidation = true;
	// First check non-optional items. We must be sure to avoid checking non-optional items and optional
	// items that have restrictions such as a mask or numeric.
	if (! e.optional) {
	  // Handle non-optional empty select list case.
	  if ((e.isSelect && ((e.selectedIndex == -1) || isBlank(e.options[e.selectedIndex].text)))) 
	    {
	      empty_fields += "\n          - " + e.title;
	      errorNo += 1;
	      e.continueValidation = false;
	      continue;	  
	    }
	  if (e.isText && ((e.value == null) || (e.value == "") || isBlank(e.value))) {
	    empty_fields += "\n          - " + e.title;
	    errorNo += 1;
	    e.continueValidation = false;
	    continue;
	  }
	}
	// Now check optional items to see first if they have restrictions (numeric, mask, date, etc.) and
	// second if they meet their restrictions.
	if (e.continueValidation) {
	  // handle non-empty date field validation case.
	  if ((! isEmpty(e.value)) && e.date) {
	    dt = getValidDate(e);
	    //alert(dt);
	    if (dt == null) {
	      invalid_dates += "\n          - " + e.title;
	      errorNo += 1;
	      continue;
	    }
	  }
	  // Handle length check on textarea case.
	  if ((e.type == "textarea") && (e.strLength != null)) {
	    if (e.value.length > e.strLength) {
	      strings_too_long += "\n          - " + e.title + " (max length=" + e.strLength + ")";
	      errorNo += 1;
	      continue;
	    }
	  }
	  // Handle non-empty mask field validation case.
	  if ((! isEmpty(e.mask)) && (! isEmpty(e.value))) { 
	    // ABOVE WAS ((e.mask != "") && (e.mask != null)) && !((e.value == "") || isBlank(e.value)) ){
	    var REData = eval(e.mask);
	    if (! REValid(e)) {
	      mask_errors += "\n          - " + e.title + ":" + "\n          form: " + REData.mask;
	      errorNo += 1;		  
	    }
	  }
	  // Now check for fields that are supposed to be numeric.
      if (! isEmpty(e.value) && (e.numeric || (e.min != null && e.min != '') || (e.max != null && e.max != ''))) { 	    
		var v = parseFloat(e.value);
	    if (isNaN(v) || 
		((e.min != null) && (v < e.min)) || 
		((e.max != null) && (v > e.max))) {
	      errors += "- The field " + e.title + " must be a number";
	      if (e.min != null && e.min !='') 
		errors += " that is greater than " + (e.min - 1);
	      if (e.max != null && e.min != null && e.min != '' && e.max != '') 
		errors += " and less than " + (e.max + 1);
	      else if (e.max != null && e.max != '')
		errors += " that is less than " + (e.max + 1);
	      errors += ".\n";
	      errorNo += 1;
	    }
	  }
	}
      }
  }
  // Now, if there were any errors, display the messages, and
  // return false to prevent the form from being submitted. 
  // Otherwise return true (the form will be submitted in this case).
  //  if (!empty_fields && !invalid_dates && !strings_too_long && !mask_errors && !errors) return true;
  if (errorNo == 0) return true;

  // For the grammarian in all of us.
  switch(errorNo) {
  case 1:
    error_s = "error";
    this_these = "this";
    break;
  default:
    error_s = "errors";
    this_these = "these";
  }
	
  msg  = "_____________________________________________________\n\n"
    msg += "A problem was found. Please correct the following " + error_s + "\n";
  msg += "_____________________________________________________\n\n"
		
    if (empty_fields) {
      msg += "* Empty required field error found in:" 
	+ empty_fields + "\n";
    }
  if (invalid_dates) {
    msg += "* Invalid Date or Date Format found error in:"
      + invalid_dates + "\n";
  }
  if (strings_too_long) {
    msg += "* The text you filled in was too long for:"
      + strings_too_long + "\n";
  }
  if (mask_errors) {
    msg += "* Invalid Input Format:"
      + mask_errors + "\n";    
  }
  if (errors) msg += "\n";
  msg += errors;
	
  // Notify the user.
  alert(msg);
  // Return false and stop the form from being submitted.
  return false;
}

/*
Sample javascript include:
<HEAD>
<SCRIPT SRC="/common/code/javascript/form_validation.js"></SCRIPT>
<TITLE>Title</TITLE>
<style>
  @import url(/common/code/stylesheets/tlc_style.css);
</style>
</HEAD>

sample form call to validate:
<FORM name="main_form" method="POST" action="get_opstats_validation.asp"
 	onSubmit="
 		javascript:
 			this.startDate.optional = false;
 			this.startDate.date = true;
			this.startDate.title = 'Start Date';
			this.endDate.optional = false;
			this.endDate.date = true;
			this.endDate.title = 'End Date';
			this.phone.mask = maskFullPhone;
			this.phone.title = 'Phone Number';
			this.pc.mask = maskZipPostal;
			this.pc.title = 'Zip/Postal Code';
			result = verify(this);
			return result;
">  
 	<table>
	<tr>
 	<td>Start Date<INPUT type="text" name="startDate" value="<%=startDate%>"></INPUT></td>
 	<td>End Date<INPUT type="text" name="endDate" value="<%=endDate%>"></INPUT></td>
	</tr>
 	<tr>
	<td>Phone<INPUT type="text" name="phone" value="<%=phone%>"></INPUT></td>
 	<td>Zip/Postal Code<INPUT type="text" name="pc" value="<%=pc%>"></INPUT></td>
 	<td><input type="submit"></input></td>
 	</tr>
	</table>

</FORM>
The whole trick is that if there are any errors and false is returned, the form will not be submitted. 
Keep in mind not to put double quotes in there.
*/
