function Field (name,options,caption,condition) {
  this.fldname = name;
  this.options = options;
  this.caption = caption;
  this.condition = condition;

  return this;
}

function Validator(formName,skipNonExisting) {
  this.need2check=true;
  this.submittedAlready=false;
  this.skipNonExisting=skipNonExisting;
  this.formName = formName;
  this.fldsToValidate = new Array();

  this.AlertMandatory = 
function AlertMandatory(fld,caption) {
  var msg = caption? ('"' + caption + '"') : 'This'
  msg += ' is a mandatory field!';
  this.DoAlert(fld,msg);
}

  this.AlertNumeric =
function AlertNumeric(fld,caption) {
  var msg = caption? ('Field "' + caption + '"') : 'This field'
  msg += ' must contain only digits!';
  this.DoAlert(fld,msg);
}
  
  this.AlertFloat =
function AlertFloat(fld,caption) {
  var msg = caption? ('Field "' + caption + '"') : 'This field'
  msg += ' must contain only digits and decimal point!';
  this.DoAlert(fld,msg);
}
  
  this.AlertEmail =
function AlertEmail(fld,caption) {
  var msg = caption? ('"' + caption + '"!') : 'this field!'
  msg = 'Invalid e-mail address in ' + msg;
  this.DoAlert(fld,msg);
}

  this.AlertTooLong =
function AlertTooLong(fld,caption,len) {
  var msg = "Text in " + (caption ? ('"' + caption + '"') : 'this field' ) + " cannot exceed " + len + " characters";
  this.DoAlert(fld,msg);
}

  this.AlertBetween=
function AlertBetween(fld,caption,min,max) {
  var msg = "The value in " + (caption ? ('"' + caption + '"') : 'this field' ) + " should be ";
  if (min!='' && max!='') msg += 'between '+min+' and '+max;
  else if (max!='')       msg += 'less than or equal to '+max;
  else if (min!='')       msg += 'greater than or equal to '+min;
  this.DoAlert(fld,msg);
}

  this.AlertBetweenX=
function AlertBetweenX(fld,caption,min,max) {
  var msg = "The value in " + (caption ? ('"' + caption + '"') : 'this field' ) + " should be ";
  if (min!='' && max!='') msg += 'greater than '+min+' and less than '+max;
  else if (max!='')       msg += 'less than '+max;
  else if (min!='')       msg += 'greater than '+min;
  this.DoAlert(fld,msg);
}

  this.AlertEq=
function AlertEq(fld,caption) {
  var msg = caption? caption : 'Fields must be the same!';
  this.DoAlert(fld,msg);
}

  this.AlertNe=
function AlertNe(fld,caption) {
  var msg = caption? caption : 'Fields must NOT be the same!';
  this.DoAlert(fld,msg);
}

  this.AlertEnterFullDate=
function AlertEnterFullDate(fld,caption) {
  var msg = caption? caption : 'One or more of the date fields have not been completed.';
  this.DoAlert(fld,msg);
}
  this.AlertAtLeastOne=
function AlertAtLeastOne(fld,caption) {
  var msg = caption? caption : 'At least one of these fields must be filled in!';
  this.DoAlert(fld,msg);
}

  this.AlertInvalidDate=
function AlertInvalidDate(fld,what) {
  var msg;
  if (what=='d')         msg='Invalid day!';
  else if (what=='m')    msg='Invalid month!';
  else if (what=='y')    msg='Please enter only last 2 digits of the year.';
  else if (what=='Y')    msg='Please enter all 4 digits of the year.';

  this.DoAlert(fld,msg);
}

  this.AlertSome=
function AlertSome(fld,caption) {
  var msg = caption? caption : 'Invalid value in this field!';
  this.DoAlert(fld,msg);
}

  this.DoAlert=
function DoAlert(fld,msg) {
  if (msg.indexOf('`')<0) {
    var fldType = this.GetFldType(fld);
    if (fldType=='radio')         fld[0].focus();
    else if (fldType!='hidden')   fld.focus();
  }
  else {
    var mfm=msg.split('`',3);
    if (mfm.length==3) {
      var fldname = mfm[1];
      msg = mfm[0] + mfm[2]
      var fld2=fld.form.elements[fldname];
      if (fld2) fld2.focus();
    }
  }
  alert(msg);
}

  this.CheckEmail=
function CheckEmail(str) {
  // must have @ and it should not be 1st or last
  if (str.search(/.+@.+/)==-1)      return false;
  // must have only one @
  if (str.search(/@.+@/)!=-1)       return false;
  // must not have .. after @
  if (str.search(/@.*\.\./)!=-1)    return false;
  // must have at least one. after @
  if (str.search(/@.+\..+/)==-1)    return false;
  // must not have . at the end
  if (str.search(/\.$/)!=-1)    return false;

  // must consist of the following chars: (?)
  var re = /\-|[A-Z]|[0-9]|_|@|&|\~|\/|\$|=|!|\"|\%|\.|\'/ig;
  if (str.replace(re,'')!='') return false;

  return true;
}

  this.GetFldType=
function GetFldType(fld) {
  if (fld==null) return null;
  var fldType = fld.type;
  if (fldType==null) {
    if (fld.length!=null) fldType = fld[0].type;
    if (fldType==null) alert('Error! Field '+Fields[j].fldname+' doesn\'t have type!')
  }
  return fldType;
}

  this.Trim=
function Trim(s) {
  for (var i=s.length-1;i>=0;--i) {
    if (s.charAt(i)==' ') s = s.substring(0,i);
    else break;
  }
  while(s.charAt(0)==' ') {
    s = s.substring(1,s.length);
  }
  return s;
}

  this.GetFldVal=
function GetFldVal(fld) {
  if (fld==null) return null;
  var fldType = this.GetFldType(fld);
  if (fldType=='text' || fldType=='textarea'
      || fldType=='password' || fldType=='hidden')  {
    fld.value = this.Trim(fld.value);
    return fld.value;
  }
  else if (fldType=='select-one')                   return fld.options[fld.selectedIndex].value
  else if (fldType=='checkbox')                     return fld.checked? fld.value : '';
  else if (fldType=='radio') {
    for (var i=0;i<fld.length;++i)
      if (fld[i].checked) return fld[i].value;
    return '';
  }
  return '';
}

  this.VerifyDate=
function VerifyDate(thatForm,options,caption,fldnames) {
  var fld1 = thatForm.elements[fldnames[0]];
  var fld2 = thatForm.elements[fldnames[1]];
  var fld3 = thatForm.elements[fldnames[2]];
  if (fld1==null) {
    if (this.NotifyFieldDoesntExist(fldnames[0])) return true;
    else return false;
  }
  if (fld2==null) {
    if (this.NotifyFieldDoesntExist(fldnames[1])) return true;
    else return false;
  }
  if (fld3==null) {
    if (this.NotifyFieldDoesntExist(fldnames[2])) return true;
    else return false;
  }
  var format = fldnames[3];
  var flds = new Array(fld1,fld2,fld3);
  var fldvals = new Array(GetFldVal(fld1),GetFldVal(fld2),GetFldVal(fld3));

  var year=-1,month=0;
  var dayfld=0;
  for (var i=0;i<3;i++) {

    if (options.search(/m/)!=-1) {
      // mandatory
      if (fldvals[i]=='')      {
        this.AlertMandatory(flds[i],caption)
        return false;
      }
    }
    if (fldvals[i]=='' && (fldvals[0]!='' || fldvals[1]!='' || fldvals[2]!=''))
    {
      this.AlertEnterFullDate(flds[i],caption)
      return false;
    }
    if (!fldvals[i].match(/^\d*$/)) {
      this.AlertNumeric(flds[i],caption)
      return false;
    }
    if (fldvals[i]!='') {
      if (format.substr(i,1)=='m' || format.substr(i,1)=='M') {
        month = fldvals[i]
        if (parseInt(fldvals[i],10)<1 || parseInt(fldvals[i],10)>12) {
          this.AlertInvalidDate(flds[i],'m')
          return false;
        }
      }
      else if (format.substr(i,1)=='d' || format.substr(i,1)=='D') {
        dayfld = flds[i];
        // we don't know the month & year yet. Just remember which fld is a day
        // and check again later
        if (parseInt(flds[i].value,10)<1 || parseInt(flds[i].value,10)>31) {
          this.AlertInvalidDate(flds[i],'d')
          return false;
        }
      }
      else if (format.substr(i,1)=='y') {
        year = flds[i].value
        if (parseInt(flds[i].value,10)>100) {
          this.AlertInvalidDate(flds[i],'y')
          return false;
        }
      }
      else if (format.substr(i,1)=='Y') {
        year = flds[i].value
        if (parseInt(flds[i].value,10)<1000) {
          this.AlertInvalidDate(flds[i],'Y')
          return false;
        }
      }
    }
  }

  // now we know the month & year
  if (dayfld!=0 && month!=0 && year!=-1) {
    var months = new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);
    if (year%4==0) months[2]++;  // %4 is enough !
    if (dayfld.value!='' && parseInt(dayfld.value,10)>months[month]) {
      this.AlertInvalidDate(dayfld,'d')
      return false;
    }
  }
  return true;
}

  this.NotifyFieldDoesntExist=
