//-----------------------------------------------------------------------
// Module name   : LFORMVAL.js
// Author        : Paul Battersby
// Creation Date : Nov 13/04
// Description   :
//  This module contains routines to support form validation.
//
//  Example Usage:
//
//  NOTE: for this to work, the text that accompanies the form field, MUST be
//        wrapped in a <td> or <span> or <div> or something otherwise it's not
//        possible to alter it's colour (can only set the colour of an elementNode
//        tag, NOT a textNode)
//
//  NOTE: there must be no blank line (perhaps no blank space either) between
//        the <td> of the label and the <span> or <div>
//        AND each label + form field MUST be within their own <tr>
//        This means that you can not have 2 fields in the same row.
//
//        Eventually I'll need to find a way to fix this
//
//  <SCRIPT LANGUAGE="JavaScript" type="text/javascript" src="lformval.js"></script>
//
//  The following form might be kept in an external .json file so that it can
//  be shared by other classes, even a PHP class. As such, some of the
//  parameters are intended to facilitate the sharing of this structure
//  by other classes
//
//  <SCRIPT LANGUAGE="JavaScript" >
//    var gtformmaker_info =
//    {
//      "normalColour" : "#000000",
//      "errorColour"  : "#ff0000",
//
//      "formCfg" :
//      [
//        {
//          "name"           : "name",
//          "label"          : "* Name:",
//          "verifyType"     : "mostChars",
//          "verifyRequired" : true
//        },
//
//        {
//          "name"           : "phone",
//          "label"          : "* Phone",
//          "verifyType"     : "mostChars",
//          "verifyRequired" : false
//        },
//
//        {
//          "name"           : "password",
//          "label"          : "* Password",
//          "verifyType"     : "mostChars",
//          "verifyRequired" : true
//        },
//
//        {
//          "name"     : "website",
//          "verifyType"   : "mostChars",
//          "verifyRequired" : false,
//          "skipLformval" : true
//        },
//
//        {
//          "name"       : "check_me[]",
//          "label"      : "Check Me",
//          "verifyType" : "checkBox",
//          "minMaxChecked" : "1-2",
//        },
//
//        {
//          "name"       : "check_me[]",
//          "verifyType" : "checkBox",
//          "skipLformval" : true
//        },
//
//        {
//          "name"           : "email",
//          "label"          : "Email",
//          "verifyType"     : "mostChars",
//          "verifyRequired" : true
//        }
//      ]
//    }
//  </SCRIPT>
//
//  Explanation of fields
//  =====================
//
//    label          - text that appears beside or above the form element
//                     It is this label that will change colour on error
//    name           - the "name" field in the form for this form element
//    verifyType     - the type of form verification to be performed
//                     See LFORMVAL_errorMsg[] for a list of verification types
//    verifyRequired - (optional) if set to true, this indcates a field that is
//                     required (can not be left blank)
//    skipLformval   - indicates that this field is to be ignored by
//                     lformval.js likely because this field is used
//                     by something else that is also sharing the configuration
//                     record
//
//  Special instructions for checkboxes
//  ===================================
//
//  Checkboxes can be part of a group such that more than one checkbox can
//  be checked and the list of checked values can be managed as an array
//  of values by PHP
//
//  To do this, checkboxes need to be given the same name with different
//  indexes. Example : myCheck[]
//
//  For validation purposes, only the first checkbox in the group needs
//  to have verifyRequired = true. The rest MUST have skipLformval = true
//  because they will all be validated at the same time as the first
//  in the group. Checkbox validation is simply to count the number of
//  checkboxes that have been checked and compare it to the configured range
//
//     minMaxChecked - either the minimum number of checkboxes that must
//                     be selected, OR a range like this "2-4" indicating
//                     in this case, that between 2 and 4 check boxes
//                     must be selected
//
// <head>
//  <SCRIPT LANGUAGE="JavaScript" >
//    window.onload = function() {
//      // initialize the form for use with the validation structure
//      LFORMVAL_initForm("form1",gtformmaker_info.formCfg,"#000000","#ff0000");
//                                         OR
//      LFORMVAL_initForm("form1",gtformmaker_info.formCfg,
//                                gtformmaker_info.normalColour,
//                                gtformmaker_info.errorColour);
//    };
//  </SCRIPT>
// </head>
//
// <body>
//  <FORM ACTION="formHandler.php" METHOD="POST" name="form1" onsubmit="return LFORMVAL_validate(gtformmaker_info.formCfg,this)">
//    <table>
//      <tr>
//        <td class="formLabels">HOME</td>
//        <td></td>
//      </tr>
//      <tr>
//        <td class="formLabels">Name</td>
//        <td><input type="text" size="35" maxlength="40" name="mame"></td>
//      </tr>
//      <tr>
//        <td class="formLabels">Phone</td>
//        <td><input type="text" size="35" maxlength="40" name="phone"></td>
//      </tr>
//      <tr>
//        <td class="formLabels">Website</td>
//        <td><input type="text" size="35" maxlength="40" name="website"></td>
//      </tr>
//
//      <tr>
//        <td colspan='2'>Check at least one of the <span id='counters'>Counters</span> below</td>
//      </tr>
//
//      <tr>
//        <td class="formLabels">One</td>
//        <td><input type="text" size="35" maxlength="40" name="check_me[0]" value="1"></td>
//      </tr>
//      <tr>
//        <td class="formLabels">Two</td>
//        <td><input type="text" size="35" maxlength="40" name="check_me[1]" value="2"></td>
//      </tr>
//      <tr>
//        <td class="formLabels">Email</td>
//        <td><input type="text" size="35" maxlength="40" name="website"></td>
//      </tr>
//    </table>
//    <p align="center"><input type="submit" value="Submit Comments"></p>
//  </form>
//
// </body>
//
// $Log: lformval.js $
// Revision 1.29  2009-05-07 11:44:15-04  Battersby
// - verify type is no longer case sensitive
//
// Revision 1.28  2009-05-01 15:41:10-04  Battersby
// - changed documentation to show that LFORMVAL_initForm() only has 4 parameters
// now instead of 5. formValidateStructName is no longer needed
//
// Revision 1.27  2009-03-30 12:49:14-04  Battersby
// - corrected mixed case of phonestrictareacode
//
// Revision 1.26  2009-03-28 09:47:52-04  Battersby
// - updated documentation for use with gtformmaker defaults
//
// Revision 1.25  2009-02-05 12:22:58-04  Battersby
// - verification type for file now defaults to "allChars"
//
// Revision 1.24  2009-02-05 11:51:53-04  Battersby
// - re-arranged the functions to be more logical
// - can now handle checkbox and radio button verification
//
// Revision 1.23  2008-12-24 12:39:13-04  Battersby
// - now permits office extensions for phone numbers
//
// Revision 1.22  2008-11-25 16:41:52-04  Battersby
// - field label search for error processing is now more forgiving when it
// comes to the presence/absence of a "*" character in the name
// - added LFORMVAL_disable() so that form val can still be called but will be ignored
// (useful when a form is to be hidden but still submitted)
//
// Revision 1.21  2008-10-10 16:15:52-04  Battersby
// - made this compatible with mootools (but this still doesn't use mootools)
//
// Revision 1.20  2008-08-06 14:06:46-04  Battersby
// - updated some documentation
// - remove some unused commented out code
// - corrected the regex for phoneGen
//
// Revision 1.19  2008-06-04 12:50:12-04  Battersby
// - formval_getTextIdNode() was checking child nodes redundantly
//
// Revision 1.18  2008-04-23 15:52:47-04  Battersby
// - formval_getTextIdNode was still not working properly for all cases
//
// Revision 1.17  2008-04-19 09:31:13-04  Battersby
// - form validation now uses the label text from the form field to find the label node
// for error indication (turning the text red)
//
// Revision 1.16  2008-04-16 13:45:45-04  Battersby
// - updated to share .json file with gtde and lformmaker.php
//
// Revision 1.15  2008-02-04 21:13:07-04  Battersby
// - added quote in this line:   password    : ["lettersOnly",  "skip"],
//
// Revision 1.14  2008-01-14 14:56:38-04  Battersby
// - added a customBlank error type that can be customized
//
// Revision 1.13  2007-12-07 12:09:31-04  Battersby
// - now trim extra spaces from form field labels during error reporting (needed by FireFox)
//
// Revision 1.12  2007-11-30 18:03:15-04  Battersby
// - forgot an alert() left over during debug
//
// Revision 1.11  2007-11-30 17:00:15-04  Battersby
// - simplified and improved search for form field text in formval_getTextIdNode()
//
// Revision 1.10  2007-11-12 17:07:20-04  Battersby
// - changed error text for "numbers"
//
// Revision 1.9  2007-06-25 09:05:15-04  Battersby
// - it is now possible to indicate form fields that are to be skipped in the validate
// struct that will later be enabled at run time
//
// Revision 1.8  2007-04-24 12:29:46-04  Battersby
// - removal of span was not working properly. Moved the code from formatError to validateOne
//
// Revision 1.7  2007-04-24 11:53:56-04  Battersby
// fixed small documentation error, added a comma to the m_validateStruct example
//
// Revision 1.6  2007-04-19 16:17:46-04  Battersby
// - added ability to prefix error message within the validateStruct
// - added LFORMVAL_customError()
// - added LFORMVAL_setErrorText()
// - form text can now be surrounded wy a <span> which will be excluded from
// error messages
// - * in form text (to indicate required fields) is excluded from error messages
//
// Revision 1.5  2007-02-02 11:44:41-04  Battersby
// - needed to add code to handle form validation both when there is a space between
// the input node and the previous <span> node and when there is not
//
// Revision 1.4  2007-02-01 17:25:00-04  Battersby
// - form validation can now handle case where text is beside form element instead of
// in a <td> of it's own
// - corrected "phone" validation to be a generic phone number with or without
// the area code
//
// Revision 1.3  2006-12-09 18:31:40-04  Battersby
// form validation can now handle the highliting of text that is either beside (as before, which means within the same TR tag) or above (which means within a different TR tag) a text box or text area
//
// Revision 1.2  2006-11-02 11:09:28-04  Battersby
// Modified the documentation
//
//------------------------------------------------------------------------*/

