// Form Validator v.1.1
//
// DESCRIPTION:
//    These JavaScript functions are used to validate form data before
//    it is submitted to a CGI program.
//
// DEPENDENCIES:
//    None
//
// USAGE:
//    This function can be called using the onSubmit() event handler.
//    To use this code, place the following line in the <head> section
//    of your HTML document:
//
//        <script src="validator.js"></script>
//
//    For each element that you want to make required, list them as:
//
//        this.elementname.required = true;
//
//    inside of the onSubmit quotes.  Other elements that aren't
//    required do not have to be listed here (it is assumed that
//    they are not required).  Text boxes and Textareas can have
//    property called 'datatype' that can be defined:
//
//        this.textboxname.datatype = 'sometype';
//
//    The available datatypes that can be checked are the following:
//
//        string, date, zip, phone, email, numeric, and url
//          
//    Additionally, the datatypes can be defined further according to the
//    attributes you wish them to have.  Listed below are the datatypes with
//    their possible attribute definitions:
//          
//        DATATYPE  ATTRIBUTES
//        --------  -------------------------------------------------
//        string    nospace (does not allow spaces),
//                  alphanum (only alpha-numeric chars [A-z,0-9]),
//                  alpha (only alphabetic chars [A-z])
//        numeric   min (specifies the minimum a number can be), 
//                  max (specifies the maximum a number can be)
//        url       http (specifies only http links)
//
//    These can be used individually or jointly.  Here are some examples of how
//    to defined attributes for a datatype:
//
//        this.firstname.attrib = 'alpha,nospace';
//        this.year.attrib = 'min=1900';
//        
//    If using more than one attribute, they must be separated by a comma.
//
//    After all the required elements are defined, the function call:
//
//        return verify(this);
//
//    must precede the ending quotes for the onSubmit event handler.
//    See the example below for sample formatting and use:
//
//        <form action="myscript.pl" method="post" onSubmit="
//              this.event.required = true;
//              this.startdate.required = true;
//              this.startdate.datatype = 'date';
//              this.attending.required = true;
//              return verify(this);
//        ">
//        <input type="text" name="event" value=""><br>
//        <input type="text" name="startdate" value="">
//        <input type="text" name="enddate" value=""><br>
//        <input type="radio" name="attending" value="yes">
//        <input type="radio" name="attending" value="no"><br>
//        <input type="submit" value="Sign Up!"><input type="reset">
//        </form>
//
//    * NEW FEATURE IN VERSION 1.1!!
//        A new feature has just been added to make the error message more
//        user friendly.  Instead of the script defaulting always to the
//        field name (as in the earlier version of this script), you may
//        specify an 'alias' for each field.  This alias will be displayed
//        instead of the field name.  Example:
//
//            this.firstname.alias = 'First Name';
//
//        When the error message is presented to the user, the field will
//        be referred to as 'First Name' instead of the actual field name
//        'firstname'.
//        
// ADDITIONAL NOTES:
//    The usage of this script as noted above will not work for forms
//    submitted using the call "document.formname.submit()".  Instead,
//    you may specify the required fields of a form in the <head> section
//    of your file in an initialization function.  Example:
//
//        <script language="javascript">
//        function init() {
//            var f = document.formname
//            f.field1.required = true;
//            f.field2.required = true;
//               ...
//        }
//        </script>
//
//    Use the "onLoad" event handler in the body tag of the document to
//    initialize your form's required fields:
//
//        <body onLoad="init()">
//
//    This will ensure that the form is loaded before you try to set properties
//    for fields that don't exists yet.  Last, call the verify script before 
//    you use the submit() command:
//
//    Example 1 - A function:
//
//        function checkthis() {
//            if (verify(document.formname)) {
//                document.formname.submit();
//            }
//            return;
//        }
//
//    Example 2 - A button:
//
//        <input type="button" value="Click Me!" onClick="
//          (verify(document.formname)?document.formname.submit():false)">
//
//    Although you can use this method, it does not seem to work 100% in the
//    test cases I prepared.  I'm working on trying to figure out the problems
//    that arise from using it in this manner.

