// This is an all-purpose form validator, written as a Javascript class.
//
// If I were going to turn this into a formal reusable component, I might make the handlers
// into subclasses, and the addRule could accept a variable number of parameters. I avoid
// the impulse at this point for flow-tracing and readability (i.e.: OOP's special variety
// of "spaghetti code").
//
// Remember that a Javascript validator is only a courtesy for the end-user, not the programmer,
// and anything that this does must also be duplicated server-side.
//
// PARAMETERS:
//   "formname": The "name" attribute of the form element.
//   "responseelement": The "id" attribute of a block element somewhere on the page which will
//                      display error messages.
function formchk( formname, responseelement ) {
  // Closure
  var self=this;
  
  // Private attributes
  
    // This array must be initialized on a per-form basis using the 
    // public function "addRule()".
    var rules=new Array();
  
  // Public attributes.
    this.responsePrefix='<p><b>There are problems with the form.</b></p>';
    this.responseSuffix='';
  
  // init()
  //
  // Simple INIT function. Creates public attributes out of the parameters and creates an anonymous
  // function for the form to call the public "validate()" function using the closure defined
  // above.
  //
  // This is TWO ASSIGNMENTS which must succeed for the handler to be activated.
  // Don't "fix" it by changing to two equality comparisons.
  if (this.form=document.forms[formname]) {
    if (this.el=document.getElementById(responseelement)) {
      this.form.onsubmit=function() { return self.validate(); };
    } else {
      alert('Form checker not created--There is no element with the id '+responseelement);
    }
  } else {
    alert('Form checker not created--There is no form with the name '+formname);
  }
  
  // Use this member function to add rules. Some rudimentary sanity-checking for
  // the benefit of the programmer goes here.
  //
  // PREREQUISITES:
  //   Any form element to be checked must have its id, title and type attribute (when relevant) set.
  //
  // PARAMETERS:
  //   "element": The ID of the element to be checked.
  //   "condition": a element-type-specific condition that must be satisfied if the element is entered.
  //   "required": a boolean value determining whether the element is required or not.
  this.addRule=function (element,condition,required) {
    var el = document.getElementById(element);
    var newrule = rules.length;
    
    if (!el) {
      alert("There is no form element with the id "+element+".\nThis rule will not be used.");
      return false;
    }
    
    var fitemtype=el.tagName.toLowerCase();
    if (fitemtype=='input') {
      fitemtype=el.type.toLowerCase();
    }
    
    // Set up structures that the various handlers will need.
    switch (fitemtype) {
    
      // Text elements are matched against a regular expression.
      case 'text':
      case 'textarea':
      case 'hidden':
        rules[newrule] = new Object();
        rules[newrule].type = fitemtype;
        rules[newrule].el = el;
        rules[newrule].required = required;
        rules[newrule].RE = new RegExp(condition);
        break;
      
      // Select elements are either required or not
      case 'select':
        rules[newrule] = new Object();
        rules[newrule].type = fitemtype;
        rules[newrule].el = el;
        rules[newrule].required = required;
        break;
      
      // The only client-side password validation one would ever use would be to
      // see if TWO password elements were the same.
      case 'password':
        var el2 = document.getElementById(condition);
        if (!el2) {
          alert("There is no form element with the id "+condition+".\nThis rule will not be used.");
          return false;
        }
        rules[newrule] = new Object();
        rules[newrule].type = fitemtype;
        rules[newrule].el = el;
        rules[newrule].el2 = el2;
        rules[newrule].required = required;
        break;
      
      // This is really kind of a kludge. This satisfies a case where a number
      // of radio buttons may be set, a default must not be pre-selected, but
      // the user may not continue without selecting one. 
      case 'fieldset':
        rules[newrule] = new Object();
        rules[newrule].type = 'radio';
        rules[newrule].el = el;
        rules[newrule].els = new Array();
        rules[newrule].required = required;
        var els = el.getElementsByTagName('INPUT');
        for (var i=0; i<els.length; i++) {
          if ((els[i].getAttribute('type').toLowerCase() == 'radio') && (els[i].getAttribute('name').toLowerCase() == element)) {
            rules[newrule].els[rules[newrule].els.length] = els[i];
          }
        }
        break;

      // No handler?  Write a new one!
      default:
        alert("There is no handler for "+fitemtype+" elements.\nThe "+element+" rule will not be used.");
        return false;
    }
    return true;
  };
  
  // This is the form validator function.
  this.validate=function() {
    var response=self.responsePrefix+'<ul>';
    var allclear=true;
    
    for (var i=0; i<rules.length; i++) {
    
      // Whenever a form item is disabled, we don't care about it. Wrap it all in a test.
      if (!rules[i].el.disabled) {
        
        switch (rules[i].type) {
          case 'text':
          case 'hidden':
          case 'textarea':
            if ((rules[i].required) && (rules[i].el.value=="")) {
              allclear=false;
              response+="<li>The value for "+rules[i].el.getAttribute('title')+" is required, but is missing.</li>\n";
              rules[i].el.style.backgroundColor="#ffcccc";
            } else if (rules[i].el.value!=="") {
              if (!rules[i].RE.test(rules[i].el.value)) {
                allclear=false;
                response+="<li>The value for "+rules[i].el.getAttribute('title')+" has errors in it.</li>\n";
                rules[i].el.style.backgroundColor="#ffcccc";
              } else {
                rules[i].el.style.backgroundColor="";
              }
            }
            break;
          
          case 'select':
            if ((rules[i].required) && (rules[i].el.selectedIndex==0)) {
              allclear=false;
              response+="<li>The value for "+rules[i].el.getAttribute('title')+" is required, but is missing.</li>\n";
              rules[i].el.style.backgroundColor="#ffcccc";
            } else {
              rules[i].el.style.backgroundColor="";
            }
            break;
            
          case 'password':
            if ((rules[i].required) && (rules[i].el.value=="")) {
              allclear=false;
              response+="<li>The value for "+rules[i].el.getAttribute('title')+" is required, but is missing.</li>\n";
              rules[i].el.style.backgroundColor="#ffcccc";
              rules[i].el2.style.backgroundColor="#ffcccc";
            } else {
              if (rules[i].el.value != rules[i].el2.value) {
                allclear=false;
                response+="<li>The value for "+rules[i].el.getAttribute('title')+" does not match "+rules[i].el2.getAttribute('title')+".</li>\n";
                rules[i].el.style.backgroundColor="#ffcccc";
                rules[i].el2.style.backgroundColor="#ffcccc";
              } else {
                rules[i].el.style.backgroundColor="";
                rules[i].el2.style.backgroundColor="";
              }
            }
            break;
            
          case 'radio':
            // The rule is used only to require a selection.
            for (var j=0;j<rules[i].els.length;j++) {
              if (rules[i].els[j].checked)
                break;
            }
            if (j==rules[i].els.length) {
              allclear=false;
              response+="<li>The value for "+rules[i].el.getAttribute('title')+" is required, but is missing.</li>"
            }
        }
      }
    }
    
    response += "</ul>"+self.responseSuffix+"\n";
    
    if (allclear)
      self.el.innerHTML='';
    else
      self.el.innerHTML=response;
      
    return allclear;
  };
} 