/* determine the type of browser */
var LFORMVAL_isDOM = (document.getElementById ? true : false);
var LFORMVAL_isIE4 = ((document.all && !LFORMVAL_isDOM) ? true : false);
var LFORMVAL_isNS4 = (document.layers ? true : false);

var LFORMVAL_errorMsg = {
  en : {
    allChars       : "",
    lettersOnly    : "may only contain letters",
    lettersNumbers : "may only contain letters & numbers",
    numbers        : "may only contain numbers",
    postalCode     : "is not a valid postal code",
    postalZip      : "is not a valid postal/zip code",
    postalGen      : "is not a valid postal/zip code",
    phone          : "is not a valid phone number",
    phoneStrictAreaCode : "must be in the form 111-222-3333",
    phoneDigits    : "must contain between 10 and 11 digits (including area code)",
    internationalPhone : "contains illegal characters",
    internationalPhoneDigits : "please include an area code/country code",
    email          : "is not a valid email address",
    url            : "is not a valid web page address",
    currency       : "may only contain numbers",
    currencyGt0    : "must not be 0",
    mastercard     : "is not a valid credit card number",
    numDigits      : "does not have the correct number of digits",
    mastercardNot  : "is not a valid Mastercard number",
    visacard       : "is not a valid credit card number",
    visacardNot    : "is not a valid Visacard number",
    noBlank        : "can not be left blank",
    customBlank    : "",
    mostChars      : "contains illegal character(s)",
    dateyyyymmdd   : "must be in yyyy-mm-dd format",
    dateText       : "must be in this format: Jan 17 2004",
    passwordLength : "contains too few characters",
    errorList      : "The following errors were found, please correct them before proceeding:"
  },

  fr : {
    allChars       : "",
    lettersOnly    : "may only contain letters",
    lettersNumbers : "may only contain letters & numbers",
    numbers        : "may only contain numbers",
    postalCode     : "n'est pas un code postal valide",
    postalZip      : "is not a valid postal/zip code",
    postalGen      : "is not a valid postal/zip code",
    phone          : "n'est pas un numéro de téléphone valide",
    phoneStrictAreaCode : "must be in the form 111-222-3333",
    internationalPhone : "contains illegal characters",
    internationalPhoneDigits : "please include an area code/country code",
    phoneDigits    : "doit contenir entre 10 et 11 chiffres",
    email          : "n'est pas une adresse courriel valide",
    url            : "is not a valid web page address",
    currency       : "ne doit contenir que des chiffres, des espaces ou un trait",
    currencyGt0    : "ne doit pas être un zéro",
    mastercard     : "n'est pas un numéro de carte de crédit valide",
    numDigits      : "ne contient pas le nombre exact de chiffres",
    mastercardNot  : "le numéro de Mastercard n'est pas valide",
    visacard       : "n'est pas un numéro de carte de crédit valide",
    visacardNot    : "le numéro de Visa n'est pas valide",
    noBlank        : "ne pas laissez en blanc",
    customBlank    : "",

    mostChars      : "contains illegal character(s)",
    dateyyyymmdd   : "must be in yyyy-mm-dd format",
    dateText       : "must be in this format: Jan 17 2004",
    passwordLength : "contains too few characters",

    errorList      : "Les erreurs suivantes ont été trouvées, s'il vous plaît, les corriger avant de continuer:"
  }
};