// This is the function that performs form verification.  It will be invoked
// from the onSubmit() event handler.  The handler should return whatever
// value this function returns.
function verify(f) {
    var msg = "";
    var empty_fields = "";
    var unselected_fields = "";
    var errors = "";
    
    // Loop through the elements of the form, looking for all elements that
    // have a "required" field defined.  If the element has a "required" and
    // "datatype" field defined, then verify that the datatype is correct.
    for (var i = 0; i < f.length; i++) {
        var e = f.elements[i];
            
        if (e.required) {
            if ((e.type == "text") || (e.type == "textarea")) {
                if (!(isblank(e.value))) {
                    if ((e.datatype == null) || (e.datatype == "string")) {
                        var attribs = (e.attrib?e.attrib:"");
                        if (attribs.match(/nospace/)) {
                            if (isspaced(e.value)) {
                                errors += "- The field " + getname(e) + " must not contain spaces.\n"
                            }
                        }
                        if (attribs.match(/alphanum/)) {
                            if (!(isalphanum(e.value))) {
                                errors += "- The field " + getname(e) + " must contain only alphanumeric characters (A-Z, a-z, or 0-9).\n"
                            }
                        } else if (attribs.match(/alpha/)) {
                            if (!(isalpha(e.value))) {
                                errors += "- The field " + getname(e) + " must contain only alphabetic characters (A-Z or a-z).\n"
                            }
                        }
                    } else if (e.datatype == "date") {
                        if (!(isdate(e.value))) {
                            errors += "- The field " + getname(e) + " must be a valid date (m/d/yy or m/d/yyyy).\n"
                        }
                    } else if (e.datatype == "zip") {
                        if (!(iszip(e.value))) {
                            errors += "- The field " + getname(e) + " must be a zip/postal code (xxxxx[-xxxx]).\n"
                        }
                    } else if (e.datatype == "phone") {
                        if (!(isphone(e.value))) {
                            errors += "- The field " + getname(e) + " must be a phone number (xxx-xxx-xxxx).\n"
                        }
                    } else if (e.datatype == "email") {
                        if (!(isemail(e.value))) {
                            errors += "- The field " + getname(e) + " must be an email address (name@internet.address).\n"
                        }
                    } else if (e.datatype == "numeric") {
                        var attribs = (e.attrib?e.attrib:"");
                        var minimum = attribs.match(/min=(\d+)/);
                        var maximum = attribs.match(/max=(\d+)/);
                        
                        if (!(isnumeric(e.value)) ||
                            !((minimum == null) || (e.value >= parseFloat(minimum[1]))) ||
                            !((maximum == null) || (e.value <= parseFloat(maximum[1])))
                           )
                        {
                            errors += "- The field " + getname(e) + " must be a number";
                            if (minimum != null) {
                                errors += " that is >= " + minimum[1];
                                if (maximum != null) {
                                    errors += " and <= " + maximum[1];
                                }
                            } else if (maximum != null) {
                                errors += " that is <= " + maximum[1];
                            }
                            errors += ".\n";
                        }
                    } else if (e.datatype == "url") {
                        var attribs = (e.attrib?e.attrib:"");
                        if (attribs.match(/link/)) {
                            if (!(islink(e.value))) {
                                errors += "- The field " + getname(e) + " must be a valid HTTP link (i.e. http://www.cnn.com).\n"
                            }
                        } else if (!(isurl(e.value))) {
                            errors += "- The field " + getname(e) + " must be a valid URL (i.e. ftp://ftp.sunet.com).\n"
                        }
                    }
                } else {
                    empty_fields += "\n      " + getname(e);
                }
            } else if ((e.type == "radio") && (e.checked == false)) {
                unselected_fields += "\n      " + getname(e);
            } else if ((e.type == "checkbox") && (e.checked == false)) {
                unselected_fields += "\n      " + getname(e);
            } else if ((e.type == "select-one") || (e.type == "select-multiple")) {
                var x = 0;
                for (cnt = 0; cnt < e.length; cnt++) {
                    if ((e.options[cnt].selected) && (!isblank(e.options[cnt].value))) {
                        x = 1;
                    }
                }
                if (x == 0) {
                    unselected_fields += "\n      " + getname(e);
                }
            } else if ((e.type == "file") && (isblank(e.value))) {
                empty_fields += "\n      " + getname(e);
            }
        } else if ((e.type == "text") || (e.type == "textarea")) {
            if (!(isblank(e.value))) {
                if ((e.datatype == null) || (e.datatype == "string")) {
                    var attribs = (e.attrib?e.attrib:"");
                    if (attribs.match(/nospace/)) {
                        if (isspaced(e.value)) {
                            errors += "- The field " + getname(e) + " must not contain spaces.\n"
                        }
                    }
                    if (attribs.match(/alphanum/)) {
                        if (!(isalphanum(e.value))) {
                            errors += "- The field " + getname(e) + " must contain only alphanumeric characters (A-Z, a-z, or 0-9).\n"
                        }
                    } else if (attribs.match(/alpha/)) {
                        if (!(isalpha(e.value))) {
                            errors += "- The field " + getname(e) + " must contain only alphabetic characters (A-Z or a-z).\n"
                        }
                    }
                } else if (e.datatype == "date") {
                    if (!(isdate(e.value))) {
                        errors += "- The field " + getname(e) + " must be a valid date (m/d/yy or m/d/yyyy).\n"
                    }
                } else if (e.datatype == "zip") {
                    if (!(iszip(e.value))) {
                        errors += "- The field " + getname(e) + " must be a zip/postal code (xxxxx[-xxxx]).\n"
                    }
                } else if (e.datatype == "phone") {
                    if (!(isphone(e.value))) {
                        errors += "- The field " + getname(e) + " must be a phone number (xxx-xxx-xxxx).\n"
                    }
                } else if (e.datatype == "email") {
                    if (!(isemail(e.value))) {
                        errors += "- The field " + getname(e) + " must be an email address (name@internet.address).\n"
                    }
                } else if (e.datatype == "numeric") {
                    var attribs = (e.attrib?e.attrib:"");
                    var minimum = attribs.match(/min=(\d+)/);
                    var maximum = attribs.match(/max=(\d+)/);
                        
                    if (!(isnumeric(e.value)) ||
                        !((minimum == null) || (e.value >= parseFloat(minimum[1]))) ||
                        !((maximum == null) || (e.value <= parseFloat(maximum[1])))
                       )
                    {
                        errors += "- The field " + getname(e) + " must be a number";
                        if (minimum != null) {
                            errors += " that is >= " + minimum[1];
                            if (maximum != null) {
                                errors += " and <= " + maximum[1];
                            }
                        } else if (maximum != null) {
                            errors += " that is <= " + maximum[1];
                        }
                        errors += ".\n";
                    }
                } else if (e.datatype == "url") {
                    var attribs = (e.attrib?e.attrib:"");
                    if (attribs.match(/link/)) {
                        if (!(islink(e.value))) {
                            errors += "- The field " + getname(e) + " must be a valid HTTP link (i.e. http://www.cnn.com).\n"
                        }
                    } else if (!(isurl(e.value))) {
                        errors += "- The field " + getname(e) + " must be a valid URL (i.e. ftp://ftp.sunet.com).\n"
                    }
                }
            }
        }
    }
    
    // Now, if there were any errors, display the messages, and
    // return false to prevent the form from being submitted.
    // Otherwise return true.

    if (!empty_fields && !unselected_fields && !errors) {
        return true;
    } else {
        msg  = "_____________________________________________________\n\n";
        msg += "The form was not submitted because of the following error(s).\n";
        msg += "Please correct these error(s) and re-submit.\n";
        msg += "_____________________________________________________\n\n";
        
        if (empty_fields) {
            msg += "- The following required field(s) are empty:" + empty_fields + "\n\n";
        }

        if (unselected_fields) {
            msg += "- The following required field(s) are not selected:" + unselected_fields + "\n\n";
        }

        if (errors) {
            msg += errors;
        }
        
        alert(msg);
        return false
    }
}

