
/*
 *
 * Standard form verification functions
 * These have all been optimized for NS 4+ / IE 4+ only!
 *
 * dburry 19990830 wrote original code
 * dburry 20000125 added IE 4.x Mac bug workaround in addError
 * dburry 20000301 added isEU getCountry, deprecated isUSA isCanada
 * dburry 20000302 added clear/set/check/uncheckFirst/Last/All _setField
 * dburry 20001005 added isEuData
 * dburry 20020710 v.addError() checks field type before field.select()
 *
 *
 */

// generic VerifyForm object
// you must instantiate one of these with v = new VerifyForm() and then access
// it through its methods and properties
function VerifyForm() {

  // modify v.msgSeparator to change separator between error messages
  this.msgSeparator = '\n';

  // modify v.blankValue to change what is considered a "blank" value in the form
  // for instance, if all fields should be considered blank when "n/a" is their
  // value, then you can set that
  this.blankValue   = '';

  // private array for storage of errors
  // don't access directly, use addError() and showErrors() defined below
  this._gripes      = new Array();

  // private regexps to validate data
  // don't use these directly, use the methods defined below
  this._StateUSA = /^(A[AEKLPRSZ]|C[AOT]|DC|DE|FL|FM|GA|GU|HI|I[ADLN]|KS|KY|LA|M[ADEHINOPST]|N[CDEHJMVY]|O[HKR]|P[ARW]|RI|SC|SD|T[NTX]|UT|V[AIT]|W[AIVY])$/i; // 63 states/territories
  this._StateCan = /^(AB|BC|MB|N[BFST]|ON|PE|PQ|QC|SK|YT)$/i;  // 13 provinces/territories
  this._ZipUSA   = /^\d{5}([- ]?\d{4})?$/;
  this._ZipCan   = /^[A-Z]\d[A-Z] ?\d[A-Z]\d$/i;
  this._PhoneNA  = /^\(?\d{3}\)?\D?\d{3}\D?\d{4}$/;
  this._Email    = /^([0-9a-z_&.+-]+!)*[0-9a-z_&.+-]+@(([0-9a-z]([0-9a-z-]*[0-9a-z])?\.)+[a-z]{2,3}|([0-9]{1,3}\.){3}[0-9]{1,3})$/i;
  this._EmailBad = /^(((postmaster|root|hostmaster|mailer-daemon|webmaster)@(adobe|frame)\.com)|.*@(.*\.(adobe|frame)\.com|localhost\.com|127\.0\.0\.1))$/i;
  this._Country = {

    us: /^(u\.?s\.?(a\.?)?|united states(\s+of\s+america)?)$/i,
    ca: /^(ca\.?|can\.?|canada)$/i,
    jp: /^(jp\.?|japan|nippon)$/i,
    at: /^(at\.?|austria|osterreich)$/i,
    be: /^(be\.?|belgium|belgie)$/i,
    dk: /^(dk\.?|d[ea]nmark)$/i,
    fi: /^(fi\.?|fin\.?|finn?land|suomi)$/i,
    fr: /^(fr\.?|france)$/i,
    de: /^(de\.?|germany|deutschland)$/i,
    gr: /^(gr\.?|greece|hellas)$/i,
    ie: /^(ie\.?|(republic\s+of\s+|northern\s+)?ireland|eire)$/i,
    it: /^(it\.?|italy|italia)$/i,
    lu: /^(lu\.?|luxembourg)$/i,
    nl: /^(nl\.?|(the\s+)?ne(th|d)erlands?|holland)$/i,
    pt: /^(pt\.?|portugal)$/i,
	// Use instead of ñ because certain version of IE can't handle it YHW 06/14/01
    es: /^(es\.?|sp\.?|spain|espa[n\xF1]a)$/i,
    se: /^(se\.?|sweden|sverige)$/i,
    uk: /^(u\.?k\.?|united kingdom|(great\s+)?britain|england|scotland|wales)$/i,
    no: /^(no\.?|nrw\.?|norway|norge)$/i,
    ch: /^(ch\.?|switzerland.*)$/i,
    au: /^(au\.?|australia)$/i,
    nz: /^(nz\.?|new zealand)$/i,
    as: /^(as\.?|american samoa)$/i,
    fj: /^(fj\.?|fiji)$/i,
    pf: /^(pf\.?|french polynesia)$/i,
    mh: /^(mh\.?|marshall islands)$/i,
    nc: /^(nc\.?|new caledonia)$/i,
    pg: /^(pg\.?|papua new guinea)$/i,
    sb: /^(sb\.?|solomon islands)$/i,
    to: /^(to\.?|tonga)$/i,
    ck: /^(ck\.?|cook islands)$/i,
    bd: /^(bd\.?|bangladesh)$/i,
    kh: /^(kh\.?|cambodia)$/i,
    // in is a keyword
    india: /^(in\.?|india)$/i,
    la: /^(la\.?|laos)$/i,
    my: /^(my\.?|malaysia)$/i,
    fm: /^(fm\.?|micronesia)$/i,
    np: /^(np\.?|nepal)$/i,
    ph: /^(ph\.?|philippines)$/i,
    sg: /^(sg\.?|singapore)$/i,
    vn: /^(vn\.?|vietnam)$/i,
    th: /^(th\.?|thailand)$/i,
    kp: /^(kp\.?|korea)$/i,
    kr: /^(kr\.?|south korea)$/i,
    cn: /^(cn\.?|china)$/i,
    mn: /^(mn\.?|mongolia)$/i,
    hk: /^(hk\.?|hong kong)$/i,
    tw: /^(tw\.?|taiwan)$/i

  };
  this._CC = {
    Visa: /^4\d{12}(\d{3})?$/,
    MasterCard: /^5[1-5]\d{14}$/,
    'American Express': /^3[47]\d{13}$/,
    Discover: /^6011\d{12}$/
  };

  // import functions defined below as methods of this object
  // see below for usage
  this.addError      = _VerifyForm_addError;
  this.showErrors    = _VerifyForm_showErrors;
  this.hasValue      = _VerifyForm_hasValue;
  this.getValue      = _VerifyForm_getValue;
  this.getAllValues  = _VerifyForm_getAllValues;
  this.clearFirst    = _VerifyForm_clearFirst;
  this.clearLast     = _VerifyForm_clearLast;
  this.clearAll      = _VerifyForm_clearAll;
  this.setFirst      = _VerifyForm_setFirst;
  this.setLast       = _VerifyForm_setLast;
  this.setAll        = _VerifyForm_setAll;
  this.checkFirst    = _VerifyForm_checkFirst;
  this.checkLast     = _VerifyForm_checkLast;
  this.checkAll      = _VerifyForm_checkAll;
  this.uncheckFirst  = _VerifyForm_uncheckFirst;
  this.uncheckLast   = _VerifyForm_uncheckLast;
  this.uncheckAll    = _VerifyForm_uncheckAll;
  this._setField     = _VerifyForm__setField;
  this.validState    = _VerifyForm_validState;
  this.validZip      = _VerifyForm_validZip;
  this.validPhone    = _VerifyForm_validPhone;
  this.validEmail    = _VerifyForm_validEmail;
  this.getCountry    = _VerifyForm_getCountry;
  this.isNA          = _VerifyForm_isNA;
  this.isEU          = _VerifyForm_isEU;
  this.isEuData      = _VerifyForm_isEuData;
  this.isPacificData = _VerifyForm_isPacificData;
  this.isUSA         = _VerifyForm_isUSA; // deprecated
  this.isCanada      = _VerifyForm_isCanada; // deprecated
  this.validCardType = _VerifyForm_validCardType;
  this.validCard     = _VerifyForm_validCard;
  this.getCardType   = _VerifyForm_getCardType;
  this._mod10        = _VerifyForm__mod10;

  return this;
}

