/*
Validator - Javascript form validation - http://www.atomicmotion.com

example:
var val = new validator('form');
val.add('userid','req','Please enter your Username.');
val.add('userid','maxlen=5');
val.remove( "userid" ); // removes all elements nammed "userid" from the validation

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: required || req
desc: The field should not be empty 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: checked || chk
desc: has a checkbox been checked.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: maxlen || maxlength = <num>
desc: the maximun character length allowed. ex. "maxlen=25"  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: minlen || minlength = <num>
desc: the minimum amount of characters allowed. ex. "minlen=5"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: alphanumeric || alnum
desc: only allows alphabetic or numeric characters. ex. "aBc123", not " BAD !"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: num || numeric
desc: only allows numeric characters. ex. "12345", not " 123bad"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: alpha || alphabetic
desc: only allows alphabetic characters. ex. "aBc", not " 123!?"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: email
desc: only allows a valid email address. ex. "me@me.com", not "a@.com"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: url || url = <http||https||ftp>
desc: only allows a valid url address. command appends corresponding header. ex. "www.google.com" becomes "http://www.google.com"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: phone
desc: only allows a valid phone number. ex. "555-5555", not "abc-1234"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: credit = <field> || <visa||mc||amex||dinner>
desc: only allows a valid credit card number. credit card type is set by command or pulled from a specified field.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: postal || postal = <zip||postal||cn||us>
desc: only allows a valid postal or zip codes. ex. "44240", "44240-5555", "T2P 3C7"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: lt || lessthan = <num>
desc: numeric value must be less than command value. ex. "lt=1000"  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: gt || greaterthan = <num>
desc: numeric value must be greater than command value. ex. "gt=100"  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: regexp || reg = <regex>
desc: performs a specified regular expression on field value. ex. "regexp=^[A-Za-z]{1,20}$" allow up to 20 alphabetic characters.  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: dontselect || nosel = <index>
desc: the selected index combo cannot be the one specified. ex. "dontselect=0"  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call: custom = <function>
desc: performs a custom validation function on specified field. ex. "custom=func"  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

function validator(frm) {
  this.errors = false;
  this.fields = new Array();
  this.before = null;
  this.after = null;
  this.form=document.forms[frm]||document.getElementById(frm);
  this.form.validator = this;

  this.error = function(msg, el) {
    this.errors = true;
    if (el) msg += ' ['+el+']'; 
    alert('Validator: '+msg);
    return false;
  }
  
  this.getEl = function(el) {
    return this.form.elements[el]||document.getElementById(el);
  }

  if(!this.form) return this.error('Could not locate form',frm);
  //if(this.form.onsubmit) this.form.old_onsubmit = function(){alert('test'); }; // this.form.onsubmit;
  this.form.onsubmit = function() {
    this.validator.errors = false;
    try {
      if (this.validator.before)
        if (!this.validator.before(this)) return false;
      if(this.validator.errors) return false;
      for(var i=0;i<this.validator.fields.length;i++) {
        var item = this.validator.fields[i];
        item.validator = this.validator;
        item.el = this.validator.getEl(item.name);
        if (item.el && item.el.length>1 && !item.el.selectedIndex) item.el = item.el[0];
        if(!item.el) return this.validator.error('Field not available.',item.name);
        item.el.setAttribute('autocomplete','off'); 
        if(!this.validator.validate(item)) {
          if (this.validator.errors) return false;
          if (!item.error) item.error = '{0}: Field is not valid';
            alert(item.error.replace('{0}',item.name));
					if(item.el.type){
					  if(item.el.type.toLowerCase()=='hidden') {
              return false;
            }
					}
          if(typeof item.el.focus=='function') item.el.focus();
          if(typeof item.el.select=='function') item.el.select();
          return false;
        }
      }
      if(this.validator.errors) return false;
      if (this.validator.after)
        if (!this.validator.after(this)) return false;
      if(this.validator.errors) return false;
      if (this.old_onsubmit) return this.old_onsubmit();
    } catch(e) {
      return this.validator.error('An error occurred while validating this form. Please try agian later.\n'+e.description);
    }
    return true;
  };
  
  this.add = function(item,cmd,msg) {
    if(!this.form) this.error('Please set your form');
    this.fields[this.fields.length] = { name: item, cmd: cmd, msg: msg }
  };

  this.remove = function(item) {
    if(!this.form) this.error("Please set your form");
    for( var i = 0; i < this.fields.length; i++ ) {
      if( this.fields[i].name == item ) {
        this.fields.splice(i,1);
        i = 0;
      }
    }
  };
  
  this.clear = function() {
    this.fields.length = 0;
  };
  
  this.validate = function(item) {
    if (!item||typeof(item)!='object') return false;
    if (!item.el) return false;
    item.error = item.msg;
    var value = (item.el.value||'').toLowerCase();
    var cmd = (item.cmd||'').toLowerCase();
    var int_equal = cmd.indexOf('=');
    if (int_equal>=0) {
     var cmdvalue = cmd.substr(int_equal+1); 
     cmd = cmd.substring(0,int_equal); 
     if(!cmdvalue) return this.error('Command value has not been set',item.name);
    }
    switch(cmd) {
      case 'req': case 'required': {
        if (!item.error)item.error='{0}: This field is required';
        var el = item.validator.getEl(item.name);
        if(el.selectedIndex){
          return (item.el.selectedIndex!=0);
        }else if (el.length>1){
          for (var i=0;i<el.length;i++) {
            if (el[i].checked) return true;
          }
        }else{
          return (value.length>0);
        }
        break;
      }
      case 'equals': case 'is': {
        if (!item.error)item.error='{0}: Please verify the entered value';
        return (value==cmdvalue);
        break;
      }
      case 'checked': case 'chk': {
        if (!item.error) item.error='{0}: Must be checked before continuing';
        return item.el.checked;
        break;
      }
      case 'maxlength': case 'maxlen': {
        if (value.length==0) return true;
        if (!item.error) item.error='{0}: Please enter at most '+cmdvalue+' characters';
        item.error+='\n[Current length = '+value.length+' ]';
        cmdvalue = parseInt(cmdvalue)||0;
        return (value.length<=cmdvalue);
        break;
      }
      case 'minlength': case 'minlen': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter at least '+cmdvalue+' characters';
        item.error+='\n[Current length = '+value.length+' ]';
        cmdvalue = parseInt(cmdvalue)||0;
        b_return (value.length>=cmdvalue);
        break;
      }
      case 'alnum': case 'alphanumeric': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter only alpha-numeric characters';
        return (value.match(/[^A-Za-z0-9]/));
        break;
      }
      case 'num': case 'numeric': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter only numeric characters';
        return (value.match(/[^0-9]/));
        break;
      }
      case 'alpha': case 'alphabetic': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter only alphabetic characters';
        return (value.match(/[^A-Za-z]/));
        break;
      }
      case 'alnumhyphen': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Only enter the allowed characters of A-Z,a-z,0-9,- and _';
        return (value.match(/[^A-Za-z0-9\-_]/));
        break;
      }
      case 'url': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter a valid web address';
        if (cmdvalue) {
          if (value.indexOf(cmdvalue+'://')!=0) {
            value = cmdvalue+'://'+value;
            item.el.value = value;
          }
        }
        return (value.match(/http:\/\/+(www\.)?[a-z0-9\-\.]{3,}\.[a-z]{2,4}$/));
        break;
      }
      case 'phone': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter a valid phone number';
        return (value.match(/^(\(?\d\d\d\)?)?( |-|\.)?\d\d\d( |-|\.)?\d{4,4}(( |-|\.)?[ext\.]+ ?\d+)?$/));
        break;
      }
      case 'zip': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter a valid zip code';
        return (value.match(/^((\d{5}-\d{4})|(\d{5}))?$/));
        break;
      }
      case 'postal': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Please enter a valid postal code number';
        if(cmdvalue=='zip'||cmdvalue=='us'||cmdvalue=='usa') {
          var reg = /^((\d{5}-\d{4})|(\d{5}))?$/;
        } else if (cmdvalue=='postal'||cmdvalue=='cn'||cmdvalue=='can') {
          var reg = /^([AaBbCcEeGgHhJjKkLlMmNnPpRrSsTtVvXxYy]\d[A-Za-z]\s?\d[A-Za-z]\d)$/;
        } else {
          var reg = /^((\d{5}-\d{4})|(\d{5})|([AaBbCcEeGgHhJjKkLlMmNnPpRrSsTtVvXxYy]\d[A-Za-z]\s?\d[A-Za-z]\d))$/;
        }
        return (value.match(reg));
        break;
      }
      case 'lt': case 'lessthan': {
        if(isNaN(value)) return this.error('Please enter a number',item.name);
        if(!item.error)item.error='{0}: Please enter a number less than '+cmdvalue;
        return (eval(value)<eval(cmdvalue));
        break;
      }
      case 'gt': case 'greaterthan': {
        if(isNaN(value)) return this.error('Please enter a number',item.name);
        if(!item.error)item.error='{0}: Please enter a number greater than '+cmdvalue;
        return (eval(value)>eval(cmdvalue));
        break;
      }
      case 'regexp': case 'regex': case 'reg': {
        if(!item.error)item.error='{0}: Please verify your entry';
        return (value.match(cmdvalue));
        break;
      }
      case 'dontselect': case 'nosel': {
        if(item.el.selectedIndex==null) return this.error('Cannot validate a non-select item',item.name);
        if(!item.error)item.error='{0}: Please select an option';
        cmdvalue = parseInt(cmdvalue)||0;
        return (item.el.selectedIndex!=cmdvalue);
        break;
      }
      case 'credit': {
        if (value.length==0) return true;
        var type = cmdvalue;
        if(type!='visa'&&type!='mc'&&type!='disc'&&type!='amex'&&type!='diners') {
          type = (item.el.form[cmdvalue].value||'').toLowerCase();
        }
        if (!type) return this.error('No credit card type specified', item.name);
        if(!item.error)item.error='{0}: Invalid credit card'; 
        if (type=="visa") var reg=/^4\d{3}[-, ,]?\d{4}[-, ,]?\d{4}[-, ,]?\d{4}$/;
        else if (type == "mc") var reg=/^5[1-5]\d{2}[-, ,]?\d{4}[-, ,]?\d{4}[-, ,]?\d{4}$/;
        else if (type == "disc") var reg=/^6011[-, ,]?\d{4}[-, ,]?\d{4}[-, ,]?\d{4}$/;
        else if (type == "amex") var reg=/^3[4,7]\d{13}$/;
        else if (type == "diners") var reg=/^3[0,6,8]\d{12}$/;
        if (!reg) return this.error('Validation expression not available for '+type, item.name);
        if (reg.test(value)) {
          var ccnum = value.split('-').join('');
          var checksum = 0;
          for (var i=(2-(ccnum.length % 2)); i<=ccnum.length; i+=2) {
            checksum += parseInt(ccnum.charAt(i-1));
          }
          for (var i=(ccnum.length % 2) + 1; i<ccnum.length; i+=2) {
            var digit = parseInt(ccnum.charAt(i-1)) * 2;
            if (digit < 10) { checksum += digit; } else { checksum += (digit-9); }
          }
          return ((checksum % 10) == 0);
        }
        break;
      }
      case 'email': {
        if (value.length==0) return true;
        if(!item.error)item.error='{0}: Invalid email address';
        return value.match(/^.+@[^\.].*\.[a-z]{2,}$/);
        break;
      }
      case 'custom': {
        if (!cmdvalue) return this.error('Please specify custom function', item.name);
	   if (cmdvalue) {
          cmdvalue = eval(cmdvalue);
          if (cmdvalue) return cmdvalue(item);
         }
        break;
      }
      default: {
        return false;
      }
    };
    return false;
  };
  
};