	var vForm = function() {
   var config = {
      /**
      * How to validate. If true validation is made when the 
      * blur-event is fired. If set to false the validation 
      * only takes place when the form is beeing submitted.
      *
      * @default: true
      */
      validateOnBlur : true,

      /**
      * Boolean indication if errors should be displayed upon
      * validation. If errorMessageId is set to an existing
      * HTML-container, the list of potential errors will be
      * added in the form of an unorded list. If the Id can't 
      * be found, due to lack of getElementById or because it
      * doesn't exist, an alert-message will be used.
      *
      * The errorMessageId layer can be declared with 'display:none'
      * or 'visibility:hidden' in the css to hide it when no errors 
      * exist.
      *
      * @default: true
      */
      listErrors : true,
      
      // Id for errorMessage-container. See above.
      errorMessageId : 'errorMessage',

      /**
      * CSS class names. The focusClassName is appended to any
      * existing classes already in obj.className when the object
      * is in focus. invalidClassName and validClassName is appened
      * to existing classes once the object is validated.
      */
      focusClassName : 'focus',
      invalidClassName : 'invalid',
      validClassName : 'valid',

      // Don't handle events for the following types.
      skip : ['submit', 'button', 'reset', 'hidden', 'image']
   }
   
   function addEvent(elm, evType, fn, useCapture) {
      if (elm.addEventListener) {
         elm.addEventListener(evType, fn, useCapture);
         return true;
      } else if (elm.attachEvent) {
         var r = elm.attachEvent("on"+evType, fn);
         return r;
      }
   }

   function validateForm(form) {
      var result, elm, valid = true;
      var onBlur = config.validateOnBlur;
      config.validateOnBlur = true;

      vFormError.clear();

      for (var i = 0; (elm = form.elements[i]); i++) {       
         if (typeof elm.type == 'undefined' || config.skip.contains(elm.type)) continue;
         result = elm.validate();
         valid = valid ? result : false;
         
         // Set error message
         if (!result) {
            vFormError.add(elm);
         }
      }
      config.validateOnBlur = onBlur;
      if (!valid && config.listErrors)
         vFormError.display();
      
      return valid;
   }

   function getLabels() {
      try {
         var label;
         var labels = new Array();
         var nodes = document.getElementsByTagName('label');
         for (var i = 0; (label = nodes[i]); i++) {
            if (typeof label.htmlFor == 'undefined') continue;
            labels[label.htmlFor] = label;
         }
         return labels;
      } catch (e) {
         return new Array();
      }
   }

   function vForm() {
      var form, elm, labels; //, obj = this;

      // Setup errorhandler
      //this.error = new vFormError();
      
      for (var i = 0; (form = document.forms[i]); i++) {      
         
         // Keep other calls to onsubmit and weight them bitwise
         // with the validation result. Experimental!
         var oldsubmit = form.onsubmit;
         if (typeof form.onsubmit != 'function') {
            form.onsubmit = function() {
               return vForm.validateForm(this);
            }
         } else {
            form.onsubmit = function() {
               return oldsubmit() & vForm.validateForm(this);
            }
         }

         addEvent(form, 'reset', function() {
            var input;
            for (var i = 0; (input = this.elements[i]); i++) {
               if (typeof input.reset == 'function')
                  input.reset(true);
            }
            if (typeof vFormError == 'object') {
               vFormError.clear();
            }
         });

         // Fetch all labels to be able to match them against elm's id.
         labels = getLabels();

         for (var j = 0; (elm = form.elements[j]); j++) {
            // Don't validate types in config.skip
            if (typeof elm.type == 'undefined' || config.skip.contains(elm.type)) continue;
            elm.validate = validateElement;
            elm.reset = resetElement;
            if (typeof labels[elm.id] != 'undefined')
               elm.label = labels[elm.id];
            
            // Don't validate onBlur when there is a multiple choice, 
            // eg. Radio or Checkbox types.
            if (elm.type == 'checkbox' || elm.type == 'radio') {
               addEvent(elm, 'click', elementBlur);
            } else {
               if (elm.type.substr(0,6) != 'change') {
                  addEvent(elm, 'focus', elementFocus);
               }
               addEvent(elm, 'blur', elementBlur);
            }
         }
      }
   }

   /**
   * The validation mechanism. Each field is evaluated with this function.
   * @return true if the field is valid.
   */
   function validateElement(showError) {
      var name, rules;
      
      this.reset(true);
      var rules = this.name.split(':');
      var name = rules[0];

      try {
         rules = rules[1].split('|');
      } catch (e) { // No rules to apply
         return true;
      }

      var valid = true; // Assume valid - prove the opposite
      var optional = false;

      showError = (typeof showError == 'undefined') ? true : showError;

      var value = getElementValue(this);

      if (!config.validateOnBlur) return true;

      // If value is empty and optional, it shall always be true.
      if (rules.contains('optional')) {
         optional = value.empty();
      }

      // obj.empty() applies to both String and Array
      if (rules.contains('required')) {
         valid = !value.empty();
      }
      
      if (typeof value != 'string') {
         // Array specific validation //
         var minChoice = /min\(([0-9]+)\)\|?/i.exec(this.name);
         if (minChoice != null) {
            valid &= minChoice[1] <= value.length;
         }
         
         var maxChoice = /max\(([0-9]+)\)\|?/i.exec(this.name);
         if (maxChoice != null) {
            valid &= maxChoice[1] >= value.length;
         }
      } else if (typeof value == 'string') {   
         // String specific validation //
         if (rules.contains('alpha')) {
            valid &= /^(\D)+$/.test(value);
         }

         if (rules.contains('numeric')) {
            valid &= /^[0-9]+$/.test(value);
         }

         if (rules.contains('url')) {
            var result = value.url();
            valid &= result ? valid : false;
            if (typeof result == 'string') {
               this.value = result;
               value = result;
            }
         }

         if (rules.contains('email')) {
            valid &= /^[a-z0-9\.\-_\+]+@[a-z0-9\-_]+\.([a-z0-9\-_]+\.)*?[a-z]+$/i.test(value);
         }

         var equal = /equal\(([a-z0-9]+)\)\|?/i.exec(this.name);
         if (equal != null) {
            //Loop through the form's elements to find the field to compare against
            var field, re;
            for (var i = 0; (field = this.form.elements[i]); i++) {
               re = new RegExp('^' + equal[1] + '\:');
               if (typeof field.name != 'undefined' && re.test(field.name))
                  break;
            }
            valid &= (value == getElementValue(field))
         }

      }

      var callback = /callback\(([a-z0-9]+)\)\|?/i.exec(this.name); // Find callback function
      if (callback != null) {
         // Try to evaluate callback function.
         try {
            var result = eval(callback[1])(this);
            valid &= result ? valid : false;
            if (typeof result == 'string') {
               this.value = result;
               value = result;
            }
         } catch (e) {}
      }

      //Fix for optional fields
      valid = optional ? true : valid;

      if (valid && !value.empty()) {
         // Element is a group member
         if (typeof this.form[this.name].length != 'undefined') {
            var member;
            for (var i = 0; (member = this.form[this.name][i]); i++) {
               try {
                  member.reset(true);
               } catch (e) {} // Fires when member is an option.
            }
         } else {
            // No siblings
            this.className = this.className.add(this, config.validClassName);
            if (typeof this.label != 'undefined')
               
               this.label.className = this.label.className.add(this, config.validClassName);
         }
      } else if (!valid && showError) {
        // this.className = this.className.remove(this, config.validClassName);
         this.className = this.className.add(this, config.invalidClassName);
         try {
           // this.label.className = this.label.className.remove(this.label, config.validClassName);
            this.label.className = this.label.className.add(this.label, config.invalidClassName);
            this.label.title = this.form[name + ':error'].value;
            this.title = this.form[name + ':error'].value;
         } catch (e) {}
      }
      return valid;
   }

   /**
   * Get element value.
   * @param elm the element object.
   * @return a string when only one value is found. If multiple choices
   * are available an array of values is returned. 
   */
   function getElementValue(elm) {
      var element, choice, group, values;   
      switch (elm.type) {
         case 'text':
         case 'textarea':
         case 'password':
         case 'file':
            return elm.value;
         case 'radio':
         case 'select-one':
            group = elm.form[elm.name];
            if (typeof group.length == 'undefined')
               return elm.value;
            choice = (elm.type == 'radio') ? 'checked' : 'selected';
            for (var i = 0; (element = group[i]); i++) {
               if (element[choice])
                  return element.value;
            }
            break;
         case 'select-multiple':
         case 'checkbox':
            group = elm.form[elm.name];
            if (typeof group.length == 'undefined')
               return elm.value;
            
            values = new Array();
            choice = (elm.type == 'checkbox') ? 'checked' : 'selected';
            for (var i = 0; (element = group[i]); i++) {
               if (element[choice])
                  values[values.length] = element.value;
            }
            return values;
            break;
      }
      return "";
   }


   /**
   * Reset field error properties.
   * @param allProps true if title and label shall be reset.
   */
   function resetElement(allProps) {
      this.className = this.className.remove(config.focusClassName);
      this.className = this.className.remove(config.invalidClassName);
      this.className = this.className.remove(config.validClassName);
      if (allProps) {
         this.title = '';
         if (typeof this.label != 'undefined') {
            this.label.className = this.label.className.remove(config.invalidClassName);
            this.label.className = this.label.className.remove(config.validClassName);
            this.label.className = this.label.className.remove(config.focusClassName);
            this.label.title = '';
         }
      }
   }

   function elementFocus(e) {
      var elm = getElementFromEvent(e);
      if (!elm) return;
      elm.reset(false);
      elm.className = elm.className.add(elm, config.focusClassName);
   }

   /**
   * Blur event. Trigger the validation of the element.
   * @param e the event.
   */
   function elementBlur(e) {
      var elm = getElementFromEvent(e);
      if (!elm) return;
      if (elm.tagName.toLowerCase() == 'label') {
         try {
            elm = document.getElementById(elm.htmlFor);
         } catch (e) {
            alert(e);
            return;
         }
      }

      // Prevent error flickering when multiple checkboxes are checked and unchecked.
      if (elm.type == 'checkbox' || elm.type == 'radio' || elm.type.substr(0,6) == 'select')
         elm.validate(false);
      else
         elm.validate();
   }

   /**
   * Get the element which trigger an event.
   * @param e the event.
   * @return the element. On failure undefined.
   */
   function getElementFromEvent(e) {
      var elm = undefined;
      if (!e) e = window.event;
      if (e.target) elm = e.target;
      else if (e.srcElement) elm = e.srcElement;
      if (elm.nodeType == 3) elm = elm.parentNode;
      return elm;
   }

   //Public properties and methods
   vForm.validateForm = validateForm;
   vForm.config = config;

   var oldonload = window.onload;
   if (typeof window.onload != 'function') {
      window.onload = vForm;
   } else {
      window.onload = function() {
         oldonload();
         vForm();
      }
   }
   
   return vForm;
}();