var LFORMVAL_lang = "en"; /* language to use is english by default */

/* this reproduces the Javascript constant names */
var NodeType = {
  ELEMENT_NODE: 1,
  ATTRIBUTE_NODE: 2,
  TEXT_NODE: 3,
  COMMENT_NODE: 8,
  DOCUMENT_NODE: 9,
  DOCUMENT_FRAGMENT_NODE: 11
};

var lformval_customErrors = new Array();

var lformval_errorColour;
var lformval_normalColour;
var lformval_initFormComplete = false; // used to ensure LFORMVAL_initForm was called

// true indicates that the form validation is to be skipped entirely
var lformval_disable = false;

//************************************************************************
// Name   : LFORMVAL_setLang
//  This allows the caller to set the language (english or french)
//  that is to be used for error messages
//
//  "lang" - one of {"en","fr"} indicates the desired language for error
//           reporting. english or french
//
// Post :  the language for error reporting has been set
// Returns :  (nothing)
//*************************************************************************/
function LFORMVAL_setLang(lang)
{
  LFORMVAL_lang = lang;
} /* end of LFORMVAL_setLang */

//************************************************************************
// Name   : LFORMVAL_noEnter
//  This prevents the Enter key from being used to submit a form
//
// Pre :
//  This needs to be placed in every text element of a form like this:
//    onkeypress="return LFORMVAL_noEnter()"
//
// Returns : (boolean)  false - if the enter key was pressed
//                      true  - otherwise
//*************************************************************************/
function LFORMVAL_noEnter()
{
  return !(window.event && window.event.keyCode == 13);
} /* end of LFORMVAL_noEnter */

//************************************************************************
// Name   : LFORMVAL_getRefById
//  In a browser independant way, this returns a reference to the HTML
//  element that contains the given "id" value
//
//  "id" = the value of the id field in an HTML element
//
// Pre    : LFORMVAL_isDOM, LFORMVAL_isIE4, LFORMVAL_isNS4 have all been defined
// Post   : (nothing)
// Returns: reference to an HTML element
//*************************************************************************/
function LFORMVAL_getRefById(id)
{
  if (LFORMVAL_isDOM) { return document.getElementById(id); }
  if (LFORMVAL_isIE4) { return document.all[id]; }
  if (LFORMVAL_isNS4) { return document.layers[id]; }
} /* end of LFORMVAL_getRefById */

//************************************************************************
// Name   : LFORMVAL_disable
//  (boolean) disable - true  = validation will be skipped
//                      false = validation will NOT be skipped
// Returns :
//*************************************************************************
function LFORMVAL_disable(disable)
{
  lformval_disable = disable;
} // end of LFORMVAL_disable

//************************************************************************
// Name   : LFORMVAL_splitPhone
//  This takes a phone number in this format "111-222-3333", breaks it into
//  3 pieces and returns those pieces in an array
//
//  phoneNumber - a phone number in this format "111-222-3333"
//
// Returns :
//  a 3 element array containing the phone number
//   [0] = 111
//   [1] = 222
//   [2] = 3333
//*************************************************************************/
function LFORMVAL_splitPhone(phoneNumber)
{
  return phoneNumber.split(/[- ]/);
} /* end of LFORMVAL_splitPhone */

//************************************************************************
// Name   : LFORMVAL_textLimit
// Author : Third Party
//  This limits the amount of text that can be entered into a text area
//
//  "field" - the text area who's max number of characters is to be limited
//  "maxlen" - max number of characters allowed in the text area
//
//    example:   onkeyup="FORMAL_limitChars(this,20)";
//
// Post   :
//    if a cut and paste operation has been performed that exceeds the
//    character limit, a message informing the user that their text has
//    been truncated has been displayed
//
// Returns : (nothing)
//*************************************************************************/
function LFORMVAL_textLimit(field, maxlen)
{
  if (field.value.length > maxlen + 1) {
    alert('your input has been truncated!');
  }
  if (field.value.length > maxlen) {
    field.value = field.value.substring(0, maxlen);
  }

} /* end of LFORMVAL_textLimit */

//************************************************************************
// Name   : LFORMVAL_getCheckedRadioVal
//  This determines the value of the checked radio button
//
//  "radioGroup" - the name of a group of radio buttons
//
// Returns : (string) the value of the checked radio button
//*************************************************************************/
function LFORMVAL_getCheckedRadioVal(radioGroup)
{
  var selectedRadioValue = "";
  var i;

  /* loop through all the radio buttons */
  for ( i = 0; i < radioGroup.length; i++ )
  {
    /* if we've found the one that is currently checked */
    if ( radioGroup[i].checked )
    {
      selectedRadioValue = radioGroup[i].value;
      break;
    }
  }
  return selectedRadioValue;
} /* end of LFORMVAL_getCheckedRadioVal */

//************************************************************************
// Name   : LFORMVAL_getEntryText
//  returns the currently selected text for the given form entry
//
//  "entryId" - id of the form entry whose currently selected text is to be returned
//                example:  document.forms.myform.myentry
//
// Returns : (string) the currently selected text for the given form entry
//*************************************************************************/
function LFORMVAL_getEntryText(entryId)
{
  return entryId.options[entryId.selectedIndex].text;
} /* end of LFORMVAL_getEntryText */

//************************************************************************
// Name   : LFORMVAL_getRadioText
//  This returns the text of the selected radio button to the caller
//
//  radioId - the id of the radio group
//              ex document.forms.myform.myRadioButtons
//
// Returns : (string) Text of the clicked radio button or "" if no radio
//                    button has been selected
//*************************************************************************/
function LFORMVAL_getRadioText(radioId)
{
  /* loop through the radio buttons */
  for (i=0; i<radioId.length; i++) {

    /* if we found the one that is checked */
    if (radioId[i].checked) {

      /* return the value to the caller */
      return radioId[i].value;
    } /* end for */
  } /* end for */

  /* no radio button is checked */
  return "";

} /* end of LFORMVAL_getRadioText */