/////
//
// The following methods are for handling errors
//
/////

// v.addError() method
// adds an error message to queue, brings keyboard focus to first one
// may be modified someday to put a visual indicator next to fields
// field - the actual form field object
// msg - string of error text to show
// no return value
function _VerifyForm_addError (field, msg) {
  if ( ! this._gripes.length) {
    // IE 4.x Mac bug workaround: "object.method" produces error
    // instead of returning true when method exists!!!
    // therefore we also test for field.all here
    if (field.all || field.focus)
      field.focus();
    if (/(password|text|textarea)/.test(field.type) && (field.all || field.select))
      field.select();
  }
  this._gripes[this._gripes.length] = msg;
}

// v.showErrors() method
// when done checking for errors, call this to do alert popup
// head - text to put above error msgs
// foot - text to put below error msgs
// returns true (ok) if no errors, false if there were errors
function _VerifyForm_showErrors (head, foot) {
  if (this._gripes.length) {
    alert((head ? head + this.msgSeparator : '') + this._gripes.join(this.msgSeparator) + (foot ? this.msgSeparator + foot : ''));
    return false;
  }
  return true;
}

/////
//
// The following methods are for getting/testing data from form fields
// these are all safe for all known field types
// including multi-valued fields (same as multiple fields with same name)
// return values are true or value(s) for the following fields: if and when:
//     button: ignored
//   checkbox: checked and value is non-blank
//       file: value is non-blank
//     hidden: value is non-blank
//   password: value is non-blank
//      radio: checked and value is non-blank
//      reset: ignored
// select-one: selected and value is non-blank
// select-mul: selected and value is non-blank
//     submit: ignored
//       text: value is non-blank
//   textarea: value is non-blank
//  (unknown): value is non-blank
//
/////

