function Validator(label) {
  this.label = label;
}

/**
 * This function initializes the validation methods 
 * for the page's form. This function should be
 * called in the <code>onload</code> event handler
 * of the <code>&lt;BODY></code> tag:
 *
 * <pre>
 * &lt;body onload="initFormValidation()"></pre>
 *
 * More specifically, this function:
 *
 * <ul>
 *   <li>Sets the <code>getIterator()</code> method
 *       of the form.</li>
 *   <li>Sets the <code>validate()</code> method.</li>
 *   <li>Sets the <code>onsubmit</code> event handler
 *       to validate the form.</li>
 *   <li>Calls <code>initFieldValidation()</code> for
 *       each field in the form.</li>
 *   <li>Calls <code>initFormData()</code> to
 *       initialize the <code>formdata</code> object
 *       and the <code>formitems</code> array.</li>
 *   <li>Calls the <code>format()</code> and
 *       <code>compute()</code> methods of all fields
 *       that have them.</li>
 * </ul>
 *
 * Of these operations, the <code>initFormData()</code>
 * is the most fundamental.
 */
function initFormValidation() {
  for (var i=0; i<document.forms.length; i++) {
    var form = document.forms[i];
    form.getIterator = function() {return new FormIterator(this);}
    form.validate = form_validate;
    form.onsubmit = function() { return this.validate();}
    for (var j=0; j<form.length; j++) {
      initFieldValidation(form.elements[j]);
    }
    initFormData(form);
  }

  for (var i=0; i<document.forms.length; i++) {
    for (var j=0; j<form.length; j++) {
      var field = form.elements[j];
      if (field.format) field.format();
      if (field.compute) field.compute();
    }
  }
}

/**
 * Defines form field manipulation methods and assigns
 * them to the global <code>formdata</code> object.
 * There are multiple sets of methods, one set for each
 * field in the form:
 *
 * <ul>
 *   <li><code>get<i>Field</i>()</code>: Returns the
 *       field's value, parsed if appropriate.</li>
 *   <li><code>set<i>Field</i>(value)</code>: Sets the
 *       field's value, formatting it if appropriate.</li>
 *   <li><code>compute<i>Field</i>()</code>: Calls the
 *       field's <code>compute</code> method for
 *       calculations and mult-field validations.</li>
 *   <li><code>get<i>Field</i>Text()</code>: Returns the
 *       field's text (select lists only).</li>
 * </ul>
 *
 * In each case, <code><i>Field</i></code> is replaced
 * by the field's capitalized name. The last two methods
 * are only present if the field has a matching method
 * (<code>compute</code> and <code>getText</code>). All
 * field manipulation methods can be called through the 
 * global <code>formdata</code> object. For example:
 *
 * <pre>
 * formdata.set<i>Field</i>(x);</pre>
 *
 * This method is roughly equivalent to:
 *
 * <pre>
 * document.<i>form.field</i>.setValue(x);</pre>
 *
 * For text fields, it is roughly equivalent to:
 *
 * <pre>
 * document.<i>form.field</i>.value = x;</pre>
 *
 * If the form includes repeated groups of non-radio
 * button elements, this method also initializes a
 * <code>formitems</code> array. Each item in this
 * array is like a <code>formdata</code> object for
 * the element group. For example, to set the value
 * for the <i>Field</i> with index <code>i</code> in
 * the group:
 *
 * <pre>
 * formitems[i].set<i>Field</i>(value);</pre>
 *
 * @param form   The form whose fields are added to the
 *               <code>formdata</code> object.
 */
function initFormData(form) {
  for (var i=0; i<form.length; i++) {
    var element = form.elements[i];
    var name = element.name;

    if ((name != null) && (name != "")) {
      name = name.charAt(0).toUpperCase() + name.substring(1);
      var target = "document.forms[0].elements[" + i + "]";
      var baseObject = formdata;
      var index = nonRadioIndex(element);

      if (index >= 0) {
        if (!formitems[index]) {
          formitems[index] = new Object();
        }
        baseObject = formitems[index];
      }

      // Define getFIELD() method:
      var getter = "return " + target + ".getValue();";
      if (element.parse) {
        getter = "return " + target + ".parse();";
      }
      baseObject["get" + name] = new Function(getter);

      // Define setFIELD() method:
      var setter = target + ".setValue(value);";
      if (element.format) {
        var setter = target + ".setValue(validations['" + 
                     element.name + "'].format(value));";
      }
      baseObject["set" + name] = new Function("value", setter);

      // Define getFIELDText() method:
      if (element.getText) {
        var getText = "return " + target + ".getText();";
        baseObject["get" + name + "Text"] = new Function(getText);
      }

      // Define computeFIELD() method:
      if (element.compute) {
        var compute = "return " + target + ".compute();";
        baseObject["compute" + name] = new Function(compute);
      }
    }
  }
}



/***********************************************************
*               Section: Formatting Functions              *
*                                                          *
* These functions format field values. Each field is       *
* assigned the formatting function appropriate to its data *
* type as a method. Each method reformats the contents of  *
* the field value in a way appropriate to the data type.   *
* Each method returns the formatted value or an error      *
* string. The error string is always in the form           *
* <code>"ERROR:<i>message</i>"</code>.                     *
***********************************************************/


/**
 * Formats a value to a positive integer string. It is
 * assigned to fields with data type "Integer".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The integer string or an error string if 
 *         the formatting failed.
 */
function formatInteger(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseInteger(value);
  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return "" + value; // Force conversion to string
  }
}

/**
 * Formats a value to a signed integer string. It is
 * assigned to fields with data type "SignedInteger".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The integer string or an error string if 
 *         the formatting failed.
 */