// A function that returns an alias for a field if one has been defined
function getname(e) {
    if (e.alias == null) {
        return e.name;
    } else {
        return e.alias;
    }
}

// A utility function that returns true if a string is empty or contains
// only whitespace characters.
function isblank(s) {
    var filter = /^\s*$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string contains any
// whitespace characters.
function isspaced(s) {
    var filter = /\s+/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string contains only alphabetic
// characters and spaces.
function isalpha(s) {
    var filter = /^([A-Z]|[a-z]|\s)+$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string contains only alphanumeric
// characters and spaces.
function isalphanum(s) {
    var filter = /^([A-Z]|[a-z]|\d|\s)+$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string contains only 
// characters used in names of people, streets, cities, etc.
function isname(s) {
    var filter = /^([A-Z]|[a-z]|\d|[ \.\#\&\(\)\-_\'])+$/;
    if (filter.test(s)) { return true; }
    return false;
}

// A utility function that returns true if a string contains  
// a standard 2-letter State abbreviation.
function isstate(s) {
    var filter = /^([A-W]|[a-w])([A-Z]|[a-z])$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}


// A utility function that returns true if a string is in correct date
// format.  Does not account for non-leap years.  Supported formats include
// the following ([] = optional numbers):
//
//      m[m]/d[d]/yy[yy], m[m].d[d].yy[yy], and m[m]-d[d]-yy[yy]
//
function isdate(s) {
    var monthdays = new Array();
    monthdays[1]  = 31;
    monthdays[2]  = 29;
    monthdays[3]  = 31;
    monthdays[4]  = 30;
    monthdays[5]  = 31;
    monthdays[6]  = 30;
    monthdays[7]  = 31;
    monthdays[8]  = 31;
    monthdays[9]  = 30;
    monthdays[10] = 31;
    monthdays[11] = 30;
    monthdays[12] = 31;

    var filter = new Array();
    filter[0] = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/;
    filter[1] = /^(\d{1,2})\.(\d{1,2})\.(\d{2}|\d{4})$/;
    filter[2] = /^(\d{1,2})-(\d{1,2})-(\d{2}|\d{4})$/;
    
    for (i = 0; i < filter.length; i++) {
        var result = s.match(filter[i]);
        if (result != null) {
            if ((parseFloat(result[1]) > 0 ) && (parseFloat(result[1]) <= 12)) {
                if ((parseFloat(result[2]) > 0) && (parseFloat(result[2]) <= monthdays[parseFloat(result[1])])) {
                    return true;
                }
                return false;
            }
            return false;
        }    
    }
    
    return false;
}

// A utility function that returns true if a string is in correct zipcode
// format.  Below is the supported format ([] = optional characters):
//
//      xxxxx[-xxxx]
//
function iszip(s) {
    var filter = /^\d{5}(-\d{4})?$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string is in correct phone
// format.  Supported formats include the following ([] = optional numbers):
//
//      xxx-xxx-xxxx, xxx/xxx-xxxx, (xxx)xxx-xxxx, and xxx.xxx.xxxx
//
function isphone(s) {
    var filter = new Array();
    filter[0] = /^\d{3}-\d{3}-\d{4}$/;
    filter[1] = /^\d{3}\/\d{3}-\d{4}$/;
    filter[2] = /^\(\d{3}\)\s*\d{3}-\d{4}$/;
    filter[3] = /^\d{3}\.\d{3}\.\d{4}$/;
    
    for (i = 0; i < filter.length; i++) {
        if (filter[i].test(s)) {
            return true;
        }
    }
    
    return false;
}

// A utility function that returns true if a string is in correct email address
// format.
function isemail(s) {
    var filter = /^([A-Z]|[a-z]|\d|[\._\-])+@([A-Z]|[a-z]|\d|[_\-])+\.([A-Z]|[a-z]|[_\-\.])+$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string is numeric
// and 0 or greater.
function isnumeric(s) {
    var filter = /^\d+$/;
    if (filter.test(s)) {
		if ( s >= 0 ) {
        return true;
		}
    }
    return false;
}

// A utility function that returns true if a string is a valid URL (a bit more
// flexible than the 'islink' function).
function isurl(s) {
    var filter = /^(http|telnet|gopher|file|wais|ftp):[\w\/#~:.?+=&%@!\\-]+$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string is a valid http link
function islink(s) {
    var filter = /^http:\/\/[\w\/#~.?+=&%@-]+$/;
    if (filter.test(s)) {
        return true;
    }
    return false;
}

// A utility function that returns true if a string is a valid credit card number
// which is a card number that passes the Luhn Mod-10 test
// ==============================================================================
//   THIS FUNCTION IS TAKEN DIRECTLY FROM NETSCAPE FROM:
//   http://developer.netscape.com/library/examples/...
//                    .../javascript/formval/FormChek.js
//   which is a bunch of functions to validate forms
// ==============================================================================
function isCreditCard(s) {
  // Encoding only works on cards with less than 19 digits
  if (s.length > 19)
    return (false);

  sum = 0; mul = 1; l = s.length;
  for (i = 0; i < l; i++) {
    digit = s.substring(l-i-1,l-i);
    tproduct = parseInt(digit ,10)*mul;
    if (tproduct >= 10)
      sum += (tproduct % 10) + 1;
    else
      sum += tproduct;
    if (mul == 1)
      mul++;
    else
      mul--;
  }

  if ((sum % 10) == 0)
    return (true);
  else
    return (false);
}