//************************************************************************
// Name   : LFORMVAL_getCheckedText
//  This returns the text of the selected check boxes to the caller
//
// Pre    : checkboxId - the id of the checkbox group
//                        ex document.myform.myCheckBoxes
//
// Returns : (array) an array containing the text of each selected checkbox
//                   The length of the array will be 0 if nothing is checked
//*************************************************************************/
function LFORMVAL_getCheckedText(checkboxId)
{
  var checkboxText = new Array();
  var idIndex;
  var textIndex = 0;

  /* loop through all the check boxes */
  for ( idIndex=0; idIndex < checkboxId.length; idIndex++ ) {

    /* if this one is checked, add it to the array */
    if (checkboxId[idIndex].checked) {
      checkboxText[textIndex++] = checkboxId[idIndex].value;
    } /* endif */
  } /* end for */

  return checkboxText;

} /* end of LFORMVAL_getCheckedText */

//************************************************************************
// Name   : LFORMVAL_replaceSingleQuote
//  This takes all "'" characters in the given string and replaces them
//  with "`"
//
//  "oldString" - any string
//
// Returns : (string) the given string with all single quotes replaced with "`"
//*************************************************************************/
function LFORMVAL_replaceSingleQuote(oldString)
{
  newString = oldString.replace(/\'/g,"`");
  return newString;
} /* end of LFORMVAL_replaceSingleQuote */

//************************************************************************
// Name   : LFORMVAL_getCheckedIndex
//  This determines the index of the checked radio button
//
//  "radioGroup" - the name of a group of radio buttons
//
// Returns :
//  the index of the checked radio button
//*************************************************************************/
function LFORMVAL_getCheckedIndex(radioGroup)
{
  var selectedRadioIndex = 0;
  var i;

  /* loop through all the radio buttons */
  for ( i = 0; i < radioGroup.length; i++ )
  {
    /* if we've found the one that is currently checked */
    if ( radioGroup[i].checked )
    {
      selectedRadioIndex = i;
      break;
    } /* endif */
  } /* end for */

  return selectedRadioIndex;
} /* end of LFORMVAL_getCheckedVal */

//************************************************************************
// Name   : LFORMVAL_countDigits
//  This counts the number of digits in the given string excluding characters,
//  spaces etc.
//
//  "number" - a number whose digits are to be counted
//
// Returns : (int) number of digits in "number"
//*************************************************************************/
function LFORMVAL_countDigits(number) {
  var matchesList = new Array();

  /* count the digits */
  matchesList = number.match(/\d/g);

  /* if there are no digits at all */
  if ( matchesList === null ) {
    return 0;

  /* return the length of the array returned by the match() method */
  /* which corresponds to the number of digits in the number */
  } else {
    return matchesList.length;
  } /* endif */

}  /* end of LFORMVAL_countDigits */

//************************************************************************
// Name   : LFORMVAL_validateLuhnFormula
// Author : copyright 12th May 2003, by Stephen Chapman, Felgall Pty Ltd
//          (reformatted/repackaged by Paul Battersby)
//
//  This performs the Luhn Formula on a credit card number to determine
//  it if is a valid Visa or Mastercard number
//
//  Based on ANSI X4.13, the LUHN formula (also known as the modulus 10 -- or
//  mod 10 -- algorithm ) is used to generate and/or validate and verify the
//  accuracy of credit-card numbers.
//
//  Most credit cards contain a check digit, which is the digit at the end of the
//  credit card number. The first part of the credit-card number identifies the
//  type of credit card (Visa, MasterCard, American Express, etc.), and the middle
//  digits identify the bank and customer.
//
//  To generate the check digit, the LUHN formula is applied to the number. To
//  validate the credit-card number, the check digit is figured into the formula.
//
//  Here's how the algorithm works for verifying credit cards; the math is quite
//  simple:
//
//  1) Starting with the second to last digit and moving left, double the value of
//  all the alternating digits.
//
//  2) Starting from the left, take all the unaffected digits and add them to the
//  results of all the individual digits from step 1. If the results from any of
//  the numbers from step 1 are double digits, make sure to add the two numbers
//  first (i.e. 18 would yield 1+8). Basically, your equation will look like a
//  regular addition problem that adds every single digit.
//
//  3) The total from step 2 must end in zero for the credit-card number to be
//  valid.
//
//  The LUHN formula was created in the late 1960s by a group of mathematicians.
//  Shortly thereafter, credit card companies adopted it. Because the algorithm is
//  in the public domain, it can be used by anyone.
//
//  The LUHN formula is also used to check Canadian Social Insurance Number (SIN)
//  validity. In fact, the LUHN formula is widely used to generate the check
//  digits of many different primary account numbers. Almost all institutions that
//  create and require unique account or identification numbers use the Mod 10
//  algorithm.
//
//  "s" - the credit card number that is to be checked
//
// Returns : (boolean) true  - credit card number passes the mathematical check
//                     false - credit card fails the mathematical check
//*************************************************************************/
function LFORMVAL_validateLuhnFormula(s) {

  var v = "0123456789";
  var w = "";
  for (var i=0; i < s.length; i++) {
    x = s.charAt(i);
    if (v.indexOf(x,0) != -1)
    w += x;
  }
  var j = w.length / 2;
  if (j < 6.5 || j > 8 || j == 7) return false;
  var k = Math.floor(j);
  var m = Math.ceil(j) - k;
  var c = 0;
  for (var i=0; i<k; i++) {
    a = w.charAt(i*2+m) * 2;
    c += a > 9 ? Math.floor(a/10 + a%10) : a;
  }
  for (var i=0; i<k+m; i++) c += w.charAt(i*2+1-m) * 1;
  return (c%10 == 0);
} /* end of LFORMVAL_validateLuhnFormula */

//************************************************************************
// Name   : LFORMVAL_handleMutEx
//  If the given form element contains data, this sets all other form elements
//  in the list to disabled
//
//  If the given form element is empty, it sets all other form elements in
//  the list to enabled
//
//  "muExList" - the list of form elements that are to be considered mutually
//               exclusive.
//  "id" - the id of the form element that was just changed either by text being
//         added or completely deleted
//
// Post   :
//  if the given form element is now empty, all the form elements in the
//  list have been enabled.
//
//  if the given form element is not empty, all the form elements in the list
//  (except the given form element) are now disabled
//
// Returns: (nothing)
//*************************************************************************/
function LFORMVAL_handleMutEx(mutExList,id)
{
  /* if this form element became blank */
  if (LFORMVAL_getRefById(id).value == "") {
    /* enable the other forms from the list */
    for ( i = 0; i < mutExList.length; i++ ) {
      LFORMVAL_getRefById(mutExList[i]).disabled = false;
    }; /* end for */

  /* text was entered */
  } else {
    /* disable the other forms elements from the list */
    for ( i = 0; i < mutExList.length; i++ ) {

      /* disable only if not the selected element */
      if ( mutExList[i] != id ) {
        LFORMVAL_getRefById(mutExList[i]).disabled = true;
      }
    }; /* end for */

  }; /* endif */
} /* end of LFORMVAL_handleMutEx */

//************************************************************************
// Name   : LFORMVAL_setErrorText
//  This allows the caller to alter any of the error messages in
//  LFORMVAL_errorMsg. Useful if a different language needs to be used
//  besides the two provided
//
//  lang       - one of {en,fr} to indicate which group of language strings is being
//               modified
//  errorClass - the specific error message that is being modified
//  newMsg     - the new message string
//
// Post : the LFORMVAL_errorMsg array has been updated with the given string
// Returns : (nothing)
//*************************************************************************/
function LFORMVAL_setErrorText(lang,errorClass,newMsg)
{
  LFORMVAL_errorMsg[lang][errorClass] = newMsg;
} /* end of LFORMVAL_setErrorText */

//************************************************************************
// Name   : LFORMVAL_customError
//  This allows the caller to add a custom error to the list of errors
//  that will be displayed during form validation
//
//  "validateStruct" - see formval_validateOne()
//  "fieldObj"       - see formval_validateOne()
//  "errorString"    - the custom error
//
// Post :
//  the custom error has been added to the list of errors to be displayed
//  and the text for the form element has had its colour changed to the error colour
//  specified in the validateStruct
//
// Returns : (nothing)
//*************************************************************************/
function LFORMVAL_customError(validateStruct,fieldObj,errorString)
{
    var errorColor = lformval_errorColour
    var normalColor = lformval_normalColour

    // find where this field is within the validate struct
    for ( var structIndex = 0; structIndex < validateStruct.length; structIndex++ ) {

      // if the names match, we found what we're looking for
      if (validateStruct[structIndex]["name"] == fieldObj.attributes.name) {
        break;
      } // endif
    } // end for

    idToChange = formval_getTextIdNode(fieldObj,validateStruct[structIndex].label);

    /* get the actual text belonging to this element */
    /* for error reporting */

    /* if this is already a text node, get it's data */
    if ( idToChange.nodeType == NodeType.TEXT_NODE ) {
      text = idToChange.data;
    /* otherwise get the inner html */
    } else {
      text = idToChange.innerHTML;
    } /* endif */

    /* if there was no error */
    if ( errorString == "" ) {
      /* make sure the color of this is normal to indicate no error */
      idToChange.style.color = normalColor;

    /* there was an error */
    } else {
      /* change the color of given text to indicate an error */
      idToChange.style.color = errorColor;

      /* often a "*" precedes the text to indicate a required field */
      /* we don't want that in our error message                    */
      text = text.replace(/^\*/,"");

      /* incase there is a <span> or something surrounding the text, remove it */
      text = text.replace(/<[^>]*>/gi,"");

      /* format the error string and add it to the custom error list  */
      errorString = "\"" + text + "\"- " + errorString + "\n";
      lformval_customErrors = lformval_customErrors.concat(errorString);

    } /* endif */

} /* end of LFORMVAL_customError */

//************************************************************************
// Name   : LFORMVAL_formatError
//  This formats and error message and returns it to the caller
//
//  "fieldText" - the text representing the field about which an error
//                is being reported. In the example that follows, this would
//                be "(first name)"
//
//  "errorId" - the id of the error string that is to be reported. This is
//              used as an index into LFORMVAL_errorMsg[]
//
// Pre    : "LFORMVAL_lang" has been defined
//
// Returns: (string) An error message
//           ex: "- (first name): may only contain letters"
//*************************************************************************/
function LFORMVAL_formatError(fieldText,errorId)
{
  var errorString = "";

  /* if this is not the error list message */
  if ( errorId != "errorList" ) {
    errorString = '"' + fieldText + '"- ' + LFORMVAL_errorMsg[LFORMVAL_lang][errorId] + "\n";

  /* this is the error list message */
  } else {
    errorString = LFORMVAL_errorMsg[LFORMVAL_lang][errorId] + "\n\n";
  } /* endif */

  return errorString;

} /* end of LFORMVAL_formatError */

//************************************************************************
// Name   : formval_getFormNode
//  Given a form field node/obj this will find it's parent form node/obj
//
//  (obj) node - a form input node/object
//
// Returns : (obj) - the form node/object parent of the given input node
//*************************************************************************
function formval_getFormNode(node) {

  // if the node is already a form node, we are done
  if (node.nodeName.toUpperCase() == "FORM") {
    return node;
  } // endif

  // find the form tag
  node = node.parentNode;
  while (node.nodeName.toUpperCase() != "FORM") {
    node = node.parentNode;
  }
  return node;

}  /* end of formval_getFormNode */

//************************************************************************
// Name   : formval_getTextIdNode
//  This starts from the id of a form input tag object (form.myForm.inputId)
//  and returns the id of the text that precedes that form element via
//  recursively calling itself until successful
//
//  node - the id of a form input tag object (ex: form.myForm.inputId)
//  labelText - the text label that belongs to the form field
//              (the node containing this text is the one we're trying to find)
//  foundForm - indicates that we've already found the form tag
//
// Returns : id of the <td> tag containing the text that precedes the given form element
//*************************************************************************/
function formval_getTextIdNode(node,labelText,foundForm)
{
  var children;
  var i;
  var nodeText;
  var sibling;

/*
  for most form items, we have this structure (CASE 1)
  <tr>
    <td>      <-- parent.previousSibling
      some text
    </td>
    <td>      <-- parent
      <input> <-- inputNode
    </td>
  <tr>

  for a text area, we might have this instead (CASE 2)

  <tr>
    <td>      <-- parent.parentNode.previousSibling.lastChild
      some text
    </td>
  </tr>
  <tr>        <-- parent.parentNode
    <td>      <-- parent
      <input> <-- inputNode

  for some forms, we may also have this (CASE 3)
  <tr>
    <td>      <-- parent
      some text <input> <-- inputNode
    </td> /|\
  <tr>     |
           `-- parent.firstChild
*/

  // if we haven't found the form tag yet
  if (!foundForm) {
    node = formval_getFormNode(node);
    foundForm = true;
  } // endif

  // for debugging via the firefox console
/*
  if ( node ) {
    switch (node.nodeName) {
      case "#text" :
        console.log(node.nodeName + ": " + node.nodeValue);
        break;

      case "INPUT" :
        console.log(node.nodeName + ": " + node.id);
        break;
      default :
        console.log(node.nodeName);
    } // end switch

  } else {
    console.log("null nodename");
  } // endif
*/
  // if the node has children
  if ( node.hasChildNodes()) {

    // loop through all the children
    children = node.childNodes;
    for ( i = 0; i < children.length ; i++ ) {
      node = formval_getTextIdNode(children[i],labelText,foundForm);

      // if we found our target node
      if ( node ) {
        return node;
      } // endif
    } // end for

  // no children, so it might be the target node
  } else {

    if ( node ) {
      // if this is the text node that we want
      if ((node.nodeName == "#text") && (node.nodeValue.indexOf(labelText) != -1) ) {
        return node.parentNode;

      // this is not a text node, or not the one we want
      } else {
        return null;
      } // endif
    } // endif
  } // endif

  // desired node not yet found
  return null;

} // end of formval_getTextIdNode

//************************************************************************
// Name   : LFORMVAL_countCheckedBoxes
//  This counts the number of checkboxes that belong to the same group
//  and are currently checked
//
//  formObj      - the form object containing the form being validated
//  validateDesc - see formval_validateOne()
//
// Returns : (int) number of checked checkboxes from this group
//*************************************************************************
function LFORMVAL_countCheckedBoxes(formObj,validateDesc)
{
  var checkGroup = validateDesc.name;
  var count = 0;
  var fieldName;
  var element;

  // remove the brackets and the bracket contents
  checkGroup = checkGroup.replace(/\[.*\]/,"");

  // put one bracket back
  checkGroup = checkGroup + "[";

  // loop through the form looking for members of the checkGroup
  for ( i = 0; i < formObj.elements.length; i++ ) {
    element = formObj.elements[i];
    // if this the right checkbox, count it
    if ( (element.type == "checkbox") &&
         (element.name == validateDesc.name) &&
         (element.checked)) {
      count++;
    } // endif
  } // endfor
  return count;
} // end of LFORMVAL_countCheckedBoxes

//************************************************************************
// Name   : lformval_stripLabel
//  This takes a label from a form description and does the following for use
//  in error messages:
//    - trim leading and trailing spaces
//    - remove any html tags from the label
//    - remove leading or trailing "*"
//
//  (string) label - label string to be stripped
//
// Returns : (string) stripped label
//*************************************************************************
function lformval_stripLabel(label)
{
  // trim leading/trailing spaces
  label = label.replace(/^\s+|\s+$/g,"");

  /* incase there is a <span> or something surrounding the text, remove it */
  label = label.replace(/<[^>]*>/gi,"");

  /* often a "*" precedes or follows the label to indicate a required field */
  /* we don't want that in our error message                                */
  label = label.replace(/^\*/,"");
  label = label.replace(/\*$/,"");

  return label;
} // end of lformval_stripLabel

//************************************************************************
// Name   : formval_validateCheckboxes
//  This ensures that the correct number of checkboxes have been selected
//
//  (obj) formObj      - see LFORMVAL_validate()
//  (obj) validateDesc - see formval_validateOne()
//
// Returns :
//  an error message if the number of checked checkboxes is not correct
//   OR
//  an empty string
//*************************************************************************/
function formval_validateCheckboxes(formObj,validateDesc)
{
  var error = "";

  // checkbox values are give as a comma separated list, count them
  var count = LFORMVAL_countCheckedBoxes(formObj,validateDesc);

  // if a minMax was specified
  var minMax = validateDesc.minMaxChecked;
  if (minMax) {
    // minMax looks like this "1" or "1-2"
    minMax = minMax.split("-");
    label = lformval_stripLabel(validateDesc.label);

    // if only 1 limit was given, user must select exactly the given
    // number of items
    if (minMax.length == 1) {
      // if the wrong number of checkboxes have been selected
      if (count != minMax[0]) {
        error = '"' + label + '"' + "- you must select " + minMax[0] + " check box";

        // make the error message plural
        if (minMax[0] > 1) {
          error += "es"
        } // endif
        error += "\n";
      } // endif

    // user has a range of choices
    } else {

      // too many or too few selected
      if (count < minMax[0] || count > minMax[1] ) {
        error = '"' + label + '"' + "- you must select between " + minMax[0] + " & " + minMax[1] + " check boxes\n";
      } // endif
    } // endif

  } // endif

  return(error);

} // end of formval_validateCheckboxes

//************************************************************************
// Name   : formval_validateRadio
//  This ensures that the correct number of checkboxes have been selected
//
//  (obj) formObj      - see LFORMVAL_validate()
//  (obj) validateDesc - see formval_validateOne()
//
// Returns :
//  an error message if the number of checked checkboxes is not correct
//   OR
//  an empty string
//*************************************************************************/
function formval_validateRadio(formObj,validateDesc)
{
  var error = "";
  var i;
  var element;

  // if a radio button MUST be selected
  if (validateDesc.verifyRequired) {
    // loop through the form looking for members of the checkGroup
    for ( i = 0; i < formObj.elements.length; i++ ) {
      element = formObj.elements[i];
      // if this the right checkbox, count it
      if ( (element.type == "radio") &&
          (element.name == validateDesc.name) &&
          (element.checked)) {
        return ""; // indicate no error
      } // endif
    } // endfor

    // indicate an error
    label = lformval_stripLabel(validateDesc.label);
    error = '"' + label + '"' + "- you must make a selection\n";
  } // endif

  return(error);

} // end of formval_validateRadio

//************************************************************************
// Name   : formval_validateOne
//    This determines if a single form element contains valid information
//    If the indicated form element is not correct, the color of the label
//    accompanying that form element is set to an error color and an error
//    message is returned to the caller. Otherwise, the form element is set
//    to a "normal" color and an empty string is returned to the caller
//
//  "validateStruct" - see header for format
//    * See the file formval-test.htm for an example
//
//  "fieldObj" - the form element to be validated typically the object
//               referred to by "this"
//                          OR
//               the form being validated in the case of a checkbox
//
//  "structIndex" - index into the validateStruct for the field being validated
//
// Post   :
//  the color of the label associated with the form element has either been
//  set to lformval_errorColour color or lformval_normalColour
//
// Returns : (string) error string if an error occurred, an empty string otherwise
//*************************************************************************/
function formval_validateOne(fieldObj,validateDesc) {
  var error = "";
  var idToChange;
  var errorColor = lformval_errorColour;
  var normalColor = lformval_normalColour;
  var verifyType;
  var value;
  var text;

  // get the node whose text is to be changed to report an error
  idToChange = formval_getTextIdNode(fieldObj,validateDesc.label);

  if ( idToChange === null ) {
    alert("formval_validateOne failed to find 'label':'" + validateDesc.label) + "'";
    return false;
  } // endif

  /* if we are validating a checkbox */
  if (validateDesc.formType == "checkBox") {
    error = formval_validateCheckboxes(fieldObj,validateDesc);

  // if we are validating a radio button group
  } else if (validateDesc.formType == "radio") {
    error = formval_validateRadio(fieldObj,validateDesc);

  /* we are not validating a checkbox */
  } else {

    // file defaults to allowing all characters
    if (validateDesc.formType == "file") {
      verifyType = "allChars";

    // not a file
    } else {
      verifyType = validateDesc.verifyType;

      // if this field is not to be verified
      if ( !verifyType) {
        return "";
      } // endif
    } // endif

    value = fieldObj.value;

    /* get the actual text belonging to this element */
    /* for error reporting */
    text = lformval_stripLabel(validateDesc.label);

    /* if this is a required field */
    if (validateDesc.verifyRequired) {

      /* if this field is left blank */
      if ((value.length == 0) || (!value.match(/[^ ]/))) {
        if ( verifyType == "customBlank" ) {
          error = LFORMVAL_formatError(text,"customBlank");
        } else {
          error = LFORMVAL_formatError(text,"noBlank");
        } // endif

        /* change the color of given text to indicate an error */
        idToChange.style.color = errorColor;
        return error;
      } /* endif */

    /* this is not a required field */
    } else if (value.length == 0) {
      /* restore the normal color incase a bad field has been erased */
      /* from a not required field */
      idToChange.style.color = normalColor;
      return "";
    } /* endif */

    switch (verifyType.toLowerCase()) {
      case "allchars":
        break;

      case "lettersonly":
        if ( value.match(/[^a-zA-Z ]/)) {
          error = LFORMVAL_formatError(text,"lettersOnly");
        } /* endif */
        break;

      case "lettersnumbers" :
        if ( value.match(/[^a-zA-Z \-\.0-9]/)) {
          error = LFORMVAL_formatError(text,"lettersNumbers");
        } /* endif */
        break;

      case "mostchars" :
        if ( value.match(/[^a-zA-Z 0-9\~\`\!\@\#\$\%\^\&\*\(\)\_\-\+\=\|\\\{\}\[\]\:\;\'\?\/\>\<\,\.]/) ) {
          error = LFORMVAL_formatError(text,"mostChars");
        } /* endif */
        break;

      case "numbers" :
        if ( value.match(/[^0-9\-\.]/)) {
          error = LFORMVAL_formatError(text,"numbers");
        }; /* endif */
        break;

      case "postalcode" :
        if (!value.match(/^[a-zA-Z]\d[a-zA-Z]\s*\d[a-zA-z]\d$/)) {
          error = LFORMVAL_formatError(text,"postalCode");
        }; /* endif */
        break;

      case "postalzip" :
        if (!value.match(/^[a-zA-Z]\d[a-zA-Z]\s*\d[a-zA-z]\d$/)
        && !value.match(/^[0-9]{5}$/)
        ) {
          /* NOTE: there is now a 9 digit US zip code not handled by this */

          error = LFORMVAL_formatError(text,"postalZip");
        }; /* endif */
        break;

      case "postalgen" :
        // if we match anything other than a valid postal code character
        if ( value.match(/[^a-zA-Z 0-9\-]/) ) {
          error = LFORMVAL_formatError(text,"postalGen");
        }; /* endif */
        break;

      case "phone" :
        if ( !value.match(/^(1[- ])?(\d{3}[- ])?\d{3}[- ]\d{4}(\s*([xX ]|(ext)|(EXT))?\s*\d+)?$/) ) {
          error = LFORMVAL_formatError(text,"phone");
        }; /* endif */
        break;

      case "phonegen" :
        // if we match anything other than a valid phone digit (with an option a extension)
        if ( value.match(/[^ 0-9\-\(\)xX]/) ) {
          error = LFORMVAL_formatError(text,"mostChars");
        }; /* endif */
        break;

//        /* if the phone number contains invalid characters */
//        if ( !value.match(/^\+?[0-9 ()-]+[0-9]$/) ) {
//          error = LFORMVAL_formatError(text,"phone");
//        } else {
//
//          /* if there are too few or too many digits */
//          numDigits = LFORMVAL_countDigits(value);
//          if ( numDigits < 7 || numDigits > 11) {
//            error = LFORMVAL_formatError(text,"phoneDigits");
//          }; /* endif */
//        }; /* endif */
        break;

      case "internationalphone" :
        /* if the phone number contains invalid characters */
        /* (may contain an extension) */
        if ( !value.match(/^\+?[0-9 ()-\~]+[0-9](\s*([xX ]|(ext)|(EXT))\s*\d+)?$/) ) {
          error = LFORMVAL_formatError(text,"internationalPhone");
        } else {

          /* if there are too few or too many digits */
          numDigits = LFORMVAL_countDigits(value);
          if ( numDigits < 10) {
            error = LFORMVAL_formatError(text,"internationalPhoneDigits");
          }; /* endif */
        }; /* endif */
        break;

      case "phonestrictareacode" :
        /* if the phone number is not in this format */
        /* 1-111-222-3333 */
        /* or 1 111 222 3333 */
        /* or 111-222-3333 */
        /* or 111 222 3333 */
        /* or 111 222 3333 x123*/
        /* or 111 222 3333 ext 123 etc */
        if ( !value.match(/^(1[- ])?\d{3}[- ]\d{3}[- ]\d{4}(\s*([xX ]|(ext)|(EXT))?\s*\d+)?$/) ) {
          error = LFORMVAL_formatError(text,"phoneStrictAreaCode");
        }; /* endif */
        break;

      case "email":
        /* if basic email validation fails */
        if ( !value.match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,4})+$/)) {
          error = LFORMVAL_formatError(text,"email");
        }; /* endif */
        break;

      case "url":
        /* if basic url validation fails */
        if ( !value.match(/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/)) {
          error = LFORMVAL_formatError(text,"url");
        }; /* endif */
        break;

      case "currency":
        if ( !value.match(/\$?[0-9-\.]/)) {
          error = LFORMVAL_formatError(text,"currency");
        }; /* endif */
        break;

      case "currencygt0":
        if ( !value.match(/\$?[0-9-\.]/)) {
          error = LFORMVAL_formatError(text,"currency");

        } else if (value <= 0){
          error = LFORMVAL_formatError(text,"currencyGt0");
        }; /* endif */
        break;

      case "mastercard":
        /* if the mastercard is the wrong length */
        if (LFORMVAL_countDigits(value) != 16 ) {
          error = LFORMVAL_formatError(text,"numDigits");

        /* if the number does not start with the correct digits */
        } else if (!value.match(/^(51|52|53|54|55)/)) {
            error = LFORMVAL_formatError(text,"mastercardNot");

        } else {
          /* if the number fails the credit card math */
          if (!LFORMVAL_validateLuhnFormula(value)) {
            error = LFORMVAL_formatError(text,"mastercard");
          }; /* endif */
        }; /* endif */
        break;

      case "visacard":
        /* if the visacard is the wrong length */
        var numDigits = LFORMVAL_countDigits(value);

        /* if the card does not have the correct number of digits */
        if (numDigits != 16 && numDigits != 13) {
          error = LFORMVAL_formatError(text,"numDigits");

        /* number does  not start with the correct starting digit */
        } else if (!value.match(/^4/)) {
          error = LFORMVAL_formatError(text,"visacardNot");

        } else {
          /* if the number fails the credit card math */
          if (!LFORMVAL_validateLuhnFormula(value)) {
            error = LFORMVAL_formatError(text,"visacard");
          }; /* endif */
        }; /* endif */
        break;

      case "dateyyyymmdd":
        /* allowable formats yyyymmdd yyyy.mm.dd yyyy-mm-dd yyyy/mm/dd yyyy\mm\dd */
        if ( !value.match(/^[0-9]{4}[.-\/\\]?[0-9]{2}[.-\/\\]?[0-9]{2}/)) {
          error = LFORMVAL_formatError(text,"dateyyyymmdd");
        }; /* endif */
        break;

      case "datetext":
        /* allowable formats "Jan 1 2004" "Jan 01 2004" "Jan 1, 2004" "Jan 01, 2004" */
        if ( !value.match(/^[a-zA-Z]{3}[, ]?[0-9]{1,2}[ ,]?[0-9]{4}/)) {
          error = LFORMVAL_formatError(text,"dateText");
        }; /* endif */
        break;

      case "password" :
        var minLength = validateDesc.minLength;

        // pwd too short
        if (value.length < minLength) {
          error = LFORMVAL_formatError(text,"passwordLength");
        } // endif
        break;

      case "customblank":
        // this simply permits a custom message to be displayed when a field
        // is left blank
        break;

      default :
        error = "formval_validateOne: " + verifyType + " is an unknown validation type";

    } // end switch
  } // endif

  /* if there was no error */
  if ( error == "" ) {
    /* make sure the color of this is normal to indicate no error */
    idToChange.style.color = lformval_normalColour;

  /* there was an error */
  } else {
    /* change the color of given text to indicate an error */
    idToChange.style.color = lformval_errorColour;
  }; /* endif */

  return error;

}  /* end of formval_validateOne */

//************************************************************************
// Name   : LFORMVAL_validate
//  Using the same structure described in formval_validateOne, this
//  calls formval_validateOne() for every field in the "fields" array
//  and builds a list of errors which is then placed in an alert()
//  message
//
//  "validateStruct" - see formval_validateOne()
//  "formObj" - the form object to be validated (ex. document.forms.myForm)
//
// Post   :
//  if formval_validateOne returned any error messages, they have been formatted
//  and placed in an alert() box
//
// Returns : (boolean) false - validation failed (errors were reported)
//                     true  - validation succeeded (no errors reported)
//*************************************************************************/
function LFORMVAL_validate(validateStruct,formObj) {
  var i;
  var errors = new String();
  var id;
  var idName; /* index into the validateStruct for the field being validated */
  var name;

  // ensure form was properly initialized
  if (!lformval_initFormComplete) {
    alert("LFORMVAL_validate : LFORMVAL_initForm() must be called before LFORMVAL_validate()")
    return false;
  } // endif

  // if validation is to be skipped, then skip it
  if (lformval_disable) {
    return true;
  } // endif

  /* loop through the list of fields to be validated */
  for ( i = 0; i < validateStruct.length; i++) {

    /* if this field is to be skipped, then move on */
    if (validateStruct[i].verifySkip || validateStruct[i].skipLformval) {
      continue;
    }

    if ((validateStruct[i].formType == "checkBox") || (validateStruct[i].formType == "radio")) {
      // set the field obj to the form object instead
      fieldObj = formObj;
    } else {
      // get the form field
      fieldObj = formObj[validateStruct[i].name];
    } // endif

    errors += formval_validateOne(fieldObj,validateStruct[i]);

  }; /* end for */

  for (i = 0; i<lformval_customErrors.length; i++) {
    errors += lformval_customErrors[i];
  } /* end for */

  /* if at least one error was detected */
  if (errors.length > 0) {
    alert(LFORMVAL_formatError("","errorList") + errors);
  }; /* endif */

  if (errors.length > 0) {

    /* reset the custom errors */
    lformval_customErrors = lformval_customErrors.splice(0,0);

    return false;
  } else {
    return true;
  };
}  /* end of LFORMVAL_validate */

//************************************************************************
// Name   : LFORMVAL_initForm
//  This prepares the form for use with the FORMVAL routines. It walks through
//  fields belonging to the validate struct with the given structName and
//  adds an on change handler
//
//  formName       - the value of the "name=" field of the form to be initialized
//  validateStruct - see formval_validateOne()
//  normalColour    - the color to use when the form element contents
//                    passes the validation
//  errorColour     - the color to use when the form element contents
//                    fails the validation
//
// Post :
//  the form fields found in the structure referred to validateStructName
//  have had the "onchange" handler modified to call LFORMVAL_vaidateOne()
//  upon any change
//
// Returns : (nothing)
//*************************************************************************/
function LFORMVAL_initForm(formName,validateStruct,normalColour,errorColour)
{
  var i;
  var idName;
  var eventString;
  lformval_normalColour = normalColour;
  lformval_errorColour = errorColour;

  /* loop though every field in the validate structure */
  for ( i = 0; i < validateStruct.length; i++ ) {

    /* if this field is to be skipped, then move on */
    if (validateStruct[i].skipLformval) {
      continue;
    }

    idName = validateStruct[i].name;

    // if there is no id name, skip it
    if ( !idName ) {
      continue;
    } // endif

    // can't perform live validation of checkboxes and radio (only when form is submitted)
    // so skip the onchange initialization for checkbox fields
    if ( (validateStruct[i].formType != "checkBox") &&
         (validateStruct[i].formType != "radio")) {
      eventString = "document.forms." +
            formName +
            "." +
            idName +
            ".onchange = function anonymous() { formval_validateOne(this,validateStruct["+i+"]) } ";
      eval(eventString);
    } // endif
  } // end for

  lformval_initFormComplete = true;
} /* end of LFORMVAL_initForm */