var vFormError = function() {
   function vFormError() {}; //Returned obj...
   
   var message = '';
   var elements = new Array();

   /**
   * Add an error message. Append to the end of the list
   * of messages.
   * @param message the error message string.
   *        id      the id the invalid element.
   * @return true if DOM is supported
   */
   vFormError.add = function(elm) {   
      var msg, div, ul, li, label;

      // Only display one error message for each group of elements.
      // The only disadvantage is that the label will point to the first
      // element in the group.
      if (elements.contains(elm.name)) return true;

      try {
         msg = elm.form[elm.name.split(':')[0] + ':error'].value;
      } catch (e) {
         if (e instanceof TypeError) {
            alert("Error Message:\n\nThe error message field '" + elm.name.split(':')[0] + 
               "_error' is not defined for '" + elm.name.split(':')[0] + "'.");
            return false;
         }
      }
      // Backup if DOM fails.
      message += msg + '\n';

      // Add to elements array.
      elements[elements.length] = elm.name;
      
      try {
         div = document.getElementById(vForm.config.errorMessageId);
         ul = div.getElementsByTagName('ul')[0];

         // Check if <ul> already exists. If not, create it.
         if (!ul) {
            ul = document.createElement('ul');
            div.appendChild(ul);
         }

         // Create a <li> with a <label> inside.
         li = document.createElement('li');
         label = document.createElement('label');
         
         label.setAttribute('for', elm.id);
         label.htmlFor = elm.id; // This works in IE.
         
         label.appendChild(document.createTextNode(msg));
         li.appendChild(label);
         ul.appendChild(li);
         return true;
      } catch (e) {
         return false;
      }
   }

   /**
   * Display error messages. If no error message container exists,
   * alert() the message.
   */
   vFormError.display = function() {
      try {
         var div = document.getElementById(vForm.config.errorMessageId);
         if (div.style.display == 0 || div.style.display == 'none') {
            div.style.display = 'block';
         } else if (div.style.visibility == 0 || div.style.visibility == 'hidden') {
            div.style.visibility = 'visible';
         }
      } catch (e) {
         // div or DOM non-existing.
         alert(message);
      }
   }

   /**
   * Clear errors. Remove ul from the error message container.
   */
   vFormError.clear = function() {
      message = '';
      elements = new Array();
      try {
         var div = document.getElementById(vForm.config.errorMessageId);
         var ul = div.getElementsByTagName('ul')[0];
         if (ul) div.removeChild(ul);
         if (div.style.visibility != 0 && div.style.visibility == 'visible') {
            div.style.visibility = 'hidden';
         } else {
            div.style.display = 'none';
         }
      } catch (e) {}
   }

   return vFormError;
}();