function formatSignedInteger(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseSignedInteger(value);
  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return "" + value; // Force conversion to string
  }
}

/**
 * Formats a value to a positive decimal string. It is
 * assigned to fields with data type "Decimal". Decimal fields
 * may have an optional <code>scale</code> property, indicating
 * the number of places after the decimal point. For example,
 * a decimal field with a precision of 3 will always be
 * formatted <code>#.###</code>. Missing values are filled
 * with 0's. Excess values are rounded.
 *
 * <pre>
 * validations.<i>field</i>.scale = #;</pre>
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The decimal string or an error string if 
 *         the formatting failed.
 */
function formatDecimal(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseDecimal(value);

  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else if (this.scale != null) {
    // Add scaling value for rounding:
    value += parseFloat("1E-" + this.scale)/2;
  }

  value += ""; // Force conversion to string
  // In Netscape, decimals under 1.0 have no leading zero:
  if (value.charAt(0) == ".") {
    value += "0";
  }

  // Scale as appropriate:
  if ((this.scale != null) && (value != "")) {
    var decimalPlace = value.indexOf(".");
    var decimalPart = "";
    if (decimalPlace < 0) {
      decimalPart = ".";
    } else {
      var decimalEnd = decimalPlace + this.scale + 1;
      decimalPart = value.substring(decimalPlace, decimalEnd);
      value = value.substring(0, decimalPlace);
    }
    while (decimalPart.length < (this.scale + 1)) {
      decimalPart += "0";
    }
    value += decimalPart;
  }

  return value;
}

/**
 * Formats a value to a signed decimal string. It is
 * assigned to fields with data type "SignedDecimal".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The decimal string or an error string if 
 *         the formatting failed.
 */
function formatSignedDecimal(value) {
  value = "" + value; // Force conversion to string
  var sign = "";

  // Strip leading spaces:
  while (value.charAt(0) == " ") value = value.substring(1);
  if (value.charAt(0) == "-") sign = "-";

  var result = formatDecimal(value);
  if (result.indexOf("ERROR:") == 0) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return sign + result;
  }
}

/**
 * Formats a value to a positive currency string
 * (<code>$##,###</code>). It is assigned to fields with
 * data type "Currency".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The currency string or an error string if 
 *         the formatting failed.
 */
function formatCurrency(value) {
  value = "" + value; // Force conversion to string
  if (value == "") return "";
  value = parseDecimal(value);

  if (isNaN(value)) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    // Add scaling value for rounding:
    value += 0.005;
  }

  value += ""; // Force conversion to string
  // In Netscape, decimals under 1.0 have no leading zero:
  if (value.charAt(0) == ".") {
    value += "0";
  }

  var decimalPlace = value.indexOf(".");
  var decimalPart = "";
  if (decimalPlace < 0) {
    decimalPart = ".";
  } else {
    var decimalEnd = decimalPlace + 3;
    decimalPart = value.substring(decimalPlace, decimalEnd);
    value = value.substring(0, decimalPlace);
  }
  while (decimalPart.length < 3) {
    decimalPart += "0";
  }

  var newValue = "";
  for (var i=value.length; i>3; i-=3) {
    newValue = "," + value.substring(i-3, i) + newValue;
  }
  newValue = "$" + value.substring(0,i) + newValue + decimalPart;

  return newValue;
}

/**
 * Formats a value to a signed currency string. It is
 * assigned to fields with data type "SignedCurrency".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The currency string or an error string if 
 *         the formatting failed.
 */
function formatSignedCurrency(value) {
  value = "" + value; // Force conversion to string
  value += ""; // Force conversion to string
  var sign = "";

  // Strip leading spaces:
  while (value.charAt(0) == " ") value = value.substring(1);
  if (value.charAt(0) == "-") sign = "-";

  var result = formatCurrency(value);
  if (result.indexOf("ERROR:") == 0) {
    return "ERROR:" + NUMBER_ERROR.replace("{0}", this.label);
  } else {
    return sign + result;
  }
}

/**
 * Formats a value to a phone number string with area code
 * (<code>###-###-####</code>). It is assigned to fields
 * with data type "Phone".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The phone string or an error string if 
 *         the formatting failed.
 */