// v.hasValue() method
// check if field has a value
// field - the actual form field object
// returns true/false
function _VerifyForm_hasValue (field) {


  if ( ! field.type && field.length ) {
    for (var i = 0; i < field.length; i++)
      if (field[i].type && this.hasValue(field[i]))
        return true;
    return false;
  }
  if (/select/.test(field.type))
    return (field.selectedIndex != -1 && (field.options[field.selectedIndex].value != this.blankValue));
  if (/(checkbox|radio)/.test(field.type))
    return ( field.checked && (field.value != this.blankValue) );
  if (/(button|reset|submit)/.test(field.type))
    return false;
  return (field.value != this.blankValue)
}

// v.getValue() method
// get value of field if there is a value
// field - the actual form field object
// returns value or null
// note: on multi-valued fields, returns only the first value encountered
function _VerifyForm_getValue (field) {
  if ( ! field.type && field.length ) {
    for (var i = 0; i < field.length; i++) {
      if (field[i].type) {
        var value = this.getValue(field[i]);
        if (value) return value;
      }
    }
    return null;
  }
  if (/select/.test(field.type))
    return ( (field.selectedIndex != -1 && (field.options[field.selectedIndex].value != this.blankValue))
      ? field.options[field.selectedIndex].value : null );
  if (/(checkbox|radio)/.test(field.type))
    return ( (field.checked && (field.value != this.blankValue)) ? field.value : null );
  return ( ( ! /(button|reset|submit)/.test(field.type) && (field.value != this.blankValue)) ? field.value : null )
}

// v.getAllValues() method
// get values of field if there are values, designed for multi-valued fields
// field - the actual form field object
// returns array of values or empty array
function _VerifyForm_getAllValues (field) {
  var arr = new Array();
  if ( ! field.type && field.length ) {
    var temparr;
    for (var i = 0; i < field.length; i++) {
      if (field[i].type) {
        temparr = this.getAllValues(field[i]);
        for (var j = 0; j < temparr.length; j++)
          arr[arr.length] = temparr[j];
      }
    }
  }
  else if (/select/.test(field.type)) {
    for (var i = 0; i < field.length; i++)
      if (field.options[i].selected && (field.options[i].value != this.blankValue))
        arr[arr.length] = field.options[i].value;
  }
  else if (/(checkbox|radio)/.test(field.type) && field.checked && (field.value != this.blankValue))
    arr[arr.length] = field.value;
  else if ( ! /(button|reset|submit)/.test(field.type) && (field.value != this.blankValue))
    arr[arr.length] = field.value;
  return arr;
}

