//-----------------------------------------------------------------------
// Module name   : GtFormmaker_val
// Author        : Paul Battersby
// Creation Date : 09/11/09
//  NOTE: this requires mootools
//  NOTE: this requires gtformmaker/gtformmaker_val_json.js BEFORE
//        GtformmakerVal is included
//
//  This module contains routines to support form validation.
//
//  See gtFormmaker.txt for a sample config file
//
//  ---- USAGE style 1: ----
//  In this example, myformval_json.js contains the verification information
//  NOTE: that gtFormmakerVal::initForm() requires the config struct name
/*
   <head>
     <script type="text/javascript" src="mootools-core.js"></script>

     <link href="gtLib/GtFormmaker/GtFormmaker_val.css" rel="stylesheet" type="text/css">
     <script type="text/javascript" src="gtLib/GtFormmaker/GtFormmaker_val.js"></script>
     <script type="text/javascript" src="gtLib/GtFormmaker/GtFormmaker_val_json.js"></script>
     <script type="text/javascript" src="myformval_json.js"></script>
     <script type="text/javascript" src="gtLib/GtTooltips/GtTooltips.js"></script>
     <script type="text/javascript" src="gtLib/GtTooltips/GtTooltips_balloon.js"></script>

     <SCRIPT LANGUAGE="JavaScript" type="text/javascript">
       var oFormval;

      window.addEvent("domready",function() {
        oFormVal = new GtFormmaker_val();
        oFormVal.initForm("contactUs",gtformmaker_info.formCfg);
      });

     </script>
   </head>

   <!-- NOTE: to include server side validation from gtformmakerVal.php, set the action to ""
        and see usage instructions in GtFormmaker_val.php -->
   <body>

    <form name="contactUs" action="formHandler.php" method="POST" onsubmit="return oFormval.validate(gtformmaker_info.formCfg,this)">
      <table>
        <tr>
          <td class="formLabels">Name</td>
          <td><input type="text" size="35" maxlength="40" name="name"></td>
        </tr>
        <tr>
          <td class="formLabels">Phone</td>
          <td><input type="text" size="35" maxlength="40" name="phone"></td>
        </tr>
        <tr>
          <td class="formLabels">Email</td>
          <td><input type="text" size="35" maxlength="40" name="email"></td>
        </tr>
      </table>
      <p align="center"><input type="submit" value="submit"></p>
    </form>
   </body>
*/
//  ---- USAGE style 2: ----
//
//  In this example, there is no json verification information
//  the config information is set by custom html variables
//    data-verifyType, data-verifyRequired, data-verifyMinMax (for checkboxes)
//
//  NOTE: that gtFormmakerVal::initForm() does not require a config struct name
/*
   <head>
     <script type="text/javascript" src="mootools-core.js"></script>
     <script type="text/javascript" src="gtLib/gtformmaker/gtformmakerVal_json.js"></script>
     <script type="text/javascript" src="gtLib/gtformmaker/gtformmakerVal.js"></script>
     <script type="text/javascript" src="myformval_json.js"></script>
     <script type="text/javascript" src="gtLib/gtTooltips/gtTooltips.js"></script>
     <script type="text/javascript" src="gtLib/gtTooltips/gtTooltips_balloon.js"></script>

     <SCRIPT LANGUAGE="JavaScript" type="text/javascript">
       var oFormVal;

      window.addEvent("domready",function() {
        oFormVal = new GtformmakerVal();
        oFormVal.initForm("contactUs");
      });

     </script>
   </head>

   <body>
    <form name="contactUs" action="formHandler.php" method="POST" onsubmit="return oFormVal.validate(this)">
      <table>
        <tr>
          <td class="formLabels">Name</td>
          <td><input type="text" size="35" maxlength="40" name="name" data-verifyType="lettersOnly" data-verifyRequired="true"></td>
        </tr>
        <tr>
          <td class="formLabels">Phone</td>
          <td><input type="text" size="35" maxlength="40" name="phone" data-verifyType="phoneStrictAreaCode"></td>
        </tr>
        <tr>
          <td class="formLabels">Email</td>
          <td><input type="text" size="35" maxlength="40" name="email" data-verifyType="email" data-verifyRequired="true"></td>
        </tr>
        <tr>
          <td class="formLabels">Best days to contact you?</td>
          <td>
            <!-- note the repeated name and [] and that only the first one contains verification info -->
            <input type="text" name="rate_us[]" data-verifyType="checkbox" value ="Mon" data-verifyRequired="true" data-verifyMinMax="1-3">Mon
            <input type="text" name="rate_us[]" data-verifyType="checkbox" value ="Tue">Tue
            <input type="text" name="rate_us[]" data-verifyType="checkbox" value ="Wed">Wed
          </td>
        </tr>
      </table>
      <p align="center"><input type="submit" value="submit"></p>
    </form>
      <p align="center"><input type="submit" value="Submit Comments"></p>
    </form>

   </body>

   NOTE: a "* = required field" is automatically inserted before the submit button
         UNLESS, something like the following is found in the cfg file

    {
      "formType"             : "html",
      "name"                 : "requiredMessage",
      "html"                 : "<td></td><td colspan='3'><img src='gtLib/gtFormmmaker/images/asterisk'> = required field</td>",
      "skipGtFormmakerVal"   : true
    },

    The "name" field MUST be set to "requiredMessage" if the autmomatic message insertion
    is to be skipped. It can also be skipped by setting insertRequiredMessage to
    false in the options. Also note that detecting the name "requiredMessage" in the
    cfg file, overrides the insertRequiredMessage option

*/
//  $Log: GtFormmaker_val.js $
//  Revision 1.29  2010-05-29 13:35:21-04  Battersby
//  -verifyType can now be a comma separated list
//  - domain testing only cares about the text after the LAST "."
//
//  Revision 1.28  2010-05-19 08:11:58-04  Battersby
//  - added .gov, .edu to list of domains
//  - list of domains is no longer case sensitive
//
//  Revision 1.27  2010-05-13 19:08:07-04  Battersby
//  -can now specify min length for password
//  -added zip as a format type
//
//  Revision 1.26  2010-05-11 23:52:06-04  Battersby
//  - postalzip may now have a dash between the postal code letters
//
//  Revision 1.25  2010-05-11 23:09:37-04  Battersby
//  - added test for valid email domains
//
//  Revision 1.24  2010-05-06 16:31:14-04  Battersby
//  - restored phonestrictareacode
//  - added phonestrictareacodefreeform
//
//  Revision 1.23  2010-05-03 11:23:48-04  Battersby
//  - corrected some documentation
//
//  Revision 1.22  2010-04-21 12:45:33-04  Battersby
//  *** empty log message ***
//
//  Revision 1.21  2010-04-16 13:13:05-04  Battersby
//  - added new verifyType: match
//
//  Revision 1.20  2010-04-14 10:37:43-04  Battersby
//  - fixed upper/lowercase issues
//  - added preValFn option
//  - added numberswspaces validation type
//  - postalzip now deals better with 4, 9 digit zip codes
//  - phonestrictareacode now accepts 123.123.1234
//  - added showValidateError()
//
//  Revision 1.19  2010-04-07 14:28:01-04  Battersby
//  - now uses new naming convention
//
//  Revision 1.18  2010-04-01 14:17:19-04  Battersby
//  - can now include gtFormmakerVal_json.js after gtFormmakerVal.js
//
//  Revision 1.17  2010-03-31 13:04:38-04  Battersby
//  - can now disable automatic insertion of * = required message above
//    submit button
//  - postal/zip formval now checks for postal code, 5 digit us zip, 9 digit us zip
//
//  Revision 1.16  2010-03-29 16:07:36-04  Battersby
//  - now performs init of gtTooltips
//
//  Revision 1.15  2010-03-29 13:26:43-04  Battersby
//  - replaced wz_tooltip.js with gtTooltips.js
//  - updated some documentation
//
//  Revision 1.14  2010-03-28 13:19:36-04  Battersby
//  renamed wz_tooltip.js to gtTooltips.js
//
//  Revision 1.13  2010-03-27 18:51:37-04  Battersby
//  - needed to replace removeEvent with removeEvents. Seems removeEvent doesn't
//    really work with IE6
//
//  Revision 1.12  2010-03-27 15:09:31-04  Battersby
//  - now uses gtTooltips for error messages
//
//  Revision 1.11  2010-03-24 14:11:07-04  Battersby
//  - removed some commented out code
//
//  Revision 1.10  2010-03-23 17:52:07-04  Battersby
//  - images are now in the images directory
//  - "* = required" message is now automatically inserted into just before
//    the submit button
//
//  Revision 1.9  2010-03-20 22:31:20-04  Battersby
//  - added handleSelectUpdated()
//  - added special code to handle updates to select boxes for use with
//     LmooCountrySelect
//
//  Revision 1.8  2010-03-20 11:32:00-04  Battersby
//  - complete overhaul to allow html embedded config
//  - now displays error icons instead of changing the text colour
//  - checkbox and radio button validation fully functional
//
//  Revision 1.7  2010-03-16 19:27:52-04  Battersby
//  - make small adjustment to an alert message
//
//  Revision 1.6  2009-11-30 16:15:46-04  Battersby
//  - removed check for verifySkip form desc field as nothing uses it
//
//  Revision 1.5  2009-10-31 14:27:57-04  Battersby
//  - was still using skipLformval, changed to skipGtformmakerVal
//
//  Revision 1.4  2009-10-15 13:34:45-04  Battersby
//  - made minor change to usage documentation
//
//  Revision 1.3  2009-10-04 13:42:29-04  Battersby
//  - replaced all mention of Lformval with GtformmakerVal
//
//  Revision 1.2  2009-09-14 11:43:26-04  Battersby
//  - now automatically skips validation for html, rawhtml
//
//  Revision 1.1  2009-09-14 09:52:09-04  Battersby
//  Initial revision
//
//------------------------------------------------------------------------