// Useful Prototypes //

/**
* Trim whitespace characters. Left and right trim.
* @return a trimmed version of the string.
*/
String.prototype.trim = function() {
   return this.replace(/^\s+|\s+$/g, "");
}

/**
* Determine if the string is empty.
* @return true if empty.
*/
String.prototype.empty = function() {
   return (this.trim().length <= 0) ? true : false;
}

/**
* Validate URL. Make sure the URL contains http:// 
* and has a valid structure.
* @return false   if string isn't a valid URL
*         String  modified string if valid URL
*/
String.prototype.url = function() {
   var str = this;
   if (!/^https?:\/\//i.test(str)) {
      str = 'http://' + str;
   }           
   return (/^https?:\/\/((?:[-a-z0-9]+\.)*)[-a-z0-9]+\.[a-z]{2,4}(\/[-a-z0-9_:@&?=+,.!\/~*%$]*)?$/i.test(str)) ? str : false;
}

/**
* Find the occurance of a string. The first position is
* bypassed since it contains the element name in most cases.
* @param needle string to search.
* @return true if needle is found.
*/
Array.prototype.contains = function(needle, start) {   
   if (typeof start == 'undefined')
      start = 0;
   for(var i = start; i < this.length; i++) {
      if (this[i].toLowerCase() == needle.toLowerCase()) {
         return true;
      }
   }
   return false;
}

Array.prototype.empty = function() {
   return (this.length == 0) ? true : false;
}

/**
* Remove a CSS class.
* @param className a string of class names.
* @return a new string with className removed.
*/
String.prototype.remove = function(className) {
   re = new RegExp(className);
   return this.replace(re, '').trim();
}

/**
* Add a CSS class.
* @param elm the element object.
*        className the className string.
*/
String.prototype.add = function(elm, className) {
   if (elm.type != 'checkbox' && elm.type != 'radio')
      return this.concat(' ' + className).trim();
   else
      return this;
}
