/* Used on the Contact Form *//* * Smart Form Validation 2.32 (John.Smart at GreyDuck.com) http://www.GreyDuck.com * * Includes publicly available code from *     Jake Howlett, http://codestore.net *     Cyanide_7, ( web site off line ) *     Email validation from http://www.perlscriptsjavascripts.com/js/check_email.html *     * You may use and distribute provided that this line and above lines are not removed. A flexible, way to validate a web form (or a Lotus Notes form). Prompts user with a single alert() box explaining every invalid entry. Can check for valid string size, numeric range, or date range. Deals with percentages well. Validates all input and select types. Built-in validaiton and formatting rules for currency, date, datetime, email, percentage, and integers. Version History         1.0        Written/Assembled by John Smart on 9/26/2001         1.1        Created clearValidationMsgs, updated some comments, fixed for dates (date min/max still broken). 5/29/2002         1.2        Added email validation         1.3        Added support for textareas 7/18/2002         1.4        Added integer validation 7/25/2002         1.41        Fixed min/max for text length msg, fixed date min/max validations 7/29/2002         1.42        Fixed regressed bug where registerValidationElement wasn't setting .valiRequired properly 8/8/2002         1.43        Corrected "field must be between 5 and 5 characters long" to "field must be 5 characters long."         1.5        Added min/max for Checkbox. 9/3/2002         1.6        Fixed bug that didn't allow dash-delimited dates. Code now changes valid date values to a slash-delimited format. (e.g. converts "12-4-02" to "12/4/2002") 12/4/2002         2.0        No changes since 1.61. I just documented the flexible way of doing things. 5/2/2003         2.01        Improved documentation 5/6/2003         2.1        Improved documentation (removed defunct contact info for Cyanide_7, fixed a typo), added optional argument to formatCurrency, rounding to the nearest decimal digit (default is 2 for backward compatibility). 9/3/2003         2.2        Added validation type of "datetime" so that values like "10/20/2003 7:53 PM" can be validated.                         Changed formatMMDDYYYY to force leading zeros for single digit month and day.                         Added optional third argument to formatCurrency for denomination symbol.                         standardValidation now changes focus to first invalid element (goValiFirstInvalid) unless goValiFirstInvalid.onInvalid is defined, in which case goValiFirstInvalid.onInvalid.focus() is called. 10/21/2003         2.21        This version was misplaced!  Upgrading from this version may cause issues.  (see version 2.31 history below)         2.3        Split the code that focuses on goValiFirstInvalid from standardValidation to its own function focusOnFirstInvalid() so that nonstandard validation can use it, too.                         Also made validateElementFI, which calls validateElement and sets goValiFirstInvalid if appropriate                         Created returnValidationResults() for easier use with flexible method                         Added "today", "yesterday", and "tomorrow" as valid date values. You can negate this functionality by setting gbValiAllowTodayString to false.                         Changed documentation to reflect this. 4/28/2004         2.31        Version 2.21 was lost until now.  Added deprecated goToFirstInvalid() function for 2.21 compatibility.  Any reference to "goValiFirstInvalidElement" variable will have to be replaced by goValiFirstInvalid.  Warning: even with these changes, backward compatibility to 2.21 is not assured.  6/8/2004         2.32        Added  o === undefined check to registerValidationElements to prevent errors. 4/29/2005The easy, three step way to use this script's validation: Step 1: Change the form's onSubmit code to "return standardValidation()" Step 2: For all single-value keyword fields on the form, make sure the first option is a dummy value like "" or "(Select One)" Step 3: In the form's JS Header, create a JavaScript function named registerValidationElements() that calls this library's registerValidationElement function for each object to be validated. (Your registerValidationElements function will be called only the first time that standardValidation() is called.) The flexible way to use this script's validation on your forms: For all single-value keyword fields on the form, make sure the first option is a dummy value like "" or "(Select One)" In your onSubmit function,      1. Call clearValidationMsgs() to clear any validation messages that might have existed the previous time you called onSubmit      2. Use validateElementFI function for each field you'd like to validate. This returns true if valid. (The arguments for validateElementFI are documented below.)      3. return returnValidationResults() This flexible way allows you to write validation that are dependent on certain criteria, such as not validating a Price field if a Quantity field is blank. It also allows you to use your own validation message code... you don't have to call returnValidationResults() at all if you'd rather write your own UI. Also, if you use validateElementFI or otherwise set goValiFirstInvalid to the first object object that failed validation, you can call focusOnFirstInvalid() to push the user's cursor to the offending object. (Note: In Notes 6.5.1 (and others?) focusOnFirstInvalid() tends to only change the mouse pointer, not the cursor.) The registerValidationElement validateElementFI, and validateElement functions use the same (up to) six arguments: registerValidationElement(o, sName, bRequired[, vMax[, vMin[, sType]]]) validateElement(o[, sName[, bRequired[, vMax[, vMin[, sType]]]]]) o Object field to be validated. e.g. document.forms[0].ZipCode sName String title to use when prompting any invalid values to the user. e.g. "Zip Code", or "Name" bRequired boolean boolean value indicating whether or not this field is required. e.g. true, or false vMax Number If sType is "number", "date" or "datetime", maximum value allowed. If sType is "text" then maximum length allowed. If sType is "date" or "datetime" then a date value or date string. Time parts of date values are ignored. Time parts of date strings are not allowed. Date values and date strings are inclusive. (1/1/2000 11:00 PM is considered within a max of "1/1/2000") e.g. 10, "10/5/2090", "today", "tomorrow", "yesterday", or a date object. vMin Number If sType is "number", "date", or "datetime", minimum value allowed. If sType is "text" then minimum length allowed. If sType is "date" or "datetime" then a date value or date string. Time parts of date values are ignored. Time parts of date strings are not allowed. Date values and date strings are inclusive. e.g. 5 or "5/10/1990", "today", "tomorrow", "yesterday", or a date object.  (To disallow "today", "tomorrow", and "yesterday" from being interpreted as date values, set gbValiAllowTodayString to false.) sType String This argument allows you to override the default type of the object. If null or not supplied, the type will be o.type. This is great for most fields, but in the case of an INPUT field, o.type returns "text". In some cases, the input in question holds date or numerical values. You can augment the validation behavior by using "number", "date", "datetime", "percent", "integer", or "email" for this argument. If "datetime", dates without time values are still valid. Note: We've seen issues with fields that have been solved by explicitly passing the type in this argument. If you're validating checkbox or radio objects, you shouldn't-but-might have to include "checkbox" or "radio" here. e.g. "number", "date", "datetime", "percent", "integer", "email", "checkbox", "radio", "textarea", "select-one", or "select-multiple" So an example registerValidationElements would be: function registerValidationElements() {      var F = document.forms[0];      registerValidationElement(F.LastName, "Last Name", true) // input text that can't be empty      registerValidationElement(F.Age, "Age", false, 150, 1, "integer") // non-required input that must be a valid integer between 1 and 150 inclusive      registerValidationElement(F.State, "State", true) // keyword field of States that must have a value      registerValidationElement(F.Interests, "Interests", true) // checkboxes where at least one must be checked      registerValidaitonElement(F.Phone1, "Home Phone", false, null, 10) // input field that, if filled, must have at least ten characters      registerValidationElement(F.Discount, "% Discount", false, 100, 0, "percent") // non-required percent value between 0 and 100 inclusive.      registerValidationElement(F.BirthDate, "Birth Date", false, new Date(), "1/1/1900", "date") // optional date between 1900 and today      registerValidationElement(F.Email, "Email Address", true, null, null, "email") //required email address field. } and as long as you return standardValidation() in the onSubmit, you're done! Note: This code also works within Lotus Notes clients. ADDITIONAL JAVASCRIPT FUNCTIONS WITHIN THIS SUBFORM: *********************************************************************** validateElement is the central code in this JS library. It returns true if a field is valid, otherwise it adds the field name to the global validation message strings and returns false. Use validateElementFI instead if you also plan to bring the user to the first invalid entry using returnValidationResults or focusOnFirstInvalid. *********************************************************************** validateElementFI returns validateElement and, if validateElement returns false and goValiFirstInvalid is false (i.e. isn't already set), sets goValiFirstInvalid. This additional functionality wasn't added within the validateElement function because there are times when the additional functionality isn't necessary or wanted. *********************************************************************** valiTime returns true if a time string is valid *********************************************************************** valiDate returns true if a date is valid *********************************************************************** valiEmail returns true if a email is valid *********************************************************************** validationMsg takes global string variables of invalid field names and concatenates them into a single string to alert to the user. (These global string variables are cleared by clearValidationMsgs and appended to by validateElement) *********************************************************************** clearValidationMsgs clears the global string variables that are used in validationMsg (above). *********************************************************************** setValidationMsgs first clears (using clearValidationMsgs) then populates (using validateElement) the global string variables of invalid fields that are used in validationMsg *********************************************************************** standardValidation calls setValidationMsgs and, if there was a validation issue, calls alert(validationMsg()) and then calls focusOnFirstInvalid() to jump to the first invalid field. If you want something more customized for your form, then instead of calling this function in the onSubmit, write your own function. *********************************************************************** returnValidationResults returns true if validationMsg() is blank, otherwise alerts user, calls focusOnFirstInvalid, and returns false *********************************************************************** focusOnFirstInvalid calls object.focus() on goValiFirstInvalid (global variable set by standardValidation). In cases where focus shouldn't go directly to first object that failed validation, create/set that object's onInvalid property to the desired destination object, and this function will call goValiFirstInvalid.onInvalid.focus() instead. Note: In Lotus Notes 6.5.1 (and others?), object.focus() tends to only change the mouse pointer, not the cursor. *********************************************************************** isNumOutOfRange returns true if the first argument is not between the second and third *********************************************************************** inputToNumber returns the numeric value of argumentobject.value, or 0 if not a valid number. *********************************************************************** formatCurrency translates a number into a comma-delimited number, rounded to the specified number of digits, prefixed with a symbol of your choosing. (default is '$') For a currency field, put      this.value = formatCurrency(inputToNumber(this), 0) in the onBlur to automatically format the fields to currency rounded to the nearest dollar. You can also use this to format numbers that aren't currency by including a blank string for the denomination symbol, like:      this.value = formatCurrency(inputToNumber(this), 0, '') This function isn't called within this script library. It's just included here as a freebie because its so darn useful. *********************************************************************** For a percentage field, put      this.value = formatPercent(inputToNumber(this)) in the onBlur to automatically format the fields to a percentage. This is also done when validating fields registered with an sType of "percent". ***********************************************************************/ var gbValiAllowTodayString = true; // set to false in your scripts if you don't want user to be able to enter "today", "tomorrow", or "yesterday" as date values. var goValiFirstInvalid = false; // cleared (set to False) by clearValidationMsgs, set by setValidationMsg, used by standardValidation var gsValiPrefix = "\n - "; // constant var gsValiEmpty; // list of field names that are required but empty, each prefixed by gsValiPrefix var gsValiNaN; // list of numeric field names that don't have valid numbers. var gsValiNotDate; var gsValiNotDateTime; var gsValiOther; // list of field names and why they are invalid var gaoValiElements = new Array(); // array of elements registered via registerValidationElement. var gsDateFormat = "mm/dd/yyyy"; // change this to dd/mm/yyyy as your location requires function isNumOutOfRange(num, min, max) {      if (min != null && num < min) return -1;      if (max != null && num > max) return 1;      return 0; } function inputToNumber(o) { // given an input field, returns the numeric value, or zero if isNaN.      var r = /[^\-0-9.]/g;      var n = o.value.replace(r, "");      return (isNaN(n) ? 0 : Number(n)); } function formatPercent(n) {      return (n < 1 ? (n * 100) : n ) + "%"; } function formatCurrency(n, iDecimals, sSymbol) { /*     Author: Cyanide_7 (email and web address defunct)      Found on http://javascript.internet.com/forms/currency-format.html      altered by adding optional iDecimals argument. default of 2 rounds to nearest hundredth (i.e. penny), 3 = nearest thousandth, -3 = nearest thousand      also added optional third argument of denomination symbol. Default is '$'. Use '' for no symbol. */      var cents      var i      var sign      var tens      if (sSymbol === undefined || sSymbol === null) sSymbol = '$'            if (isNaN(n)) n = '0'; else n = n.toString().replace(/\$|\,/g,'')      sign = (n == (n = Math.abs(n)))      if (iDecimals == null) iDecimals = 2            var tens = Math.pow(10,iDecimals)      n = Math.floor(n*tens+0.50000000001)      if (iDecimals <= 0) cents = ''      else {           cents = n%tens           cents = tens.toString() + cents.toString()           cents = '.' + cents.substr(cents.length-iDecimals)      }      n = Math.floor(n/tens)      n = n.toString()      for (var i = 0; i < Math.floor((n.length-(1+i))/3); i++)           n = n.substring(0, n.length - (4 * i + 3)) + ',' + n.substring(n.length - (4 * i + 3))      return (((sign)?'':'-') + sSymbol + n + cents) } function timeComponents(sTime) {      var aTime = sTime.match(/^(\d{1,2}):(\d{2})\s{1}([AP]M)$/i);            if (aTime == null) return null;            return(new Array(aTime[1], aTime[2], aTime[3])); } function valiTime(timeBits) {      if (timeBits == null) return false;      var iHour = Number(timeBits[0]);      var iMinute = Number(timeBits[1]);      var sAMPM = timeBits[2];            if (iHour < 1 || iHour > 12) return false;      if (iMinute < 0 || iMinute > 59) return false;      return true; } function dateComponents(dateStr) {      var results = new Array();      if (gbValiAllowTodayString && dateStr.toLowerCase) {           var bDateString;           var dt;           switch (dateStr.toLowerCase()) {           case "today" :                bDateString = true                dt = new Date()                break;           case "yesterday" :                bDateString = true                dt = new Date()                dt.setDate(dt.getDate() - 1)                break;           case "tomorrow" :                bDateString = true                dt = new Date()                dt.setDate(dt.getDate() + 1)                break;           default :                bDateString = false           }           if (bDateString) { // This is a date instead of a string                results[0] = dt.getDate();                results[1] = dt.getMonth() + 1;                results[2] = dt.getFullYear();                return results;           }      }       /*     Added functionality for if dateStr is actually a date, not a string */      if (dateStr.getDate) { // This is a date instead of a string           results[0] = dateStr.getDate();           results[1] = dateStr.getMonth() + 1;           results[2] = dateStr.getFullYear();           return results;      } /*     The following splits a date into day, month and year components      Used with persmission from Jake Holwett, http://www.codestore.org */      var datePat = /^(\d{1,2})([\-\/\.])(\d{1,2})\2(\d{2}|\d{4})$/;      var matchArray = dateStr.match(datePat);      if (matchArray == null) {           return null;      }      //check for two digit years and prepend 20.      matchArray[4] = (matchArray[4].length == 2) ? '20' + matchArray[4] : matchArray[4];      // parse date into variables      if (gsDateFormat.charAt(0)=="d"){ //what format does the server use for dates?           results[0] = Number(matchArray[1]);           results[1] = Number(matchArray[3]);      } else {           results[1] = Number(matchArray[1]);           results[0] = Number(matchArray[3]); }      results[2] = Number(matchArray[4]);      return results; } function valiDate(dateBits) { // example usage: if (!valiDate(dateComponents(obj.value))) alert("Bad Date!")      if (dateBits == null) return false;      day = dateBits[0];      month = dateBits[1];      year = dateBits[2];      if ((month < 1 || month > 12) || (day < 1 || day > 31)) { // check month range           return false;      }      if ((month==4 || month==6 || month==9 || month==11) && day==31) {           return false;      }      if (month == 2) {           // check for february 29th           var isleap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));           if (day>29 || (day==29 && !isleap)) {           return false;           }      }      return true; } function valiEmail(e) { // Email validation from http://www.perlscriptsjavascripts.com/js/check_email.html      ok = "1234567890qwertyuiop[]asdfghjklzxcvbnm.@-_QWERTYUIOPASDFGHJKLZXCVBNM";      for(i=0; i < e.length ;i++) if(ok.indexOf(e.charAt(i))<0)return (false)      re = /(@.*@)|(\.\.)|(^\.)|(^@)|(@$)|(\.$)|(@\.)/;      re_two = /^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;      if (!e.match(re) && e.match(re_two)) return (true);      return (false) } function leading0s(iValue, iMinDigits) {      var s = iValue.toString();      while (s.length < iMinDigits) s = '0' + s;      return s } function formatMMDDYYYY(aiDateBits) {           if (gsDateFormat.charAt(0)=="d") //what format does the server use for dates?                return leading0s(aiDateBits[0], 2) + "/" + leading0s(aiDateBits[1], 2) + "/" + aiDateBits[2]           else                return leading0s(aiDateBits[1], 2) + "/" + leading0s(aiDateBits[0], 2) + "/" + aiDateBits[2] } function registerValidationElement(o, sName, bRequired, nMax, nMin, sType) { /* adds properties to o for later use in validateElement(), then adds o to the gaoValiElements array */ 	var undefined;	     if (o === undefined) {           alert("Error: object argument passed to registerValidationElement is undefined.\n(" + sName + ")");           return false;      }     o.valiName = (sName ? sName : o.name);      o.valiType = (sType ? sType : o.type);      o.valiMin = nMin;      o.valiMax = nMax;      if (bRequired) o.valiRequired = true;      gaoValiElements[gaoValiElements.length] = o; } function goToFirstInvalid() { /* deprecated.  function existed in lost codestream of 2.21.  Here only for backwards compatibility.  Use focusOnFirstInvalid instead */ } function focusOnFirstInvalid() {      if (goValiFirstInvalid) {           var o = goValiFirstInvalid.onInvalid;           if (!o) o = goValiFirstInvalid;           if (o.focus) {                o.focus()                if (o.select) o.select()           }           return true      } else return false } function standardValidation() {      clearValidationMsgs();      if (setValidationMsgs()) return true      else {           return returnValidationResults()      } } function returnValidationResults() {      var s = validationMsg()      if (s.length == 0) return true      else {           alert(validationMsg())           focusOnFirstInvalid()           return false      } } function clearValidationMsgs() {      gsValiEmpty = "";      gsValiNaN = "";      gsValiNotDate = "";      gsValiNotDateTime = "";      gsValiOther = "";      goValiFirstInvalid = false } function setValidationMsgs() {      var undefined;      // don't forget to call clearValidationMsgs first!      if (gaoValiElements.length == 0) registerValidationElements(); //if gaoValiElements not populated, populate it.            for (var i=0; i < gaoValiElements.length; i++) if (!validateElement(gaoValiElements[i]) && !goValiFirstInvalid)           goValiFirstInvalid = gaoValiElements[i];      if (gsValiEmpty) return false;      if (gsValiNaN) return false;      if (gsValiNotDate) return false;      if (gsValiNotDateTime) return false;      if (gsValiOther) return false;      return true; } function validationMsg() {      var sMsg = "";            if (gsValiEmpty) sMsg = "The following fields are required but empty:" + gsValiEmpty + "\n\n";      if (gsValiNaN) sMsg += "The following numeric fields have invalid values:" + gsValiNaN + "\n\n";      if (gsValiNotDate) sMsg += "The following date fields have invalid values:" + gsValiNotDate + "\n\n";      if (gsValiNotDateTime) sMsg += 'The following date-time fields have invalid values:' + gsValiNotDateTime + '\n(valid date-time format is "' + gsDateFormat + ' hh:mm AM")\n\n';      if (gsValiOther) sMsg += "The following fields are invalid:" + gsValiOther;      return sMsg; } function validateElementFI(o, sName, bRequired, vMax, vMin, sType) { // sets goValiFirstInvalid if validateElement is false and goValiFirstInvalid isn't already set      if (validateElement(o, sName, bRequired, vMax, vMin, sType)) return true      else {           if (!goValiFirstInvalid) goValiFirstInvalid = o           return false      } } function validateElement(o, sName, bRequired, vMax, vMin, sType) { // only the first argument is required if registerValidationElement was called.      var r; // regular expression      var val; // o.value      var i; //counter      var iLen; //val.length      var undefined;      var sTime = undefined; // time portion of val, used for datetime types of elements      var sTimeStartMsg = ''; // part of the error message if invalid datetime value      var sTimeEndMsg = '';           if (o === undefined) {           alert("Error: object argument passed to validateElement is undefined.\n(" + sName + ")");           return false;      } // if any parameters aren't supplied, check for the properties that should have been set during registerValidationElement      if (sName === undefined) sName = (o.valiName) ? o.valiName : o.name;      if (bRequired === undefined) bRequired = o.valiRequired;      if (vMin === undefined) vMin = o.valiMin;      if (vMax === undefined) vMax = o.valiMax;      if (sType === undefined) sType = (o.valiType) ? o.valiType : o.type; // set val and bEmpty          switch(sType) {           case "select-one" :                val = o.selectedIndex; // use selectedIndex property, if available                bEmpty = (val < 1); // 0 or -1, with assumption that first choice is blank.                break;           case "select-multiple" :                val = o.selectedIndex; // use selectedIndex property, if available                bEmpty = (val == -1);                break;           case "text" :           case "number" :           case "integer" :           case "percent" :           case "date" :           case "datetime" :           case "email" :           case "textarea" :                val = o.value                bEmpty = (val.length == 0);                break;           case "radio" :           case "checkbox" :                bEmpty = true;                for (i = 0; i < o.length; i++) {                     if (o[i].checked) {                          val = i;                          bEmpty = false;                          break;                     }                }                break;           default :                alert("Error: validateElement doesn't know how to deal with valiType = " + sType + "\n(" + sName + ")");                return false;      } // if bEmpty is true, immediately return false or true, depending on if it's required or not.      if (bEmpty) {           if (bRequired) {                gsValiEmpty += gsValiPrefix + sName;                return false;           } else return true      } // now for the real validation          switch (sType) {           case "text" :           case "textarea" :                iLen = val.length;                if (isNumOutOfRange(iLen, vMin, vMax)) {                                     if (vMin == null)                          gsValiOther += gsValiPrefix + sName + " must be less than " + vMax + " characters long."                     else if (vMax == null)                          gsValiOther += gsValiPrefix + sName + " must be at least " + vMin + " characters long."                     else if (vMax == vMin)                          gsValiOther += gsValiPrefix + sName + " must be " + vMin + " characters long."                     else                          gsValiOther += gsValiPrefix + sName + " must be between " + vMin + " and " + vMax + " characters long.";                     return false;                }                return true;           case "email" :                if (valiEmail(o.value)) return true                else {                     gsValiOther += gsValiPrefix + sName + " requires a valid email address.  Please check the entry.";                     return false                }           case "percent" :           case "number" :           case "integer" :                if (sType == "integer")     r = /[, ]/g; // regexp set to look for all commas and spaces.                else r = /[$,% ]/g; // regexp set to look for all dollar signs, commas, percent signs, and spaces.                val = val.replace(r,"") // strip them out                if (isNaN( val )) {                     gsValiNaN += "\n -- " + sName;                     return false                } else if (isNumOutOfRange(val, vMin, vMax)) {                     if (vMin == null)               gsValiOther += gsValiPrefix + sName + " must be less than or equal to " + vMax                     else if (vMax == null)     gsValiOther += gsValiPrefix + sName + " must be at least " + vMin                     else                               gsValiOther += gsValiPrefix + sName + " must be between " + vMin + " and " + vMax;                                         return false                } else if (sType == "integer" && val != parseInt(val)) {                     gsValiOther += gsValiPrefix + sName + " must be an integer.";                     return false;                }                if (sType == "percent") o.value = formatPercent(val); // make sure a % is within the field. Domino will treat '5' as 500%                return true;           case "datetime" :                var i = val.indexOf(' ');                if (i != -1) {                     sTime = val.substr(i + 1);                     if (valiTime(timeComponents(sTime))) {                          val = val.substr(0, i)                     } else {                          gsValiNotDateTime += gsValiPrefix + sName;                          return false;                     }                }                sTimeStartMsg = (sType != 'datetime' ? '' : ' 12:00 AM');                sTimeEndMsg = (sType != 'datetime' ? '' : ' 11:59 PM');                      // note: no break. code continues to validate the date as well           case "date" :                var aiDateBits = dateComponents(val);                if (aiDateBits == null) {                     if (sType=='date') gsValiNotDate += gsValiPrefix + sName                     else gsValiNotDateTime += gsValiPrefix + sName;                     return false;                }                //Check it is a valid date first                if (valiDate(aiDateBits) == false) {                     if (sType=='date') gsValiNotDate += gsValiPrefix + sName                     else gsValiNotDateTime += gsValiPrefix + sName;                     return false;                }                //Now check whether a range is specified and if in bounds                var theDate = new Date(aiDateBits[2], parseInt(aiDateBits[1]) - 1, aiDateBits[0]);                if ( vMin ) {                     var minBits = dateComponents (vMin);                     for (i = 2; i > -1; i--) {                          if (minBits[i] < aiDateBits[i]) break // if year is before, stop. Don't compare month numbers.                          else if (minBits[i] > aiDateBits[i]) {                               if ( vMax )     gsValiOther += gsValiPrefix + sName + " must be between " + formatMMDDYYYY(minBits) + sTimeStartMsg + " and " + formatMMDDYYYY(dateComponents(vMax)) + sTimeEndMsg                               else gsValiOther += gsValiPrefix + sName + " must be on or after " + formatMMDDYYYY(minBits) + sTimeStartMsg                               return false;                          }                     }                }                if ( vMax) {                     var maxBits = dateComponents (vMax);                     for (i = 2; i > -1; i--) {                          if (maxBits[i] > aiDateBits[i]) break                          else if (maxBits[i] < aiDateBits[i]) {                               if ( vMin )     gsValiOther += gsValiPrefix + sName + " must be between " + formatMMDDYYYY(minBits) + sTimeStartMsg + " and " + formatMMDDYYYY(maxBits) + sTimeEndMsg                               else          gsValiOther += gsValiPrefix + sName + " must be on or before " + formatMMDDYYYY(maxBits) + sTimeEndMsg                               return false;                          }                     }                }                o.value = formatMMDDYYYY(aiDateBits)                if (sTime !== undefined) o.value += ' ' + sTime                return true;                     case "checkbox" :                if (vMin || vMax) { // don't bother checking if no need.                     var iChecked = 1;                     for (i = val + 1; i < o.length; i++) if (o[i].checked) iChecked++                     if (isNumOutOfRange(iChecked, vMin, vMax)) {                          if (vMin == null)                               gsValiOther += gsValiPrefix + sName + " must have less than " + (vMax + 1) + " choices selected."                          else if (vMax == null)                               gsValiOther += gsValiPrefix + sName + " must have at least " + vMin + " choices selected."                          else if (vMax == vMin)                               gsValiOther += gsValiPrefix + sName + " must have " + vMin + " choices selected."                          else                               gsValiOther += gsValiPrefix + sName + " must have between " + vMin + " and " + vMax + " choices selected.";                          return false;                     }                }                return true;           default : // select, radio, etc                return true      } } 