function formatPhone(value) {
  if (value == "") return "";

  value = stripNonNumeric(value);
  if (value.length > 3) {
    var oldValue = value;
    value = value.substring(0,3) + "-" + value.substring(3,6);
    if (oldValue.length > 6) {
      value += "-" + oldValue.substring(6,10);
    }
  }

  if (value.length != 12) {
    return "ERROR:" + PHONE_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a SSN number string
 * (<code>###-##-####</code>). It is assigned to fields
 * with data type "SSN".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The SSN string or an error string if 
 *         the formatting failed.
 */
function formatSSN(value) {
  if (value == "") return "";

  value = stripNonNumeric(value);
  if (value.length > 3) {
    var oldValue = value;
    value = value.substring(0,3) + "-" + value.substring(3,5);
    if (oldValue.length > 5) {
      value += "-" + oldValue.substring(5,9);
    }
  }

  if (value.length != 11) {
    return "ERROR:" + SSN_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a postal code string (<code>#####</code>
 * or <code>#####-####</code>). It is assigned to fields with
 * data type "Postal".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The postal string or an error string if 
 *         the formatting failed.
 */
function formatPostal(value) {
  if (value == "") return "";

  value = stripNonNumeric(value);
  if (value.length > 5) {
    value = value.substring(0,5) + "-" + value.substring(5,9);
  }

  if ((value.length != 5) && (value.length != 10)) {
    return "ERROR:" + POSTAL_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}

/**
 * Formats a value to a valid email string. It is
 * assigned to fields with data type "Email".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The email string or an error string if 
 *         the formatting failed.
 */
function formatEmail(value) {
  if (value == "") return "";

  value += ""; // Force conversion to string
  var indexOfAtSign = value.indexOf("@");
  var indexOfPeriod = value.lastIndexOf('.');

  if (value.indexOf(" ") >= 0) {
    // Ensure there are no spaces:
    return "ERROR:" + NO_SPACE_ERROR.replace("{0}", this.label);
  } else if (indexOfAtSign < 1) {
    // Ensure there is one "@", not at the beginning:
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else if (value.indexOf("@", indexOfAtSign+1) >= 0) {
    // Ensure there is not a second "@":
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else if ((indexOfPeriod-indexOfAtSign) < 2) {
    // Ensure there is a character between "." and "@":
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else if ((value.length - indexOfPeriod) < 3) {
    // Ensure there are 2 characters after the ".":
    return "ERROR:" + EMAIL_ERROR.replace("{0}", this.label);
  } else {
    return value;
  }
}


function formatFoto(value) {

    if (value == "") return "";

    var indexOfPeriod = value.lastIndexOf('.');

    // while (value.indexOf("\\") != -1)
    //     value = value.slice(file.indexOf("\\") + 1);  
    // ext = value.slice(value.indexOf(".")).toLowerCase();

    var ext = value.substring(indexOfPeriod+1,(value.length));

    if ('jpg' == ext || 'JPG' == ext ) {
        return value; 
    }
    else{
        return "ERROR:" + FOTO_ERROR.replace("{0}", this.label);
    }
} 


/**
 * Formats a value to a valid date string
 * (<code>mm/dd/yyyy</code>). It is assigned to fields
 * with data type "Date".
 *
 * @param value The value to be formatted. When assigned
 *              as a validation method, the field's value
 *              is used as the parameter for this method,
 *              and that value is reset to the result of
 *              this method.
 * @return The date string or an error string if 
 *         the formatting failed.
 */
function formatDate(value) {
  if (value == "") return "";

  var date = parseDate(value);
  if (isNaN(date)) {
    return "ERROR:" + DATE_ERROR.replace("{0}", this.label);
  } else {
    var month = date.getMonth()+1;
    var day = date.getDate();
    var year = date.getFullYear();
    return month + "/" + day + "/" + year;
  }
}



/***********************************************************
*                Section: Parsing Functions                *
*                                                          *
* These methods parse string values into various data      *
* types. They are used primarily to parse the values of    *
* form fields. Each field is assigned the parsing function *
* appropriate to its data type. The parsing methods return *
* a value of <code>NaN</code> if the value passed to the   *
* method does not parse to the desired value.              *
***********************************************************/


/**
 * This method parses a string into an integer value.
 * It is more forgiving than Javascript's standard
 * <code>parseInt()</code> method because it ignores
 * <i>all</i> non-decimal characters in the string.
 * This method returns a positive integer. To allow
 * negative integers, use <code>parseSignedInteger()</code>
 * instead.
 *
 * @param value The value to be parsed.
 * @return The positive integer value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseInteger(value) {
  return parseInt(stripNonDecimal(value));
}

/**
 * As <code>parseInteger()</code>, but allows negative
 * integers as well. This is not the default because for
 * most applications, allowing negative values can lead
 * to unexpected results and possibly security violations.
 *
 * @param value The value to be parsed.
 * @return The signed integer value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseSignedInteger(value) {
  value += ""; // Force conversion to string.
  while (value.charAt(0) == " ") {
    value = value.substring(1);
  }
  var sign = 1;
  if (value.charAt(0) == "-") {
    sign = -1;
  }
  return sign * parseInteger(value);
}

/**
 * This method parses a string into a decimal value.
 * It is more forgiving than Javascript's standard
 * <code>parseFloat()</code> method because it ignores
 * <i>all</i> non-decimal characters in the string.
 * This method returns a positive number. To allow
 * negative numbers, use <code>parseSignedDecimal()</code>
 * instead.
 *
 * @param value The value to be parsed.
 * @return The positive decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseDecimal(value) {
  return parseFloat(stripNonDecimal(value));
}

/**
 * As <code>parseDecimal()</code>, but allows negative
 * values as well. This is not the default because for
 * most applications, allowing negative values can lead
 * to unexpected results and possibly security violations.
 *
 * @param value The value to be parsed.
 * @return The signed decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseSignedDecimal(value) {
  value += ""; // Force conversion to string
  while (value.charAt(0) == " ") {
    value = value.substring(1);
  }
  var sign = 1;
  if (value.charAt(0) == "-") {
    sign = -1;
  }
  return sign * parseDecimal(value);
}

/**
 * This method parses currency into a decimal value.
 *
 * @param value The value to be parsed.
 * @return The positive decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseCurrency(value) {
  return parseDecimal(value);
}

/**
 * As <code>parseCurrency()</code>, but allows negative
 * values as well. This is not the default because for
 * most applications, allowing negative values can lead
 * to unexpected results and possibly security violations.
 *
 * @param value The value to be parsed.
 * @return The signed decimal value of the string, or
 *         <code>NaN</code> if the string contains no
 *         numbers.
 */
function parseSignedCurrency(value) {
   return parseSignedDecimal(value);
}

/**
 * This method parses a string into a date value.
 * It is less forgiving than Javascript's standard
 * <code>Date.parse()</code> method because it does not
 * allow illegal date combinations like "2/31/1999".
 * It also has better Y2K compliance, converting 2 digit
 * years to a date within 70 years in the past of the
 * current date, or 30 years in the future.
 *
 * @param value The value to be parsed. If the value is
 *              already a <code>Date()</code> object,
 *              this method returns that object.
 * @return The date as a Javascript <code>Date</code>,
 *         or <code>NaN</code> if the date is invalid.
 */
function parseDate(value) {
  if ((typeof value == "object") && (value.constructor == Date)) {
    return value;
  }
  value += ""; // Force conversion to string
  var date = NaN;
  var dateArray = value.split("/");
  if (dateArray.length != 3) {
    dateArray = value.split("-");
  }
  if (dateArray.length == 3) {
    var month = parseInt(dateArray[0]);
    var day = parseInt(dateArray[1]);
    var year = parseInt(dateArray[2]);

    // Rolling Y2K compliance:
    if (year < 100) {
      // Add current century to any 2-digit year
      var today = new Date();
      var todayYear = today.getFullYear();
      var century = parseInt(todayYear / 100);
      year = 100 * century + year;

      // If more than 30 years in future, subtract a century
      if ((year - todayYear) > 30) {
        year = year - 100;
      }

      // If more than 70 years in the past, add a century
      if ((todayYear - year) > 70) {
        year = year + 100;
      }
    }

    if (!(isNaN(day) || isNaN(month) || isNaN(year))) {
      date = new Date(year, month-1, day);

      // Verify that day, month and year are the same
      // as their original values:
      var newMonth = date.getMonth()+1;
      var newDay = date.getDate();
      var newYear = date.getFullYear();
      if ( (newYear != year) || (newMonth != month) ||
           (newDay != day) ) {
        date = NaN;
      }
    }
  }
  return date;
}

/**
 * This method strips the value of any non-numeric
 * characters. It is useful for the preliminary
 * formatting of such fields as Social Security
 * Numbers, Postal Codes and Phone Numbers.
 *
 * @param value The value to be stripped.
 * @return The string with all non-numeric characters
 *         stripped.
 */
function stripNonNumeric(value) {
  var result = "";
  var c;              // The current character.

  value += ""; // Force conversion to string

  for (var i=0; i<value.length; i++) {
    c = value.charAt(i);
    if ((c >= "0") && (c <= "9")) {
      result += c;
    }
  }
  return result;
}

/**
 * This method strips the value of any non-decimal
 * characters. In particular, it retains only "." and
 * the values "0" to "9". It is a useful preliminary
 * for parsing of integer and decimal values.
 *
 * @param value The value to be stripped.
 * @return The string with all non-decimal characters
 *         stripped.
 */
function stripNonDecimal(value) {
  var result = "";
  var c;              // The current character.

  value += ""; // Force conversion to string

  for (var i=0; i<value.length; i++) {
    c = value.charAt(i);
    if (((c >= "0") && (c <= "9")) || (c == ".")) {
      result += c;
    }
  }
  return result;
}



/***********************************************************
*              Section: Get/SetValue Methods               *
*                                                          *
* These methods get and set form field values. There is a  *
* different method for each type of form element. Each     *
* field in the form is assigned <code>getValue()</code>    *
* and <code>setValue()</code> methods appropriate to its   *
* type during the form's initialization (see the           *
* <code>initFormValidation()</code> method for additional  *
* details). These methods provide an object-oriented       *
* wrapper for manipulating field values in a consistent    *
* way. In particular, the following methods work as        *
* expected regardless of the field's actual type:          *
*                                                          *
* <pre>  var value = document.<i>form.field</i>.getValue();*
* document.<i>form.field</i>.setValue(value);</pre>        *
***********************************************************/

/**
 * A <code>getValue()</code> method for the various
 * text form elements (text, password, file and hidden
 * fields, as well as text areas).
 *
 * @return The field value.
 */
function text_getValue() {
  return this.value;
}

/**
 * A <code>setValue()</code> method for the various
 * text form elements (text, password, file and hidden
 * fields, as well as text areas).
 *
 * @param value The value to which the field is set.
 */
function text_setValue(value) {
  this.value = value;
}

/**
 * A <code>getValue()</code> method for select lists.
 *
 * @return The value of the currently selected option,
 *         or an empty string if none is selected.
 */
function select_getValue() {
  var result = "";

  var index = this.selectedIndex;
  if (index >= 0) {
    result = this.options[index].value;
  }

  return result;
}

/**
 * A <code>getText()</code> method for select lists.
 *
 * @return The text of the currently selected option,
 *         or an empty string if none is selected.
 */
function select_getText() {
  var result = "";

  var index = this.selectedIndex;
  if (index >= 0) {
    result = this.options[index].text;
  }

  return result;
}

/**
 * A <code>setValue()</code> method for select lists.
 * The option whose value matches the parameter is
 * selected. If there is no matching value, the list
 * is set so that no option is selected.
 *
 * @param value The value to which the field is set.
 */
function select_setValue(value) {
  var index = -1;

  for (var i=0; i<this.options.length; i++) {
    if (this.options[i].value == value) {
      index = i;
      break;
    }
  }

  this.selectedIndex = index;
}

/**
 * A <code>getValue()</code> method for checkboxes.
 *
 * @return The value of the checkbox if selected,
 *         or an empty string if not selected.
 */
function checkbox_getValue() {
  var result = "";

  if (this.checked == true) {
    result = this.value;
  }

  return result;
}

/**
 * A <code>setValue()</code> method for checkboxes.
 * Selects the checkbox if the value parameter is
 * equivalent to <code>true</code>, and deselects
 * the checkbox is the parameter is equivalent to
 * <code>false</code> (e.g. <code>null</code>, 0
 * or an empty string).
 *
 * @param value  Whether to select this checkbox.
 */
function checkbox_setValue(value) {
  if (value) {
    this.checked = true;
  } else {
    this.checked = false;
  }
}

/**
 * A <code>getValue()</code> method for radio button
 * groups.
 *
 * @return The value of the currently selected button,
 *         or an empty string if none is selected.
 */
function group_getValue() {
  var result = "";

  for (var i=0; i<this.length; i++) {
    if (this[i].checked == true) {
      result = this[i].value;
    }
  }

  return result;
}

/**
 * A <code>setValue()</code> method for radio button
 * groups. The button whose value matches the parameter
 * is selected. If there is no matching value, no button
 * is selected.
 *
 * @param value  The value to which the field is set.
 */
function group_setValue(value) {
  for (var i=0; i<this.length; i++) {
    if (this[i].value == value) {
      this[i].checked = true;
    } else {
      this[i].checked = false;
    }
  }
}

/**
 * A <code>getValue()</code> method for individual radio
 * buttons. It calls the <code>getValue()</code> method
 * of its group.
 *
 * @return The value of the currently selected button,
 *         or an empty string if none is selected.
 */
function radio_getValue() {
  return this.form[this.name].getValue();
}

/**
 * A <code>setValue()</code> method for individual radio
 * buttons. It calls the <code>setValue()</code> method
 * of its group.
 *
 * @param value  The value to which the field is set.
 */
function radio_setValue(value) {
  this.form[this.name].setValue(value);
}



/***********************************************************
*               Section: Validation Functions              *
*                                                          *
* These functions are for validating fields. They are      *
* initialized when the page is loaded, and will be         *
* called automatically when the field value changes or     *
* during form submission.                                  *
***********************************************************/


/**
 * Checks whether a given field is (a) required and
 * (b) missing. If so, it returns an error message.
 * A field is marked required as follows:
 *
 * <pre>
 * validations.<i>field</i>.required = true;</pre>
 * 
 * @param field The field being validated.
 * @return An error string if a required field is
 *         missing and an empty string otherwise. The
 *         error string is in a form suitable for
 *         inclusion in a list of missing fields.
 */
function checkRequired(field) {
  var error = ""; // Error message to display to the user.

  if ( (field.required == true) && 
       (field.getValue() == "") ) {
    error += "    * " + field.label;
    var index = nonRadioIndex(field) + 1;
    if (index > 0) {
      error += ITEM_MSG.replace("{0}", index);
    }
    error += "\n";
  }

  return error;
}

/**
 * Checks whether a given field is within the specified
 * range for that field. If no range is specified, the
 * field always passes this test. The range for a field
 * is set as follows:
 *
 * <pre>
 * validations.<i>field</i>.min = #;
 * validations.<i>field</i>.max = #;</pre>
 *
 * Either the max or the min may be omitted if there is
 * no upper and lower bound. The max and min values are
 * included in the allowed range.
 *
 * @param field The field being validated.
 * @return An error string if a field is out of its
 *         allowed range.
 */
function checkRange(field) {
  var value = parseSignedDecimal(field.getValue());
  // If not a number, return no error:
  if (isNaN(value)) return "";

  var error = ""; // Error message to display to the user.
  var label = field.label;
  var max = field.max;
  var min = field.min;

  if ((min != null) && (max != null)) {
    // Both max and min:
    if ((value < min) || (value > max)) {
      error = RANGE1_ERROR.replace("{0}", label);
      error = error.replace("{1}", field.formatValue(min));
      error = error.replace("{2}", field.formatValue(max));
    }
  } else if (min != null) {
    // If there is a min only:
    if ((value < min)) {
      error = RANGE2_ERROR.replace("{0}", label);
      error = error.replace("{1}", field.formatValue(min));
    }
  } else if (max != null) {
    // If there is a max only:
    if ((value > max)) {
      error = RANGE3_ERROR.replace("{0}", label);
      error = error.replace("{1}", field.formatValue(max));
    }
  }

  return error;
}

/**
 * Checks whether the length of a given field is within
 * the specified range for that field. If no range is
 * specified, the field always passes this test. The range
 * for a field's length is set as follows:
 *
 * <pre>
 * validations.<i>field</i>.minlength = #;
 * validations.<i>field</i>.maxlength = #;</pre>
 *
 * Either the max or the min may be omitted if there is
 * no upper and lower bound. The max and min lengths are
 * included in the allowed range. It may seem that the
 * <code>maxlength</code> property duplicates the
 * <code>MAXLENGTH</code> attribute of HTML text fields.
 * It is not completely redundant, because:
 *
 * <ul>
 *   <li>Text areas do not have a <code>MAXLENGTH</code> 
 *       attribute.</li>
 *   <li>Bugs sometimes allow the user to circumvent
 *       the <code>MAXLENGTH</code> attribute.</li>
 * </ul>
 *
 * For example, in most browsers a text field's
 * <code>MAXLENGTH</code> attribute is checked if the
 * user types the entered characters, but not if the
 * user pastes the characters into the field.
 *
 * @param field The field being validated.
 * @return An error string if a field text's length is
 *         out of its allowed range.
 */
function checkLength(field) {
  var value = field.getValue();
  if (value == null) return "";
  var length = value.length;
  var error = ""; // Error message to display to the user.
  var label = field.label;
  var minlength = field.minlength;
  var maxlength = field.maxlength;

  if ((minlength != null) && (maxlength != null)) {
    // Both max and min:
    if ((length < minlength) || (length > maxlength)) {
      if (minlength == maxlength) {
        error = LENGTH0_ERROR.replace("{0}", label);
        error = error.replace("{1}", maxlength);
      } else {
        error = LENGTH1_ERROR.replace("{0}", label);
        error = error.replace("{1}", minlength);
        error = error.replace("{2}", maxlength);
      }
    }
  } else if (minlength != null) {
    // If there is a min only:
    if ((length < minlength)) {
      error = LENGTH2_ERROR.replace("{0}", label);
      error = error.replace("{1}", minlength);
    }
  } else if (maxlength != null) {
    // If there is a max only:
    if ((length > maxlength)) {
      error = LENGTH3_ERROR.replace("{0}", label);
      error = error.replace("{1}", maxlength);
    }
  }

  return error;
}

/**
 * A method for formatting a form field using the 
 * <code>format</code> method in the field's associated
 * <code>Validator</code> object.
 *
 * @return An empty string, or an error string if
 *         formatting failed. Unlike the underlying
 *         <code>format<i>Type</i></code> method, the
 *         error string is not preceded by "ERROR:"
 *         and is suitable for display to the user.
 */
function field_format() {
  var value = validations[this.name].format(this.getValue());
  value += ""; // Force conversion to string
  if (value.indexOf("ERROR:") == 0) {
    return value.substring(6);
  } else {
    this.setValue(value);
    return "";
  }
}

/**
 * A method for parsing a form field using the
 * <code>parse</code> method in the field's associated
 * <code>Validator</code> object.
 *
 * @return The parse value of the field, or <code>NaN</code>
 *         if the field value does not parse correctly.
 *         The "error value" of <code>NaN</code> is true
 *         for all data types, including dates.
 */
function field_parse() {
  return validations[this.name].parse(this.getValue());
}

/**
 * A standard validation method for fields. This method is
 * assigned to all form elements during form initialization.
 * It is called each time the field value changes: the 
 * "<code>onchange</code>" event for text fields, the 
 * "<code>onblur</code>" event select lists, and the 
 * "<code>onclick</code>" event for checkboxes and radio
 * buttons. This function calls the following standard
 * validating functions:
 *
 * <ul>
 *   <li><code>format()</code> to see if the field
 *       contents are formatted properly.</li>
 *   <li><code>checkRange()</code> to see if the
 *       field value is in its allowed range.</li>
 *   <li><code>checkLength()</code> to see if the
 *       field's text is not too long.</li>
 *   <li><code>compute()</code> to perform any necessary
 *       computations and multi-field validations.</li>
 * </ul>
 *
 * If any errors are returned by these methods, the 
 * errors are displayed to the user in an alert and
 * the form's focus returns to this element.
 */
function field_validate() {
  var error = ""; // The error message.

  // Do not validate non-labeled fields.
  if (!this.label) return;

  // Validate:
  if (this.format) error += this.format();
  error += checkRange(this);
  error += checkLength(this);
  if (this.compute) error += this.compute();

  // Show errors:
  if (error != "") {
    alert(error);
    this.focus();
  }
}

/**
 * A standard validation method for forms. This method
 * is assigned to the form during its initialization.
 * It is called when the form is submitted. This function
 * calls iterates through all the form's fields using a
 * <code>FormIterator</code>. For each field, it calls
 * the following standard validating functions:
 *
 * <ul>
 *   <li><code>checkRequired()</code> to see if a
 *       required value is missing.</li>
 *   <li><code>format()</code> to see if the field
 *       contents are formatted properly.</li>
 *   <li><code>checkRange()</code> to see if the
 *       field value is in its allowed range.</li>
 *   <li><code>checkLength()</code> to see if the
 *       field's text is not too long.</li>
 *   <li><code>compute()</code> to perform any necessary
 *       computations and multi-field validations. This
 *       method is only called if there is no form-level
 *       <code>compute()</code> method.</li>
 * </ul>
 *
 * This method also calls the following form method:
 *
 * <ul>
 *   <li><code>compute()</code> to perform any necessary
 *       computations and multi-field validations for
 *       the entire form. If this method is not present,
 *       each field's <code>compute()</code> method is
 *       called instead.</li>
 * </ul>
 *
 * If any errors are returned by these methods, the 
 * errors are displayed to the user in an alert, and the
 * form submission is canceled.
 *
 * @return <code>true</code> if the field is valid,
 *         <code>false</code> otherwise.
 */
function form_validate() {
  // Prevent form resubmission once submitted:
  if (FORM_IS_SUBMITTED) return false;

  var error = "";         // Error string.
  var missingFields = ""; // For required fields.
  var checkFieldComputations = false;
  
  i = this.getIterator();
  
  if (this.compute) {
    error += this.compute();
  } else {
    checkFieldComputations = true;
  }
  
  while (i.hasNext()) {
    var field = i.next();
    
    missingFields += checkRequired(field);
    if (field.format) error += field.format();
    error += checkRange(field);
    error += checkLength(field);
    if (checkFieldComputations && field.compute) {
      error += field.compute();
    }
  }

  // Prepend missing fields, if any:
  if (missingFields != "") {
    error = REQUIRED_ERROR.replace("{0}", missingFields) +
            error;
  }

  if (error == "") { // If there were no errors:
    FORM_IS_SUBMITTED = true;
    return true;     //   Submit the form.
  } else {           // Otherwise:
    alert(error);    //   Alert the user of errors.
    return false;    //   Cancel the form submission.
  }
}



/***********************************************************
*         Section: Form Initialization Functions           *
*                                                          *
* These functions are used to initialize all the other     *
* validation functions in this script.                     *
***********************************************************/

/**
 * A constructor for creating an iterator for walking
 * through form elements with validations. This iterator
 * has two methods:
 *
 * <ul>
 *   <li><code>hasNext()</code>: Returns <code>true</code>
 *       if the iterator has not yet reached the final
 *       form element.</li>
 *   <li><code>next()</code>: Returns the next element
 *       with validations and increments the iterator to
 *       the element following it.</li>
 * </ul>
 *
 * The iterator is used as follows:
 *
 * <pre>
 * i = form.getIterator();
 * while (<b>i.hasNext()</b>) {
 *   var field = <b>i.next()</b>;
 *   <i>// Work with each field</i>
 * }</pre>
 *
 * @param form The form to iterate through.
 */
function FormIterator(form) {
  this.form = form;
  this.count = 0;

  // Set the hasNext() function:
  this.hasNext = function() { 
    return(this.count < this.form.length);
  }

  // Set the next() function
  this.next = function() {
    var field = this.form[this.count];
    do {
      this.count++;
    } while ((this.count<this.form.length) && 
             (!(this.form[this.count].label) || 
              isNotFirstRadioButton(this.form[this.count])));
    return field;
  }

  // Move forward to the first element in the iteration:
  if (!(this.form[this.count].label)) {
    this.next();
  }
}

/**
 * A function that moves the cursor from the given
 * element to the next editable (not readonly) field.
 * It is assigned to the <code>onfocus</code> event
 * handler for read-only fields.
 *
 * @parameter field The field with the current focus.
 */
function goToNextEditableField(field) {
  var index = -1;
  var elements = field.form.elements;
  for (i=indexInForm(field); i<elements.length; i++) {
    if (!elements[i].readonly) {
      index = i;
      break;
    }
  }
  if (index > 0) {
    elements[index].focus();
  } else {
    field.blur();
  }
}

/**
 * This function assigns form validiation properties and
 * methods to a field during form initialization. In
 * particular, it copies the validation properties and
 * methods assigned to the matching <code>Validator</code>
 * in the <code>validations</code> collection to the field.
 *
 * <pre>
 * // Example validations for an amount field:
 * validations.amount = new Validator("Item Amount");
 * validations.amount.datatype = "Integer";
 * validations.amount.compute = computeTotals;
 *
 * ...
 *
 * // The validations copied to the amount field:
 * document.<i>form</i>.amount.label = "Item Amount";
 * document.<i>form</i>.amount.datatype = "Integer";
 * document.<i>form</i>.amount.compute = computeTotals;</pre>
 *
 * In addition, the field will be assigned getter, setter
 * and validate methods appropriate to the field's type.
 * The assignments are:
 *
 * <ul>
 *   <li><code>getValue()</code>: Returns the field's
 *       value.</li>
 *   <li><code>setValue()</code>: Sets the field's
 *       value.</li>
 *   <li><code>validate()</code>: Performs basic
 *       form validations.</li>
 *   <li><code>onchange/onblur/onclick</code> event: Calls
 *       the <code>validate()</code> method.</li>
 * </ul>
 *
 * Finally, the following methods are called to format the
 * field and perform multi-field computations, if these
 * methods are present:
 *
 * <ul>
 *   <li><code>format()</code></li>
 *   <li><code>compute()</code></li>
 * </ul>
 *
 * Note that format functions will be assigned automatically
 * for fields with known datatypes. Any validation errors that
 * occur during initialization are suppressed.
 *
 * @param field  The field to be initialized.
 */
function initFieldValidation(field) {
  var name = field.name;

  // Process validations for field:
  if (validations[name]) {
    var validationsItem = validations[name];

    // Set parse and format functions from data type:
    if (validationsItem.datatype) {
      var formatFunction = "format" + validationsItem.datatype;
      var parseFunction = "parse" + validationsItem.datatype;
      if (window[formatFunction]) {
        validationsItem.format = window[formatFunction];
      }
      if (window[parseFunction]) {
        validationsItem.parse = window[parseFunction];
      }
    }

    // Transfer matching validations properties to field:
    for (property in validationsItem) {
      if (property == "parse") {
        field.parse = field_parse;
        field.parseValue = validationsItem.parse;
      } else if (property == "format") {
        field.format = field_format;
        field.formatValue = validationsItem.format;
      } else {
        field[property] = validationsItem[property];
      }
    }
  }

  // Set field validation and refocusing methods:
  field.validate = field_validate;
  if (field.readonly) {
    field.onfocus = function() { goToNextEditableField(this); }
  }

  // Assign getter, setter and event handlers for the field:
  if (field.type=="checkbox") {
    field.getValue = checkbox_getValue;
    field.setValue = checkbox_setValue;
    field.onclick = function() { this.validate(); }
  } else if (field.type=="radio") {
    field.getValue = radio_getValue;
    field.setValue = radio_setValue;
    field.onclick = function() { this.validate(); }

    // Transfer matching validations properties to radio group:
    if (isFirstRadioButton(field)) {
      var group = field.form[field.name];
      group.getValue = group_getValue;
      group.setValue = group_setValue;
      if (validations[name]) {
        var validationsItem = validations[name];
        for (property in validationsItem) {
          if (property == "parse") {
            group.parse = function() { return this[0].parse(); }
          } else if (property == "format") {
            group.format = function() { return this[0].format(); }
          } else {
            group[property] = validationsItem[property];
          }
        }
      }
    }
  } else if (field.type=="select-one") {
    field.getValue = select_getValue;
    field.setValue = select_setValue;
    field.getText = select_getText;
    field.changed = false;

    // A combination of "onchange" and "onblur" is used
    // to trigger validation for select lists so that
    // validation only occurs after (a) the field value
    // has changed and (b) the user leaves the field.
    field.onchange = function() { this.changed = true; }
    field.onblur = function() { 
      if (this.changed) {
        this.changed = false;
        this.validate();
      }
    }
  } else if ( (field.type=="text") ||
              (field.type=="hidden") ||
              (field.type=="textarea") ||
              (field.type=="file") ||
              (field.type=="password") ) {
    field.getValue = text_getValue;
    field.setValue = text_setValue;
    field.onchange = function() {this.validate();}
  }
}



/***********************************************************
*              Section: Field Index Functions              *
*                                                          *
* These methods return information about the index of a    *
* field element within its form or the group of elements   *
* with the same name.                                      *
***********************************************************/

/**
 * A function for determining an element's index within
 * its form.
 *
 * @param field  The field being checked.
 * @return The index of the element within its form.
 */
function indexInForm(field) {
  var elements = field.form.elements;
  var index = -1;
  for (i=0; i<elements.length; i++) {
    if (elements[i] == field) {
      index = i;
      break;
    }
  }
  return index;
}

/**
 * A function for determining an element's index within
 * its group of elements with the same name.
 *
 * @param field  The field being checked.
 * @return The index of the element within its group, or
 *         -1 if the element is not in a group.
 */
function indexInGroup(field) {
  var index = -1;
  var group = field.form[field.name];
  if ((typeof group == "object") && (group.length)) {
    for (var i=0; i<group.length; i++) {
      if (group[i] == field) {
        index = i;
        break;
      }
    }
  }
  return index;
}

/**
 * A function for determining a non-radio button's index
 * within its group of elements with the same name.
 *
 * @param field  The field being checked.
 * @return The index of a non-radio button within its
 *         group, or -1 if (a) the element is not in
 *         a group or (b) the field is a radio button.
 */
function nonRadioIndex(field) {
  if (field.type != "radio") {
    return indexInGroup(field);
  } else {
    return -1;
  }
}

/**
 * A function for determining a radio button's index
 * within its radio button group.
 *
 * @param field  The field being checked.
 * @return The index of a radio button within its
 *         group, or -1 if the field is not a radio
 *         button.
 */
function radioIndex(field) {
  if (field.type == "radio") {
    return indexInGroup(field);
  } else {
    return -1;
  }
}

/**
 * A function for determining the first radio button
 * in a group.
 *
 * @param field  The field being checked.
 * @return <code>true</code> if this is the first radio
 *         button in a group of radio buttons. This
 *         means it returns <code>false</code> if the
 *         field is not a radio button at all.
 */
function isFirstRadioButton(field) {
  return(radioIndex(field)==0);
}

/**
 * A function for determining if the field is a radio
 * button but not the first button in its group.
 *
 * @param field  The field being checked.
 * @return <code>true</code> if the field is a radio
 *         button, but not the first radio button of
 *         its group. This value is not necessarily
 *         the same as
 *         <code>!isFirstRadioButton(field)</code>.
 */
function isNotFirstRadioButton(field) {
  return(radioIndex(field)>0);
}



//-----------------------------------------------------
// Fundamental object creation:
//-----------------------------------------------------

validations = new Object();
formdata = new Object();
formitems = new Array();

document.onload = function() { initFormValidation(); }
FORM_IS_SUBMITTED = false;



//-----------------------------------------------------
// Error string definitions:
//-----------------------------------------------------

// {0} is the field label:
NUMBER_ERROR = "{0} no es un numero.\n\n";

// {0} is the field label:
PHONE_ERROR = "{0} is not a valid phone number (###-###-#### including area code).\n\n";

// {0} is the field label:
SSN_ERROR = "{0} is not a valid Social Security Number (###-##-####).\n\n";

// {0} is the field label:
POSTAL_ERROR = "{0} is not a valid US Zip Code.\n\n";

// {0} is the field label:
NO_SPACE_ERROR = "{0} no debe tener espacios en blanco.\n\n";
EMAIL_ERROR = "{0} no es una direccion de correo valida (por ejemplo: name@domain.com).\n\n";

// {0} is the field label:
DATE_ERROR = "{0} no es una fecha valida.\n\n";

// {0} is the item index; the leading space is necessary
ITEM_MSG = " (item {0})";

// {0} is the label, {1} the min and {2} the max
RANGE1_ERROR = "El valor {0} debe estar entre {1} y {2}.\n\n";

// {0} is the label, {1} the min
RANGE2_ERROR = "El valor {0} debe ser mayor que {1}.\n\n";

// {0} is the label, {1} the max
RANGE3_ERROR = "El valor {0} debe ser menor que {1}.\n\n";

// {0} is the label, {1} the maxlength:
LENGTH0_ERROR = "{0} must be exactly {1} characters.\n\n";

// {0} is the label, {1} the minlength and {2} the maxlength:
LENGTH1_ERROR = "{0} must be between {1} and {2} characters.\n\n";

// {0} is the label, {1} the minlength:
LENGTH2_ERROR = "{0} cannot be less than {1} characters.\n\n";

// {0} is the label, {1} the maxlength:
LENGTH3_ERROR = "{0} cannot be more than {1} characters.\n\n";

// {0} is the missingFields list:
REQUIRED_ERROR = "Los siguientes datos son necesarios:\n{0}\n";

// {0} is the field label:
FOTO_ERROR = "{0} no es una imagen valida (.jpg).\n\n";