/////
//
// The following methods are for clearing/setting/checking/unchecking fields
// these are all safe for all known field types
// including multi-valued fields (same as multiple fields with same name)
// return value is true if field changed, false if not
// only those input items that are checkable may be checked/unchecked
// only items whose values are settable by a normal user may be cleared/set
//     button: ignored
//   checkbox: checkable
//       file: value settable
//     hidden: ignored
//   password: value settable
//      radio: checkable
//      reset: ignored
// select-one: checkable
// select-mul: checkable
//     submit: ignored
//       text: value settable
//   textarea: value settable
//  (unknown): ignored
//
/////

// v.clear/set/check/uncheckFirst/Last/All() methods
// modify the value or checked/selected status of a field
// giving 'val' clrs/sets/checks/unchks first/last/all values that match val
// if no val is given, clrs/sets/checks/unchks first/last/all item(s) period
function _VerifyForm_clearFirst (field, val) { return this._setField(field,  1, val, this.blankValue, true) }
function _VerifyForm_clearLast  (field, val) { return this._setField(field, -1, val, this.blankValue, true) }
function _VerifyForm_clearAll   (field, val) { return this._setField(field,  0, val, this.blankValue, true) }
function _VerifyForm_setFirst     (field, toval, val) { return this._setField(field,  1, val, toval,  true) }
function _VerifyForm_setLast      (field, toval, val) { return this._setField(field, -1, val, toval,  true) }
function _VerifyForm_setAll       (field, toval, val) { return this._setField(field,  0, val, toval,  true) }
function _VerifyForm_checkFirst   (field, val)        { return this._setField(field,  1, val,  true, false) }
function _VerifyForm_checkLast    (field, val)        { return this._setField(field, -1, val,  true, false) }
function _VerifyForm_checkAll     (field, val)        { return this._setField(field,  0, val,  true, false) }
function _VerifyForm_uncheckFirst (field, val)        { return this._setField(field,  1, val, false, false) }
function _VerifyForm_uncheckLast  (field, val)        { return this._setField(field, -1, val, false, false) }
function _VerifyForm_uncheckAll   (field, val)        { return this._setField(field,  0, val, false, false) }

// internal method used above, do not call directly, use above methods instead
function _VerifyForm__setField    (field, whichway, val, toval, valueorcheck) {
  var retval = false;
  if ( ! field.type && field.length ) {
    for (var i = (whichway == 1 ? 0 : field.length - 1); i < field.length && i >= 0; i += (whichway || -1))
      if (field[i].type && this._setField(field[i], whichway, val, toval, valueorcheck))
        if (whichway) return true;
        else retval = true;
    return retval;
  }
  if (valueorcheck) {
    if (/(file|password|text)/.test(field.type) && (typeof val != 'string' || field.value == val)) {
      field.value = toval;
      return true;
    }
  }
  else if (/select/.test(field.type)) {
    for (var i = (whichway == 1 ? 0 : field.length - 1); i < field.length && i >= 0; i += (whichway || -1)) {
      if ( typeof val != 'string' || field.options[i].value == val) {
        field.options[i].selected = toval;
        if (whichway) return true;
        else retval = true;
      }
    }
    return retval;
  }
  else if (/(checkbox|radio)/.test(field.type) && (typeof val != 'string' || field.value == val)) {
    field.checked = toval;
    return true;
  }
  return false
}


/////
//
// The following methods are for testing if data actually looks valid
// all return true/false unless otherwise noted
//
/////

// v.validState() method
// check if state/province abbreviation looks valid for supplied country
function _VerifyForm_validState (s, c) { return ( this.isUSA(c) ? this._StateUSA.test(s) : (this.isCanada(c) ? this._StateCan.test(s) : true) ) }

// v.validZip() method
// check if zip/postal code looks valid for supplied country
function _VerifyForm_validZip (z, c) { return ( this.isUSA(c) ? this._ZipUSA.test(z)   : (this.isCanada(c) ? this._ZipCan.test(z)   : true) ) }