//---------------------------- INCLUDE FILES -----------------------------

var GtFormmaker_val = new Class({
  Implements: [Events, Options],

//----------------------------- CONSTANTS --------------------------------


//----------------------------- VARIABLES --------------------------------
  options : {
    lang : "en", // one of {"en","fr"} for English, French

    noEnter : false, // true = prevent enter from submitting the form

    libPath : "gtLib/GtFormmaker", // location of icons if this is called
                                   // from the root directory

    insertRequiredMessage : true, // true = "* = require field" will automatically
                                  //  be inserted in the form

    // NOT YET SUPPORTED
    legendLocation : "none",       // indicates that the icon legend should be
                                  // displayed and where it should be displayed
                                  // one of {"top", "bottom", "none"}

    // where to find the balloon images
    balloonImgPath : "gtLib/GtTooltips/GtTooltips_balloon",

    checkDomain : false, // true indicates email addresses should undergo a domain check
                         //  (look for .ca, .com etc)

    // a function that is to be called just prior to performing form validation
    //  Usage:
    //    var myFunc = function(formObj) {
    //      alert(formObj.name);
    //    }
    //    var formVal = new GtFormmakerVal({preValFn : myFunc})
    preValFn : Class.empty
  },

  // list of domain names
  domainList : [
    ".com",".net",".org",".biz",".info",".name",
    ".gov",".edu",
    ".us",    ".dk",    ".fr",
    ".bz",    ".se",    ".at",
    ".nu",    ".org",   ".ch",
    ".ws",    ".de",    ".be",
    ".cc",    ".it",    ".no",
    ".sr",    ".nl",    ".tv",
    ".ca",    ".mobi",  ".la",
    ".md",    ".sc",    ".hn",
    ".vc", ".uk"
  ],

  form : null, // the form being managed by this class

  ICON_SIZE : 16, // width and height of icons

  errorMsg : null, // validation error messages from json file (set by constructor)

  /* this reproduces the Javascript constant names */
  NodeType : {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_FRAGMENT_NODE: 11
  },

  customErrors : [],

  validatableFields : null,

  initFormComplete : false, // used to ensure initForm() was called

  // true indicates that the form validation is to be skipped entirely
  disabled : false,

  // list of verification icons and their properties used by __showImage()
  icons : {
    check : {
      src :        "images/check.png",
      visibility : "visible",
      title      : "Input format is valid"
    },

    warning : {
      src :        "images/warning.png",
      visibility : "visible",
      title      : "Error: "
    },

    required : {
      src :        "images/asterisk.png",
      visibility : "visible",
      title      : "This is a required field"
    },

    error : {
      src :        "images/error.png",
      visibility : "visible",
      title      : "Error: "
    },

    none : {
      src :        "images/check.png",
      visibility : "hidden",
      title      : ""
    }
  },

  img : {}, // holds icon image elements indexed by field name

//----------------------------- FUNCTIONS --------------------------------

  //************************************************************************
  // Name   : disable
  //  (boolean) disable - true  = validation will be skipped
  //                      false = validation will NOT be skipped
  //
  // Returns : (nothing)
  //************************************************************************
  disable : function(disable) {
    this.disabled = disable;
  },

  //************************************************************************
  // Name   : 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
  //************************************************************************
  splitPhone : function(phoneNumber) {
    return phoneNumber.split(/[- ]/);
  },

  //************************************************************************
  // Name   : _stripBrackets
  //  This removes the square brackets from an array name or returns the
  //  given string if no brackets were found
  //
  // Returns : (nothing)
  //************************************************************************
  _stripBrackets : function(arrayName) {
    return arrayName.replace(/[\[\]]+/g,"");
  },

  //************************************************************************
  // Name   : getRadioIndex
  //  This determines the index of the checked radio button
  //
  //  This assumes the radio buttons are created like this:
  //    <input type="radio" name="radioGroupName" value="1">
  //    <input type="radio" name="radioGroupName" value="2">
  //              ..
  //
  //  (obj) radioGroupObj - pointer to the radio group (ex document.myform.radioGroupName)
  //
  // Returns : (int) the index of the checked radio button or -1 if no button checked
  //************************************************************************
  getRadioIndex : function(radioGroupObj) {
    var i;

    /* loop through all the radio buttons */
    for ( i = 0; i < radioGroupObj.length; i++ )
    {
      /* if we've found the one that is currently checked */
      if ( radioGroupObj[i].checked )
      {
        return i;
      } /* endif */
    } // end for

    return -1;
  },

  //************************************************************************
  // Name   : getRadioVal
  //  This determines the value of the checked radio button
  //
  //  This assumes the radio buttons are created like this:
  //    <input type="radio" name="radioGroupName" value="1">
  //    <input type="radio" name="radioGroupName" value="2">
  //              ..
  //
  //  (obj) radioGroupObj - pointer to the radio group (ex document.myform.radioGroupName)
  //
  // Returns : (string) the value of the checked radio button
  //************************************************************************
  getRadioVal : function(radioGroupObj) {
    var index;

    // get the selected index
    index = this.getRadioIndex(radioGroupObj);

    // return "" if no index selected, otherwise return the value
    return (index == -1) ? "" : radioGroupObj[index].value;
  },

  //************************************************************************
  // Name   : getCheckboxVal
  //  This returns the text of the selected check boxes to the caller
  //
  //  This assumes the checkboxes are created like this:
  //    <input type="checkbox" name="checkGroupName[]" value="1">
  //    <input type="checkbox" name="checkGroupName[]" value="2">
  //              ..
  //
  //  (obj) checkBoxObj - pointer to the checkbox group (ex document.myform.checkboxGroupName)
  //
  // Returns : (array) an array containing the text of each selected checkbox
  //                   The length of the array will be 0 if nothing is checked
  //************************************************************************
  getCheckboxVal : function(checkboxObj) {
    var checkboxText = [];
    var i;
    var textIndex = 0;

    /* loop through all the check boxes */
    for ( i=0; i < checkboxObj.length; i++ ) {

      /* if this one is checked, add it to the array */
      if (checkboxObj[i].checked) {
        checkboxText[textIndex++] = checkboxObj[i].value;
      } /* endif */
    } // end for

    return checkboxText;
  },

  //************************************************************************
  // Name   : 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 "`"
  //************************************************************************
  replaceSingleQuote : function(oldString) {
    return oldString.replace(/\'/g,"`");
  },

  //************************************************************************
  // Name   : countDigits
  //  This counts the number of digits in the given string excluding characters,
  //  spaces etc.
  //
  //  (string) numberString - a number string whose digits are to be counted
  //
  // Returns : (int) number of digits in "numberString"
  //************************************************************************
  countDigits : function(numberString) {
    var matchesList = [];

    /* count the digits */
    matchesList = numberString.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 */

  },

  //************************************************************************
  // Name   : 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 objects (or ids) that are to be considered
  //               mutually exclusive.
  //  "changedElement" - 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)
  //************************************************************************
  handleMutEx : function(mutExList,changedElement) {

    // if this form element became blank
    if ($(changedElement).getValue() === "") {
      /* enable the other forms from the list */
      for ( i = 0; i < mutExList.length; i++ ) {
        $(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]) != $(changedElement) ) {
          $(mutExList[i]).disabled = true;
        } // endif
      } // end for

    } // endif
  },

  //************************************************************************
  // Name   : setErrorText
  //
  //  This allows the caller to create or alter any of the error messages in
  //  errorMsg. Useful if a different language needs to be used
  //  besides the two provided
  //
  //  (string) lang       - one of {en,fr} to indicate which group of language strings is being
  //                         modified
  //  (string) errorClass - the specific error message that is being modified
  //  (string) newMsg     - the new message string
  //
  // Returns : (nothing)
  //************************************************************************
  setErrorText : function(lang,errorClass,newMsg) {

    // if there is no support for the given language, create it
    if (!$defined(this.errMsg[lang])) {
      this.errMsg[lang] = [];
    } // endif

    // set the new message
    this.errorMsg[lang][errorClass] = newMsg;
  },

  //************************************************************************
  // Name   : 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)
  //************************************************************************
  customError : function(validateStruct,fieldObj,errorString) {
    // 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

    /* if there was no error */
    if ( errorString === "" ) {
      /* make sure the color of this is normal to indicate no error */
      this._showImage(validateStruct[structIndex].name,"check");

    /* there was an error */
    } else {
      this._showImage(validateStruct[structIndex].name,"error",errorString);
    } /* endif */

  },

  //************************************************************************
  // Name   : formatError
  //  This formats and error message and returns it to the caller
  //
  //  (string) errorId - the id of the error string that is to be reported. This is
  //              used as an index into errorMsg[]
  //
  //  (string) replaceList - (optional) an comma separated list of name value pairs
  //                     that will be used to replace tags in the error message with
  //                     the given values (first index is name, second is the value)
  //                   ex
  //                    "fieldTitle,Password"
  //
  //                   will turn this
  //                    "this must match ~fieldTitle~"
  //
  //                   into this
  //                    "this must match Password"
  //
  // Returns: (string) An error message
  //           ex: "this may only contain letters"
  //************************************************************************
  formatError : function(errorId,replaceList) {
    var message = "this " + this.errorMsg[this.options.lang][errorId];
    var replaceRegEx;
    var i;

    // if search and replace is needed
    if (replaceList) {
      replaceList = replaceList.split(",");
      // for each tag to be replaced
      for (i = 0; i < replaceList.length; i+=2) {
        replaceRegEx = new RegExp("~" + replaceList[i] + "~","g");
        message = message.replace(replaceRegEx,replaceList[i+1]);
      } // end for
    } // endif

    return message;
  },

  //************************************************************************
  // Name   : _showImage
  //  This displays, or hides, the indicated image
  //
  //  (string) fieldName - name of the form field, beside which, an image
  //                       is to be displayed
  //
  //  (string) imageType - one of {"check","warning","error","none"}
  //
  //  (string) message - optional message to appear on mouseover as the
  //                     alt or title text in the image
  //
  //  (boolean) suppressTip - (boolean) true = suppress the tool tip
  //                                    (default = false)
  // Returns : (nothing)
  //************************************************************************
  _showImage : function(fieldName,imageType,message,suppressTip) {

    // if message is missing, make it an empty string
    message = message ? message : "";

    // configure the image
    this.img[fieldName].setProperties({
      src   : this.options.libPath + "/" + this.icons[imageType].src
    });

    if (!suppressTip) {
      // remove the previous mouse events if they existed
      this.img[fieldName].removeEvents("mouseover");
      this.img[fieldName].removeEvents("mouseout");
      // replace the old mouse events with new mouseevents (with new message)
      this.img[fieldName].addEvents({
        "mouseover" : function(event,message) {
/*
          Tip(
            message,
            OFFSETX, -13,
            BALLOON, true,
            ABOVE, true
          );
*/
          Tip(
            "<span class='gtFormmaker_valTip'>" + message + "</span>"
          );
        }.bindWithEvent(this,this.icons[imageType].title + message),

        "mouseout" : function() {
          UnTip();
        }
      });
    } // endif

    // make the image visible or invisible
    this.img[fieldName].setStyle("visibility",this.icons[imageType].visibility);
  },

  //************************************************************************
  // Name   : _insertImage
  //  This inserts an image relative to the targetElement.
  //
  //  (element) targetElement - element after which the image will be placed
  //  (string)  fieldName     - the name found in the targetElement that will
  //                              also be used to index into this.img for accessing
  //                              the image
  //  (string)  where         - one of {"before", "bottom", "top", "after"}
  //                             see mootools Element.inject()
  // Returns : (element) - the just inserted image
  //************************************************************************
  _insertImage : function(targetElement,fieldName,where) {

    // default to placing the image immediately after the form field
    where = where ? where : "after";

    // create a hidden image
    this.img[fieldName] = new Element("img",{
      align : "top",
      src : this.options.libPath + "/" + this.icons["required"].src,
      styles : {
        "margin-left" : (where === "first") ? 0 : 4,
        // use a default image even through it's hidden
        display : "inline",
        visibility : "hidden",
        width  : this.ICON_SIZE,
        height : this.ICON_SIZE
      }
    });

    // inject the image
    this.img[fieldName].inject(targetElement,where);
    return this.img[fieldName];
  },

  //************************************************************************
  // Name   : _validateBoxes
  //  This ensures that the correct number of checkboxes or radio buttons have
  //  been selected
  //
  //  (string) fieldName   - name of form field to be validated
  //  (obj)    buttonGroup - a collection of radio buttons or checkboxes
  //  (boolean) init - (optional) true indicates that this was calle during initialization
  //                                   and the "required field" icon should be
  //                                   displayed instead of a warning for an
  //                                   empty field (default = false)
  // Returns :
  //  (boolean) true  - if the number of checked checkboxes is correct
  //            false - otherwise
  //************************************************************************
  _validateBoxes : function(fieldName,buttonGroup,init) {
    var error = "";
    var i;
    var count = 0;
    var minMax; // min or min-max # buttons that must be selected

    // loop through all the buttons that are part of the group and count
    // the ones that are checked
    for (i = 0; i< buttonGroup.length; i++) {
      count += $(buttonGroup[i]).get("checked") ? 1 : 0;
    } // end for

    // if this is a radio button, then a selection is always necessary
    if ($(buttonGroup[0]).getProperty("type").toLowerCase() === "radio") {
      minMax = "1";

    // a checkbox, a selection may be optional
    } else {
      minMax = $(buttonGroup[0]).getProperty("data-verifyMinMax");
    } // endif

    // if a minMax was specified
    if (minMax) {
      // minMax looks like this "1" or "1-2"
      minMax = minMax.split("-");

      // 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 = "you must choose " + minMax[0] + " option";

          // make the error message plural
          if (minMax[0] > 1) {
            error += "s";
          } // endif
        } // endif

      // user has a range of choices
      } else {
        // too many or too few selected
        if (count < minMax[0] || count > minMax[1] ) {
          error = "you must choose between " + minMax[0] + " & " + minMax[1] + " options";
        } // endif
      } // endif
    } // endif

    /* if there was no error */
    if ( error === "" ) {
      /* show the checkmark to indicate a validated field */
     this._showImage(fieldName,"check");

    /* there was an error */
    } else {
      // indicate that this field is a required field
      if (init) {
        this._showImage(fieldName,"required");
      } else {
        /* show an error image, validation failed */
        this._showImage(fieldName,"error",error);
      } // endif

    } /* endif */

    return (error === "");
  },

  //************************************************************************
  // Name   : domainIsValid
  //  This checks to see if the email address domain is found in the list
  //  of valid domains
  //
  //  (string) emailAddr - the email address to be tested
  //
  // Returns : (nothing)
  //************************************************************************
  domainIsValid : function(emailAddr) {
    // find the first @ symbol
    var domain = emailAddr.split("@");

    domain = domain[1];

    // get everything from the last "." after the "@" symbol to the end of the string
    domain = domain.substr(domain.lastIndexOf("."));

    // if this extension is in the list, then domain is valid
    if (this.domainList.contains(domain.toLowerCase())) {
      return true;
    } else {
      return false;
    } // endif
  },

  //************************************************************************
  // Name   : validateLength
  //  This returns true if the length of the given string is within the given
  //  inclusive range
  //
  //  (string) value - the value that is being tested for length
  //  (string) lengthRangeString - a array containing a "min,max" values. If max
  //                               value is set to -1, then there is no max limit
  // Returns : (boolean) true  - length is within range
  //                     false - length is not within range
  //************************************************************************
  validateLength : function(value,lengthRangeString) {
    var lengthRange = lengthRangeString.split(",");
    var min = parseInt(lengthRange[0]);
    var max = parseInt(lengthRange[1]);

    // if value is too short
    if (value.length < min) {
      return false;
    } // endif

    // if value is too long
    if (max > 0 && (value.length > max)) {
      return false;
    } // endif

    return true;
  },

  //************************************************************************
  // Name   : 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
  //
  //   NOTE: this does not validate checkboxes or radio buttons. Use
  //         _validateBoxes() for that
  //
  //  (element) fieldObj - the form element to be validated typically the object
  //                       referred to by "this"
  //
  //  (boolean) init - (optional) true indicates that this was calle during initialization
  //                                   and the "required field" icon should be
  //                                   displayed instead of a warning for an
  //                                   empty field (default = false)
  // Post   :
  //  the color of the label associated with the form element has either been
  //  set to this.options.errorColour color or this.options.normalColour
  //
  // Returns : (string) error string if an error occurred, an empty string otherwise
  //************************************************************************
  validateOne : function(fieldObj,init) {
    var i;
    var error = "";
    var idToChange;
    var verifyType;
    var value;
    var text;
    var fieldToMatch; // indicates which field, if any, must exactly match the
                      //   the current field

    // if validation has been disabled, return "no error" indication
    if (this.disabled) {
      return "";
    } // endif

    // file defaults to allowing all characters
    if ($(fieldObj).getProperty("type") === "file") {
      verifyType = "allChars";

    // not a file
    } else {
      verifyType = fieldObj.getProperty("data-verifyType");

      // if this field is not to be verified
      if ( !verifyType) {
        return true;
      } // endif

      // look for a comma separated list of verify types
      verifyType = verifyType.split(",");

    } // endif

    value = fieldObj.getProperty("value");

    // remove extra white space
    value = value.trim();

    // put the trimmed value back
    fieldObj.setProperty("value",value);

    /* if this is a required field */
    if (fieldObj.getProperty("data-verifyRequired") === "true") {

      /* if this field is left blank */
      if ((value.length === 0) || (!value.match(/[^ ]/))) {
//        if ( verifyType === "customBlank" ) {
//          error = this.formatError("customBlank");
//        } else {
          error = this.formatError("noBlank");
//        } // endif

        // indicate that this field is a required field
        if (init) {
          this._showImage(fieldObj.name,"required");
        } else {
          this._showImage(fieldObj.name,"warning",error);
        } // endif

        return (error === "");
      } /* endif */

    /* this is not a required field */
    } else if (value.length === 0) {
      // remove the image in case a bad field has been erased
      // from a not required field
        this._showImage(fieldObj.name,"none");

      return true;
    } // endif

    for (i = 0; i < verifyType.length; i++) {

      // *** NOTE: all cases must be LOWERCASE!
      switch (verifyType[i].toLowerCase()) {
        case "allchars":
          break;

        case "lettersonly":
        case "letters":
          if ( value.match(/[^a-zA-Z ]/)) {
            error = this.formatError("lettersOnly");
          } /* endif */
          break;

        case "name":
          if ( value.match(/[^a-zA-Z \.\-\']/)) {
            error = this.formatError("lettersOnly");
          } /* endif */
          break;

        case "lettersnumbers" :
          if ( value.match(/[^a-zA-Z \$\+#\-\.0-9]/)) {
            error = this.formatError("lettersNumbers");
          } /* endif */
          break;

        case "mostchars" :
          if ( value.match(/[^a-zA-Z 0-9\~\`\!\@\#\$\%\^\&\*\(\)\_\-\+\=\|\\\{\}\[\]\:\;\'\?\/\>\<\,\.]/) ) {
            error = this.formatError("mostChars");
          } /* endif */
          break;

        case "numbers" :
        case "numbersonly" :
          if ( value.match(/[^0-9\-\.]/)) {
            error = this.formatError("numbers");
          } /* endif */
          break;

        case "numberswspaces" :
          if ( value.match(/[^0-9\-\. ]/)) {
            error = this.formatError("numbers");
          } // endif
          break;

        case "postalcode" :
          if (!value.match(/^[a-zA-Z]\d[a-zA-Z]\s*\d[a-zA-z]\d$/)) {
            error = this.formatError("postalCode");
          } /* endif */
          break;

        case "zip" :
          // this matches
          // 5 or 9 digit American Zip codes
          // 12345, 12345-1234
          if (!value.match(/^[0-9]{5}$/) &&
              !value.match(/^[0-9]{5}[- \.][0-9]{4}$/)) {
            error = this.formatError("zip");
          } /* endif */
          break;

        case "postalzip" :
          // this matches Canadian postal codes,
          // 5 or 9 digit American Zip codes
          // k2k 3k3, 12345, 12345-1234
          if (!value.match(/^[a-zA-Z]\d[a-zA-Z][\s-]?\d[a-zA-z]\d$/) &&
              !value.match(/^[0-9]{5}$/) &&
              !value.match(/^[0-9]{5}[- \.][0-9]{4}$/)) {
            error = this.formatError("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 = this.formatError("postalGen");
          } /* endif */
          break;

        case "phone" :
          if ( !value.match(/^(1[- \.])?(\d{3}[- \.])?\d{3}[- \.]\d{4}(\s*([xX ]|(ext)|(EXT))?\s*\d+)?$/) ) {
            error = this.formatError("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 = this.formatError("mostChars");
          } /* 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 = this.formatError("internationalPhone");
          } else {

            /* if there are too few or too many digits */
            numDigits = this.countDigits(value);
            if ( numDigits < 10) {
              error = this.formatError("internationalPhoneDigits");
            } /* endif */
          } /* endif */
          break;

        case "internationalphonechars" :
          /* if the phone number contains invalid characters */
          /* (may contain an extension) */
          if ( value.match(/[^0-9 \(\)\-\+\~\.]/)) {
            error = this.formatError("internationalPhone");
          } else {

            /* if there are too few or too many digits */
            numDigits = this.countDigits(value);
            if ( numDigits < 10) {
              error = this.formatError("internationalPhoneDigits");
            } /* endif */
          } /* endif */
          break;

        case "phonestrictareacode" :
          /* if the phone number is not in this format */
          /* 1-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)|(ex)|(EX))?\s*\d+)?$/) ) {
            error = this.formatError("phoneStrictAreaCode");
          } /* endif */
          break;

        case "phonestrictareacodenoext" :
          /* if the phone number is not in this format */
          /*   111-222-3333 */
          if ( !value.match(/^(1[- \.])?\d{3}[-]\d{3}[-]\d{4}?$/) ) {
            error = this.formatError("phoneStrictAreaCodeNoExt");
          } /* endif */
          break;

        case "phonestrictareacodefreeform" :
          /* 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)|(ex)|(EX))?\s*\d+)?$/) ) {
            error = this.formatError("phoneStrictAreaCodeFreeForm");
          } /* endif */
          break;

        case "email":
          /* if basic email validation fails */
    //      if ( !value.match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,4})+$/)) {
          if ( !value.match(/^.+@.+\..+$/)) {
            error = this.formatError("email");

          // basic validation passed
          } else {
            // if domain error checking is enabled
            if (this.options["checkDomain"]) {
              if (!this.domainIsValid(value)) {
                error = this.formatError("badDomain");
              } // endif
            } // endif
          } /* endif */
          break;

        case "url":
          /* if basic url validation fails */
          if ( !value.match(/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/)) {
            error = this.formatError("url");
          } /* endif */
          break;

        case "currency":
          if ( !value.match(/\$?[0-9-\.]/)) {
            error = this.formatError("currency");
          } /* endif */
          break;

        case "currencygt0":
          if ( !value.match(/\$?[0-9-\.]/)) {
            error = this.formatError("currency");

          } else if (value <= 0){
            error = this.formatError("currencyGt0");
          } /* 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 = this.formatError("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 = this.formatError("dateText");
          } /* endif */
          break;

        case "password" :
          var lengthRangeString = fieldObj.getProperty("data-verifyLength") ? fieldObj.getProperty("data-verifyLength") : "8,-1";

          // if pwd too short
          if (!this.validateLength(value,lengthRangeString)) {
            var minLength = lengthRangeString.split(",")[0];
            error = this.formatError("passwordLength","length," + minLength);
          } // endif
          break;

        case "customblank":
          // this simply permits a custom message to be displayed when a field
          // is left blank
          break;

        case "match":
          var matchParams = fieldObj.getProperty("data-verifyMatch");
          if (!matchParams) {
            error = "Gtformmaker_val::validateOne() - You must provide a verifyMatch";
          } else {

            // this expects form field name, form field label
            matchParams = matchParams.split(",");
            if (fieldObj.value != this.form[matchParams[0]].value ) {
              error = this.formatError("match","label," + matchParams[1]);
            } // endif
          } // endif
          break;

        default :
          error = "GtFormmaker_val::validateOne: " + verifyType + " is an unknown validation type";
          alert("GtFormmaker_val::validateOne: " + verifyType + " is an unknown validation type");

      } // end switch

      // if an error has already been found we're done
      if (error) {
        break;
      } // endif
    } // end for

    /* if there was no error */
    if ( error === "" ) {
      /* show the checkmark to indicate a validated field */
     this._showImage(fieldObj.name,"check");

    /* there was an error */
    } else {
      /* show an error image, validation failed */
      this._showImage(fieldObj.name,"error",error);

    } /* endif */

    return (error === "");
  },

  //************************************************************************
  // Name   : showValidateError
  //  This simply shows the alert message that error have occurred
  //  (this is also needed by GtEcart_checkout.js)
  //
  // Returns : (nothing)
  //************************************************************************
  showValidateError : function() {
    alert(this.errorMsg[this.options.lang]["errorList"]);
  },

  //************************************************************************
  // Name   : validate
  //  This calls validateOne() for every field in the "fields" array
  //  for normal fields and calls _validateBoxes for radio buttons and checkboxes
  //  and alerts the user if any errors were reported
  //
  //  (element) formObj     - the form object to be validated (ex. document.forms.myForm)
  //  (boolean) skipMessage - true indicates that a dialog box does not need to be
  //                               displayed upon detection of an error. This is
  //                               only used internally during form initialization
  //                               (default = false)
  //
  //  (boolean) init - see validateOne() (default = false)
  //
  // Returns : (boolean) true  - validation succeeded (no errors reported)
  //                     false - validation failed (errors were reported)
  //************************************************************************
  validate : function(formObj,skipMessage,init) {
    var i;
    var valid = true;
    var id;
    var name;     /* name of the form field */
    var fieldObj;
    var formType;
    var boxValidated = {}; // used to keep track of which checkbox and radio button
                           //  groups have already been validated

    // if validation is to be skipped, then skip it
    if (this.disabled) {
      return true;
    } // endif

    // ensure form was properly initialized
    if (!this.initFormComplete) {
      alert("GtformmakerVal::validate : GtformmakerVal::initForm() must be called before GtformmakerVal::validate()");
      return false;
    } // endif

    // ensure the form exists
    if (!formObj) {
      alert("GtformmakerVal::validate : formObj is unknown");
      return false;
    } // endif

    // if a pre validate function has been given, and we are not
    // here during the form initialization process
    if (this.options.preValFn && !skipMessage) {
      valid = valid && this.options.preValFn.run([formObj]);
    } // endif

    // loop through the validatable fields
    this.validatableFields.each(function(item,index) {
      switch (item.fieldType) {
        case "checkbox":
          name = item.fieldName;

          // if this checkbox group has not already been validated
          if (!boxValidated[name]) {
            boxValidated[name] = true;
            if (this._validateBoxes(name,this.form[item.fieldName],init)) {
              valid = valid && true;
            } else {
              valid = false;
            } // endif
          } // endif
          break;

        case "radio":
          // if this radio group has not already been validated
          if (!boxValidated[item.fieldName]) {
            boxValidated[item.name] = true;
            if (this._validateBoxes(item.fieldName,this.form[item.fieldName],init)) {
              valid = valid && true;
            } else {
              valid = false;
            } // endif
          } // endif
          break;

        default :
          if (this.validateOne(this.form[item.fieldName],init)) {
            valid = valid && true;
          } else {
            valid = false;
          } // endif
          break;
      } // end switch
    }.bind(this));

    /* if at least one error was detected, alert the user */
    if (!valid && !skipMessage) {
      // inform the user that errors have been detected
      this.showValidateError();
    } /* endif */

    return valid;
  },

  //************************************************************************
  // Name   : _initButtons
  //   This inserts a hidden image after the last button (radio or checkbox)
  //   in a line of buttons and adds a click event to perform form validation
  //   on the checkboxes or radio buttons
  //
  //  (string) name of the button ex: "myCheckList[]", "myRadioButtons"
  //
  // Returns : (nothing)
  //************************************************************************
  _initButtons : function(buttonName) {
    var i;
    var buttonGroup; // an array of radio buttons/check boxes from the form
    var lastButton;    // the last button of a group of related buttons
    var buttonSibling; // sibling of the button if it exists

    // get the group of buttons from the form
    buttonGroup = this.form[buttonName];

    // if the button group is not an array make it an array
    if ($type(buttonGroup) === "element") {
      buttonGroup = $splat(buttonGroup);
    } // endif

    // get the last button in the array
    lastButton = $(buttonGroup[buttonGroup.length-1]);

    // check for a sibling (like <br> for example)
    buttonSibling = $(lastButton.getNext());

    // if the button has a sibling
    if (buttonSibling) {
      // inject the image before the sibling
      //(which should put the image between the button and the <br> sibling)
      this._insertImage(buttonSibling,buttonName,"before");

    // no sibling
    } else {
      // if this button has trailing text
      if ((lastButton.nextSibling) && (lastButton.nextSibling.nodeName == "#text")) {
        // place the image after the trailing html (as last sibling in parent)
        this._insertImage(lastButton.getParent(),buttonName,"bottom");

      // no trailing text, so buttons must be in columns,
      // place the image in an adjacent empty cell (requirement
      // for validatable buttons)
      } else {
        this._insertImage(lastButton.getParent().getNext(),buttonName,"top");
      } // endif
    } // endif

    // loop through each button in the group
    for (i = 0; i < buttonGroup.length; i++) {
      // add a check event to the button
      $(buttonGroup[i]).addEvent("click",function(event) {
        this._validateBoxes(buttonName,buttonGroup);
      }.bind(this));
    } // end for

  },

  //************************************************************************
  // Name   : handleSelectUpdated
  //  This is called when a select field has been programmatically altered
  //  and is no longer displaying a value. If the select is a required field
  //  then the warning icon will be shown. (useful with lmooCountrySelect
  //  when the country has changed and there is no longer a selected prov/state)
  //
  //  (string) selectName - the "name=" property of the select field
  //                        that no longer has a selected value
  //
  // Returns : (nothing)
  //************************************************************************
  handleSelectUpdated : function(selectName) {
    // if this is a validatable field
    if (this.validatableFields.get(selectName)) {
      // ensure it is not blank if it is not allowed to be blank
      // also, display the error as a note instead of as a warning
      this.validateOne(this.form[selectName],true);
    } // endif
  },

  //************************************************************************
  // Name   : _findInsertPoint
  //  This finds a parent element, at the top or bottom of the form, into which
  //  a message or other html can be inserted
  //
  //  At the bottom, this will be just above the submit button
  //
  //  NOTE: *** insert at top is not yet working
  //
  //  At the top, this will be just before the form
  //
  // Returns : (nothing)
  //************************************************************************
  _findInsertPoint : function(form,location) {
    var i;
    var insertPoint = null;

    switch (location) {
      // if we are to find an insert point at the bottom of the form
      case "bottom":
        // starting from the end, look for the submit button
        for (i = form.length-1; i >= 0; i--) {

          // get this form field and extend with mootools
          formField = $(form[i]);

          // if this is a submit button
          if (formField.get("tag") === "input" && formField.type.toLowerCase() === "submit") {
            insertPoint = formField.getParent();
            break;
          } // endif
        } // end for
        break;

      // we are to find an insertion point at the top
      case "top":
        insertPoint = $(form).getFirst("table");
        break;
    } // end switch

    // if no insert point was found, report an error
    if (!insertPoint) {
      alert("GtFormmaker_val::_findInsertPoint\n- unable to find insert point at location '"+location+"' for form name '"+form.name +"'");
    } // endif
    return insertPoint;
  },

  //************************************************************************
  // Name   : 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
  //
  //  (string) formName    - the value of the "name=" field of the form to be initialized
  //  (obj) validateStruct - (optional) see gtFormmaker.txt for a sample validateStruct
  //                           this field is used if a validateStruct is used. If html
  //                           embedded data (ex: data-verifyType="letters") is used
  //                           for validation config, then this parameter is not needed
  // Post :
  //  the form fields found in the structure referred to validateStructName
  //  (or found in the form field properties) have had the "onchange" handler
  //  modified to call LFORMVAL_vaidateOne() upon any change
  //
  // Returns : (nothing)
  //************************************************************************
  initForm : function(formName,validateStruct) {
    var i;
    var fieldName;  // value of "name=" from a form field
    var name;       // "name" field from a validate struct
    var formField;  // a field element in the form
    var verifyType; // type of verification to perform (see validateOne())
    var minMax;     // range of checkboxes that must be selected
    var numButtons; // number of radio/checkboxes in a group
    var required;   // indicates that a field is required to be filled in
    var showRequiredHtml = false; // indicates if the * = required field message
                                  // should be displayed
    var insertPoint; // where legend or *= required message is to be placed
    var img;
    var message;

    // this has to be done here (as upposed to during the Class definition) or
    //  else all instances of this class will share the same Hash() !!!
    this.validatableFields = new Hash();

    // get this form from the document
    this.form = document.forms[formName];

    // config info comes from the validateStruct or the form itself
    var src = validateStruct ? validateStruct : this.form;

    // loop through all the form fields
    for (i = 0; i < src.length; i++) {

      // if this field has no name, it is not to be verified so skip it
      if (!src[i].name) {
        continue;
      } // endif

      // if the "* = require field" has been manually inserted, then
      // we don't need to automatically insert it
      if (src[i].name === "requiredMessage") {
        this.options.insertRequiredMessage = false;
      } // endif

      // get a form field
      formField = this.form[src[i].name];

      // if this field exists in the cfg file but not in the actual form
      // (perhaps this field is meant for gtde only)
      if (!formField) {
        continue;
      } // endif

      // if this is a collection of checkboxes or radio buttons
      if ($type(formField) === "collection" ) {
        numButtons = formField.length;
        formField = formField[0];
      } // endif

      // extend the field with mootools
      formField = $(formField);

      // determine the type of field
      fieldType = formField.get("tag");

      // if this is an input (rather than a textarea)
      if (fieldType === "input") {
        // get the type of input
        fieldType = formField.getProperty("type").toLowerCase();
      } // endif

      // if config info comes from the validateStruct
      if (validateStruct) {
        // if validation is to be skipped for this field, then skip it
        if (validateStruct[i].skipGtFormmaker || validateStruct[i].skipGtFormmakerVal || validateStruct[i].formType == "submit") {
          continue;
        } // endif

        // don't change this if it's already set to something else
        if (!formField.getProperty("data-verifyType")) {
          formField.setProperty("data-verifyType",validateStruct[i].verifyType);
        } // endif
      } // endif

      // if this field is not to be verified, skip it
      if (!formField.getProperty("data-verifyType")) {
        continue;
      } // endif

      fieldName = formField.name;

      // if config info comes from the validateStruct
      if (validateStruct) {
        // don't change this if it's already set to something else
        if (!formField.getProperty("data-verifyRequired")) {
          formField.setProperty("data-verifyRequired",validateStruct[i].verifyRequired ? "true" : "false");
        } // endif

        // if this field must match another field
        if (validateStruct[i].verifyMatch) {
          formField.setProperty("data-verifyMatch",validateStruct[i].verifyMatch);
        } // endif

        // if this field must be a certain length
        if (validateStruct[i].verifyLength) {
          formField.setProperty("data-verifyLength",validateStruct[i].verifyLength);
        } // endif

      } // endif

      // determine if this is a required field
      required = (formField.getProperty("data-verifyRequired") === "true");

      // store this as a validatable field
      this.validatableFields.set(fieldName,{
        fieldName  : fieldName,
        fieldType  : fieldType,
        required   : required
      });

      switch (fieldType) {
        case "checkbox" :
          // if config info comes from the validateStruct
          if (validateStruct) {
            formField.setProperty("data-verifyMinMax",validateStruct[i].minMaxCheck);
          } // endif

          // need to move beyond the last checkbox in this group
          i+= numButtons-1;

          // if no min max was specified, report an error
          if (!formField.getProperty("data-verifyMinMax")) {
            // don't need this, it's invalid
            this.validatableFields.erase(fieldName);

            alert("GtformmakerVal::initForm() - no need to verify '" + fieldName + "' if minMax is not specified");
            break;
          } // endif

          this._initButtons(fieldName);
          break;

        case "radio" :
          // need to move beyond the last checkbox in this group
          i+= numButtons-1;

          this._initButtons(fieldName);
          break;

        case "submit":
        case "reset":
          // no setup for a submit/reset buttons
          break;

        default :
          // insert a hidden validation image immediately after the form field
          this._insertImage(this.form[fieldName],fieldName,"after");

          if (fieldType === "select") {
            // add a change event to the form element
            $(this.form[fieldName]).addEvent("change",function(event) {
              this.validateOne(event.target);
            }.bind(this));

          } else {
            // add a blur event to the form element
            $(this.form[fieldName]).addEvent("blur",function(event) {
              this.validateOne(event.target);
            }.bind(this));

          } // endif

          // if the enter key is not permitted to submit the form
          if (this.options.noEnter) {
            var element = $(this.form[fieldName]);
            var tagName = element.get("tag");
            var inputType = element.type;

            // if this is a text box
            if (tagName == "input" && inputType == "text") {
              element.addEvent("keypress",function(event) {
                return !(event.key == "enter");
              });
            } // endif

          } // endif
          break;
      } // end switch

      // if this is a required field, indicate it or validate it
      // if it already contains data
      if (required) {
        // indicate that we need to show the * = required field message
        showRequiredHtml = true;
      } // endif

    } // end for

    this.initFormComplete = true;

    // if there are any required fields
    if (showRequiredHtml) {
      // if the "* = required" message is to be automatically injected
      // just before the submit button
      if (this.options.insertRequiredMessage) {
        insertPoint = this._findInsertPoint(this.form,"bottom");
        if (insertPoint) {
          // inject the required message at the bottom of the form
          img = this._insertImage(insertPoint,"requiredMessage","top");
          this._showImage("requiredMessage","required","",true);

          // create and inject some text to go with the image
          message =  new Element("span",{
            html : " = required field<br>"
          });
          message.inject(img,"after");
        } // endif
      } // endif

      // validate fields, but don't display any dialog message
      this.validate(this.form,true,true);

    } // endif

  },

  //************************************************************************
  // Name   : initialize (constructor)
  //
  //  (object) options - (optional) configuration options. See options declaration
  //                     above for the available options
  //
  // Returns : (nothing)
  //*************************************************************************
  initialize : function(options) {
    this.setOptions(options);

    // initialize the tooltips
    gtTooltipsInit({
      offsetX :  -13,
      balloon : true,
      above : true,
      balloonImgPath : this.options.balloonImgPath
    });

    // validation error messages from json file
    this.errorMsg = gtformmakerVal_errorMsg;
  }

});