function NotifyFieldDoesntExist(fldname) {
  if (skipNonExisting) return true;
  alert('Error! Field '+fldname+' doesn\'t exist!');
  return false;
}

  this.CheckFields=
function CheckFields(thatForm,Fields) {

  for (j=0;j<Fields.length;j++) {
    if (Fields[j].condition!=null) {
      var str=Fields[j].condition;
      str=str.replace(/\$(\w+)(\W|$)/g,"validator.fldval('$1')$2"); // Perl-like vars ($xyz)

      var ret = eval(str);
      if (!ret) continue;
    }
    if (Fields[j].fldname=='=') {
      var ret = eval(Fields[j].options);
      if (!ret) return false;
      continue;
    }
    if (Fields[j].fldname.substr(0,1)!='?') {
      // real fields
      var fld = thatForm.elements[Fields[j].fldname];
      if (fld==null) {
        if (this.NotifyFieldDoesntExist(Fields[j].fldname)) continue;
        else return false;
      }
      var fldType = fld.type;
      if (fldType==null) {
        if (fld.length!=null) fldType = fld[0].type;
        if (fldType==null) alert('Error! Field '+Fields[j].fldname+' doesn\'t have type!')
      }
      if (Fields[j].options=='') {
          this.AlertSome(fld,Fields[j].caption)
          return false;
      }
        
      var val = this.GetFldVal(fld);

      if (Fields[j].options.search(/m/)!=-1) {
        // mandatory fields
        if ( val == '' ) {
          this.AlertMandatory(fld,Fields[j].caption)
          return false;
        }
      }
      if ((val!='') && (fldType=='text' || fldType=='textarea')) {
        // all other checks for text fields only
        if (Fields[j].options.search(/f/)!=-1 && !val.match(/^\d*\.?\d*$/)) {
          // numeric fields
          this.AlertFloat(fld,Fields[j].caption)
          return false;
        }
        if (Fields[j].options.search(/n/)!=-1 && !val.match(/^\d*$/)) {
          // numeric fields
          this.AlertNumeric(fld,Fields[j].caption)
          return false;
        }
        if (Fields[j].options.search(/e/)!=-1 && !this.CheckEmail(val)) {
          // email fields
          this.AlertEmail(fld,Fields[j].caption)
          return false;
        }
        if (Fields[j].options.search(/l\d+/)!=-1) {
          // fields with maxlen specified (usefull for textarea)
          var len = Fields[j].options.replace(/.*l(\d+).*/,'$1')
          if (val.length > len) {
            this.AlertTooLong(fld,Fields[j].caption,len)
            return false;
          }
        }
        if (Fields[j].options.search(/b\d*\:\d*/)!=-1) {
          // between min:max
          var bb = Fields[j].options.replace(/.*b(\d*):(\d*).*/,'$1:$2')
          var min = bb.replace(/(\d*):(\d*)/,'$1')
          var max = bb.replace(/(\d*):(\d*)/,'$2')

          if ((min!='' && parseFloat(val,10)<min) || (max!='' && parseFloat(val,10)>max)) {
            this.AlertBetween(fld,Fields[j].caption,min,max)
            return false;
          }
        }
        if (Fields[j].options.search(/B\d*\:\d*/)!=-1) {
          // exclusive between min:max
          var bb = Fields[j].options.replace(/.*B(\d*):(\d*).*/,'$1:$2')
          var min = bb.replace(/(\d*):(\d*)/,'$1')
          var max = bb.replace(/(\d*):(\d*)/,'$2')

          if ((min!='' && parseFloat(val,10)<=min) || (max!='' && parseFloat(val,10)>=max)) {
            this.AlertBetweenX(fld,Fields[j].caption,min,max)
            return false;
          }
        }
      }
    }
    else {
      var fldnames = Fields[j].fldname.substr(1).split(':');
      if (Fields[j].options.search(/^[A-z]*!=/)!=-1) {
        // verify 2 fields for !=
        var fld1 = thatForm.elements[fldnames[0]];
        var fld2 = thatForm.elements[fldnames[1]];
        if (fld1==null) {
          if (this.NotifyFieldDoesntExist(fldnames[0])) continue;
          else return false;
        }
        if (fld2==null) {
          if (this.NotifyFieldDoesntExist(fldnames[1])) continue;
          else return false;
        }

        if (GetFldVal(fld1) == GetFldVal(fld2)) {
          AlertNe(fld,Fields[j].caption)
          return false;
        }
      }
      else if (Fields[j].options.search(/^[A-z]*=/)!=-1) {
        // verify 2 fields for ==
        var fld1 = thatForm.elements[fldnames[0]];
        var fld2 = thatForm.elements[fldnames[1]];
        if (fld1==null) {
          if (this.NotifyFieldDoesntExist(fldnames[0])) continue;
          else return false;
        }
        if (fld2==null) {
          if (this.NotifyFieldDoesntExist(fldnames[1])) continue;
          else return false;
        }

        if (GetFldVal(fld1) != GetFldVal(fld2)) {
          this.AlertEq(fld,Fields[j].caption)
          return false;
        }
      }
      else if (Fields[j].options.search(/^[A-z]*d/)!=-1) {
        // date field
        if (!this.VerifyDate(thatForm,Fields[j].options,Fields[j].caption,fldnames)) return false;
      }
      else if (Fields[j].options.search(/^[A-z]*o/)!=-1) {
        // at least one must have value
        var isEmpty = true;
        var fld;
        for (var i=0;i<fldnames.length;++i) {
          fld = thatForm.elements[fldnames[i]];
          if (fld==null) {
            if (this.NotifyFieldDoesntExist(fldnames[i])) break;
            else return false;
          }
          if (GetFldVal(fld) != '') {
            isEmpty = false;
            break;
          }
        }
        if (!fld) continue;
        if (isEmpty) {
          this.AlertAtLeastOne(thatForm.elements[fldnames[0]],Fields[j].caption);
          return false;
        }
      }
// NEW
      else if (Fields[j].options.search(/^[A-z]*[aAxX]/)!=-1) {
        var Opti;
        if (Fields[j].options.search(/^[A-z]*a/)!=-1) Opti='a';
        else if (Fields[j].options.search(/^[A-z]*A/)!=-1) Opti='A';
        else if (Fields[j].options.search(/^[A-z]*x/)!=-1) Opti='x';
        else if (Fields[j].options.search(/^[A-z]*X/)!=-1) Opti='X';
        var vl;
        var useVL = 0;
        if (Fields[j].options.search(/[aAxX]\((.+)\)/)!=-1) {
          vl = Fields[j].options.replace(/[aAxX]\((.+)\)/,'$1');
          useVL = 1;
        }
        var fld;
        fld = thatForm.elements[fldnames[0]];
        if (fld==null) {
          if (this.NotifyFieldDoesntExist(fldnames[0])) continue;
          else return false;
        }
        NeedVal = 0;
        if ((Opti == 'a') || (Opti == 'X')) {
          if ( (!useVL && this.GetFldVal(thatForm.elements[fldnames[0]]) != '') ||
              (useVL && this.GetFldVal(thatForm.elements[fldnames[0]]) == vl)
            )  NeedVal = 1;
        }
        else if ((Opti == 'A') || (Opti == 'x')) {
          if ( (!useVL && this.GetFldVal(thatForm.elements[fldnames[0]]) == '') ||
              (useVL && this.GetFldVal(thatForm.elements[fldnames[0]]) != vl)
            ) NeedVal = 1;
        }
        if (NeedVal == 1)
        {
          for (var i=1;i<fldnames.length;++i) {
            fld = thatForm.elements[fldnames[i]];
            if (fld==null) {
              if (this.NotifyFieldDoesntExist(fldnames[i])) break;
              else return false;
            }
            if ((((Opti == 'A') || (Opti == 'X')) &&
               (this.GetFldVal(thatForm.elements[fldnames[i]]) != '')) ||
            (((Opti == 'a') || (Opti == 'x')) &&
               (this.GetFldVal(thatForm.elements[fldnames[i]]) == '')))
            {
              this.AlertSome(thatForm.elements[fldnames[i]],Fields[j].caption);
              return false;
            }
          }
          if (!fld) continue;
        }
      }
      else if (Fields[j].options.search(/^[A-z]*O/)!=-1) {
        // only one must have value
        var nNotEmpty = 0;
        var fld;
        for (var i=0;i<fldnames.length;++i) {
          fld = thatForm.elements[fldnames[i]];
          if (fld==null) {
            if (this.NotifyFieldDoesntExist(fldnames[i])) break;
            else return false;
          }
          if (this.GetFldVal(thatForm.elements[fldnames[i]]) != '')
            nNotEmpty++;
        }
        if (!fld) continue;
        if (nNotEmpty!=1) {
          this.AlertSome(thatForm.elements[fldnames[0]],Fields[j].caption);
          return false;
        }
      }
    }
  }
  return true;
}

  this.CheckFieldsOrNot=
function CheckFieldsOrNot(that,Flds) {
  if (this.submittedAlready) return false;
  if (this.need2check) {
    return this.submittedAlready=this.CheckFields(that,Flds);
  }
  else return true;
}

  this.add = 
    function add(name,options,caption,condition) {
      this.fldsToValidate[this.fldsToValidate.length] = new Field(name,options,caption,condition);
    }
  this.validate = 
    function validate() {
      return this.CheckFieldsOrNot(document.forms[this.formName],this.fldsToValidate);
    }
  this.on= 
    function on() {
      this.need2check = true;
    }
  this.off= 
    function off() {
      this.need2check = false;
    }
  this.fld= 
    function fld(fld) {
      return document.forms[this.formName].elements[fld];
    }
  this.fldval= 
    function fldval(fld) {
      return this.GetFldVal(this.fld(fld));
    }
  this.copyFields= 
    function copyFields(v2) {
      this.fldsToValidate = v2.fldsToValidate;
    }
}