// v.validPhone() method
// check if phone number looks valid for supplied country
function _VerifyForm_validPhone (p, c) { return ( this.isNA(c)  ? this._PhoneNA.test(p)  : true ) }

// v.validEmail() method
// check if email looks valid
function _VerifyForm_validEmail (e) { return ( this._Email.test(e) && ! this._EmailBad.test(e) ) }

// v.getCountry() method
// check if country matches given list of 2-letter country abbreviations
// returns matching abbreviation (also true) or null (also false)
function _VerifyForm_getCountry (c) {
  for (var i = 1; i < arguments.length; i++)
    if (this._Country[arguments[i]] && this._Country[arguments[i]].test(c))
      return arguments[i];
  return null;
}

// v.isNA() method
// check if country looks like USA/Canada (North America)
// returns matching abbreviation (also true) or null (also false)
function _VerifyForm_isNA (c) { return ( this.getCountry(c, 'us', 'ca') ) }

// v.isEU() method
// check if country looks like it's in the European Union
// returns matching abbreviation (also true) or null (also false)
function _VerifyForm_isEU (c) { return ( this.getCountry(c, 'at', 'be', 'de', 'dk', 'es', 'fi', 'fr', 'gr', 'ie', 'it', 'lu', 'nl', 'pt', 'se', 'uk') ) }

// v.isEuData() method
// check if country is one of the ones that belongs in the European data files
// returns matching abbreviation (also true) or null (also false)
function _VerifyForm_isEuData (c) { return ( this.getCountry(c, 'at', 'be', 'ch', 'de', 'dk', 'es', 'fi', 'fr', 'ie', 'it', 'nl', 'no', 'pt', 'se', 'uk') ) }

// v.isPacificData() method
// check if country is one of the ones that belongs in the Pacific data files
// returns matching abbreviation (also true) or null (also false)
function _VerifyForm_isPacificData (c) { return ( this.getCountry(c, 'au', 'nz', 'as', 'fj', 'pf', 'mh', 'nc', 'pg', 'sb', 'to', 'ck', 'bd', 'kh', 'india', 'la', 'my', 'fm', 'np', 'ph', 'sg', 'vn', 'th', 'kp', 'kr', 'cn', 'mn', 'hk', 'tw') ) }

// v.isUSA() method
// check if country looks like USA
// this is now deprecated, use v.getCountry(c, 'us') instead
function _VerifyForm_isUSA (c) { return ( this._Country.us.test(c) ) } // deprecated

// v.isCanada() method
// check if country looks like Canada
// this is now deprecated, use v.getCountry(c, 'ca') instead
function _VerifyForm_isCanada (c) { return ( this._Country.ca.test(c) ) } // deprecated

// v.validCardType() method
// check if credit card number looks valid according to supplied type
function _VerifyForm_validCardType (n, t) {
  return ( this._CC[t] && this._CC[t].test(n) && this._mod10(n) )
}

// v.validCard() method
// check if credit card number looks valid, regardless of the type
// note: returns type (which is also true) or null (which is also false)
function _VerifyForm_validCard (n) {
  for (var t in this._CC) if (this._CC[t].test(n))
    return (this._mod10(n) ? t : null); return null
}

// v.getCardType() method
// guess which credit card type it looks like even if number looks invalid
// note: returns type (which is also true) or null (which is also false)
function _VerifyForm_getCardType (n) {
  for (var t in this._CC) if (this._CC[t].test(n)) return t; return null
}

// LUHN (Mod 10) credit card number validation algorithm
// private method used by validCard() and validCardType() above
// don't access this directly
// see http://www.beachnet.com/~hstiles/cardtype.html for more info on this
function _VerifyForm__mod10 (num) {
  var sum = 0;
  for (var i = num.length - 1; i >= 0; i -= 2)
    sum += num.charAt(i) * 1 + num.charAt(i - 1) * 2 - (num.charAt(i - 1) >= 5 ? 9 : 0);
  return ( sum % 10 == 0 );
}


