Page 1 of 1

XHTML form validation library

Posted: Sat Aug 19, 2006 4:38 pm
by alex.barylski
a couple years back I wrote a small little javascript library which validated form elements based on known types and custom regex...

I'll post the source code at the end of this message

Anyways, it was quite clever I thought as it didn't require a lot of Javascript to get the thing going...

Basically, you called a function inside onsubmit() and the function traversed the FORM looking for INPUT's and such...

Each INPUT used custom attributes to actually store the validation information...

For example, if one input was an email, you might (off the top of my head) do somehting like:

Code: Select all

<input type="input" size="30" validateas="email" maxlength="255" />
The library had some built in types, email being one of them...and the javascript would test the input email against a regex and display an error if the email wasn't formatted correctly...

You could also customize the warning message or do things like, indicate whether "that" field was required or optional by specifing attrbutes such as:

Code: Select all

required="true" 
requiredmessage="Email address is a required field"
invalidmessage="Email is not a valid address, please try again"
The reason I liked this approach was that it was highly customizable and easy to integrate intop existing forms. Also the big plus for me, was the fact I used it in multiple language applications, so there was no need to provide multiple language packs with the library as that information could be provided by the client programmer...

Anyways, it was has come to my attention lately, that may (probably does) cause validation to fail...which is a bummer as it's IMHO the best approach to client side validation...but I digress...

I've also, noticed a trend and started doing it myself...moving away from client side and server side validation, instead just going with server side possibly using AJAX on the client side to validate before continuing a trip to the server...

At one time I had a neat little popup calendar control and a TEXTAREA which could restrict to certain number of characters, but I seem to have lost those... :cry:

Right now I've started development of a cross browser WYSIWYG editor, looking at TinyMCE & FckEditor and a couple commercial ones which I bought...also going on experience I've gained over the years from HtmlArea...

The it stricts me, wouldn't it be neat to have a library which you could use, which hijacked normal controls and turned them into powered-up versions? Date picker, WYSIWYG, restricted TEXTAREA w/ counter, email, etc...

By providing an input mask you could limit the type of characters one would enter, much like advanced Windows textboxes...thus not validating, by forcing a format if browser is cvapable and displying those warning messages on the sever side only if people cheat or have JS turned off...

I'd like to hear you thoughts on the implications of input filtering though...indeed it's not very common in web apps, yet...and I'm not sure it's possible in all browsers...but I think in most you can capture and cancel onkeyup/onkeydown which I think is all you need to implement input masking...

If you have any ideas on how to implement such a library...I'd be interested in hearing them...

I think something like this would be nice addition to a programmers toolkit...especially if someone took it and included it to a CSS powered FORM class which generated valid XHTML forms...

Anyways, here's the code from library I refered to:

I've removed as much comments as possible for the sake of brevity 8) Don't ask about use, as I don't plan on supporting it, just use it for ideas, etc...

Code: Select all

var _swift_validator_hash = new Array();

// Built in types
_swift_validator_hash['file'] = /^[\w\-\. ]+$/i;
_swift_validator_hash['folder'] = /^[\w\- ]+$/i;

_swift_validator_hash['alpha'] = "^[a-zA-Z0-9]+";
_swift_validator_hash['email'] = "^[-_a-zA-Z0-9]+@[-_a-zA-Z0-9]+\.(au|jp|org|net|com|ca)";
_swift_validator_hash['postal'] = "^[a-zA-Z][0-9][a-zA-Z] ?[0-9][a-zA-Z][0-9]";


function swift_form_validate(frmObj)
{
  if(frmObj.tagName.toLowerCase() != "form") return false;

  var el = frmObj.elements;

  for(i=0; i<el.length; i++){
    var elObj = el[i];

    var elTag = elObj.tagName.toLowerCase();
    var elType = elObj.type.toLowerCase();

    //
    // Initialize validation attributes
    var strValidateAs = elObj.getAttribute("validateas") || null;

    // Client supplied messages
    var strRequired = elObj.getAttribute("requiredmessage") || null;
    var strInvalid = elObj.getAttribute("invalidmessage") || null;

    var strDefault = elObj.getAttribute("default") || null;

    var bRequired = elObj.getAttribute("required") || null;

    var nMaxCheck = elObj.getAttribute("maxcheck") || null; // SELECT
    var nMinCheck = elObj.getAttribute("mincheck") || null;

    var nMaxValue = elObj.getAttribute("maxvalue") || null;
    var nMinValue = elObj.getAttribute("minvalue") || null;
    var nMaxlength = elObj.getAttribute("maxlength") || null;
    var nMinLength = elObj.getAttribute("minlength") || null;

    // Doesn't work because FILE relies on file extensions, not named hash elements
    //if(strValidateAs == null || strValidateAs == "" || strValidateAs.length == 0)
    //  strValidateAs = elObj.getAttribute("name");

    //
    // Handle each FORM tag
    switch(elTag){
      case "input":
        switch(elType){
          case "password":
          case "text":

            if(bRequired && elObj.value.length<=0){
              elObj.value = (strDefault==null?"":strDefault);
              elObj.focus();
              elObj.select();

              // Display warning about missing field info
              if(strRequired!=null)
                alert(strRequired);
              else
                alert(strValidateAs+" - Error Code: 0001");

              return false;
            }

            if(_swift_validator_hash[strValidateAs] == undefined) continue;

            var re = new RegExp(_swift_validator_hash[strValidateAs]);
            var valid = re.test(elObj.value);

            var bLength = _swift_checkLength(elObj.value.length, nMinLength, nMaxlength);
            var bValue =  _swift_checkLength(elObj.value, nMinValue, nMaxValue);
            var bChecked = (bLength && bValue);

            if((!valid && elObj.value.length>0) || !bChecked){
              elObj.focus();
              elObj.select();

              if(strInvalid!=null)
                alert(strInvalid);
              else // Only in DEBUG mode???
                alert(strValidateAs+" - Error Code: 0002");

              return false; // Prevent FORM from submitting
            }

            break;
          case "checkbox":
            var optName = el[i].name;

            var count = 0;
            while(1){
              var optNext = el[i+1].name;

              var elTmp = el[i];
              if(elTmp.checked) count++;

              if(optNext != optName) break;

              i = i + 1;
            }

            if(!_swift_checkLength(count, nMinCheck, nMaxCheck)){
              elObj.focus();

              if(strRequired!=null)
                alert(strRequired);
              else
                alert(strValidateAs+" - Error Code: 0001");

              return false; // Prevent FORM from submitting
            }

            break;
          case "radio":
            break;
          case "submit": // Fall through
          case "reset":
          case "hidden":
          case "image":
          case "button":
            break;
          case "file":
            if(bRequired && elObj.value.length<=0){
              elObj.focus();

              if(strRequired!=null)
                alert(strRequired);
              else
                alert(strValidateAs+" - Error Code: 0001");

              return false; // Prevent FORM from submitting
            }

            if(strValidateAs==null) continue;

            var file_name = elObj.value;
            var file_type = ""; // Holds the file type (jpg, bmp, etc...)

            var valid_ext = new Array();
            valid_ext = strValidateAs.split(',');

            file_type = file_name.substr(file_name.lastIndexOf(".")+1, file_name.length);

            var bValid = false; // File isn't valid by default
            for(k=0; k<valid_ext.length; k++){
              // File extension found - it's assumed safe
              if(valid_ext[k].toLowerCase() == file_type.toLowerCase()){
                bValid = true;
                break;
              }
            }

            if(!bValid){
              elObj.focus();
              elObj.select();

              // Display warning about missing field info
              if(strInvalid!=null)
                alert(strInvalid);
              else
                alert(strValidateAs+" - Error Code: 0001");

              return false; // Prevent FORM from submitting
            }

            break;
          default:
            alert(elType+": Not supported!!!");
        }
        break;
      case "select":
        if(elType != "select-one"){
          var num_options = elObj.options.length;
          var count = 0;

          for(j=0; j<num_options; j++){
            if(elObj.options[j].selected)
              count++;
          }

          if(!_swift_checkLength(count, nMinCheck, nMaxCheck)){
            elObj.focus();

            // Display warning about missing field info
            if(strRequired!=null)
              alert(strRequired);
            else
              alert(strValidateAs+" - Error Code: 0001");

            return false; // Prevent FORM from submitting
          }
        }
        else{
          if(nMinValue!=null){

            if(nMaxValue==null) nMaxValue = 4294967296;

            if(!_swift_checkLength(elObj.options[elObj.selectedIndex].value, nMinValue, nMaxValue)){
              elObj.focus();

              if(strRequired!=null)
                alert(strRequired);
              else
                alert(strValidateAs+" - Error Code: 0001");

              return false; // Prevent FORM from submitting
            }
          }
        }

        break;
      case "textarea":
        break;
    }
  }

  return true;
}

function _swift_checkLength(nLength, nMin, nMax)
{
  var gt = false; // Length still in boundry by default
  var lt = false;

  nLength = parseInt(nLength);
  nMin = parseInt(nMin);
  nMax = parseInt(nMax);

  if(nMin != null && nLength < nMin) lt = true;
  if(nMax != null && nLength > nMax) gt = true;

  return !(lt || gt);
}

Posted: Sat Aug 19, 2006 5:13 pm
by feyd
The system I'm working on right now will be doing unobtrusive upgrades to fields and other controls. Almost everyone brings expectations to the table when using an application, wherever it may be. Today we are used to very rapid feedback: digital cable, high-speed internet, satellite radio, and so forth.

So yes, I am in favor of filtering, if I understand your usage of it.

Posted: Sat Aug 19, 2006 5:25 pm
by alex.barylski
You are, as am I...

I've been obsessed with designing input filtering controls since I learned the Windows SDK well enough to do so :P

But my dad has always struggled with the idea of a control restricting him from entering a "-" in a phone number or a period, etc...

I like it when it beeps at me instead of letting me enter bogus characters only to later scream at me in the way of an alert() or server side message :P

But UI aren't for programmers they are for everyday Joes...

Would care to enlighten me as to the general over view of how your system works?

How does it support multi-langauge...?
How easy is it to get everything rolling...?

I've seen JS validation libraries in the past which required hardwiring each element to the library via some API call...this is messy and WYSIWYG editors can't change the JS, but they can manipulate attributes easily...thats why I liked this approach...

I was thinking, instead of attributes I could store messages in title attributes, possibly making use of LABEL's, etc...to substitue for attributes so code validates...

???

Posted: Sat Aug 19, 2006 5:37 pm
by feyd
My current line of thought is using the class, id or a combination thereof to determine what upgrades the controls receive. The code supports being told what fields to upgrade and how, too. These instructions are automatically generated by the page controller as you add controls to the page when turned on.