/*********************************************************************
JavaScript 1.2 Validation Script (IE and Netscape)
version 1.02
by matthew frank

There are no warranties expressed or implied.  This script may be
re-used and distrubted freely provided this header remains intact
and all supporting files are included (unaltered) in the distribution:

nn-validation.js   - this file
nn-validation.htm  - example form
nn-readme.htm      - directions on using this script
(nn-validation.zip contains all files)

If you are interested in keeping up with the latest releases of this
script or asking questions about its implementation, think about joining
the eGroups discussion forum dedicated to data validation:

http://www.egroups.com/group/validation

*********************************************************************/


/*====================================================================
Function: Validation
Purpose:  Custom object constructor.
Inputs:   None
Returns:  undefined
====================================================================*/
function Validation(sz34, sz35, sz36, sz37, sz38, sz39, sz40, sz43, sz46, sz47, sz48, sz49, sz50, sz51, sz52) {
    /*====================================================================
    Function: Err
    Purpose:  Custom object constructor
    Inputs:   None
    Returns:  undefined
    ====================================================================*/
    var Err = function() {
        var oSource = new Object;
        var sMessage = new String;
        /*********************************************************************
        Method:   Err.clear
        Purpose:  Clear values from Error object
        Inputs:   none
        Returns:  undefined
        *********************************************************************/
        this.clear = function() {
            oSource = new Object;
            sMessage = new String;
        }
        /*********************************************************************
        Method:   Validation.Err.add
        Purpose:  Adds error to Error object
        Inputs:   oSource - source element object
        vType   - integer value of error type (or custom string)
        sFormat - optional date format
        Returns:  undefined
        *********************************************************************/
        this.add = function(oSrc, sMsg) {
            oSource = oSrc;
            sMessage = sMsg;
        }
        /*********************************************************************
        Method:   Err.raise
        Purpose:  Gives visual warning to user about all errors contained in
        the Error object
        Inputs:   none
        Returns:  undefined
        *********************************************************************/
        this.raise = function() {
            var sName = oSource.NAME;
            var sMsg = oSource.MSG;

            // Alert appropriate error message
            sMsg = sMsg ? sMsg : sMessage + (sName ? sz34 + sName + sz35 : "");
            alert(sMsg);

            // Give invalid field focus
            if (oSource.focus)
                oSource.focus();
            if (oSource.select)
                oSource.select();
            // Clear the Err object
            this.clear();
        }
    }
    /*********************************************************************
    Function: isDate
    Purpose:  Check that value is a date of the correct format
    Inputs:   sDate   - date as string
    sFormat - string format
    Returns:  boolean
    *********************************************************************/
    var isDate = function(sDate, sFormat) {
        var aDaysInMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

        // Fetch the date separator from the user's input
        var sSepDate = sDate.charAt(sDate.search(/\D/));
        // Fetch the date separator from the format
        var sSepFormat = sFormat.charAt(sFormat.search(/[^MDY]/i));
        // Compare separators
        if (sSepDate != sSepFormat)
            return false;

        // Fetch the three pieces of the date from the user's input and the format
        var aValueMDY = sDate.split(sSepDate);
        var aFormatMDY = sFormat.split(sSepFormat);

        // Assign day, month, year based on format
        var iMonth, iDay, iYear;
        iMonth = aValueMDY[0];
        iDay = aValueMDY[1];
        iYear = aValueMDY[2];

        // Validate that all pieces of the date are numbers
        if (!isNum(iMonth)
			|| !isNum(iDay)
			|| !isNum(iYear))
            return false;

        // Require 4 digit year
        if (iYear.length != 4)
            return false;

        // Check for leap year
        var iDaysInMonth = (iMonth != 2) ? aDaysInMonth[iMonth - 1] :
			((iYear % 4 == 0 && iYear % 100 != 0 || iYear % 400 == 0) ? 29 : 28);

        return (iDay != null && iMonth != null && iYear != null
				&& iMonth < 13 && iMonth > 0 && iDay > 0 && iDay <= iDaysInMonth);
    }
    /********************************************
    Function: isNum
    Purpose:  Check that parameter is a number
    Inputs:   v - string value
    Returns:  boolean
    ********************************************/
    var isNum = function(v) {
        return (typeof v != "undefined" && v.toString() && !/\D/.test(v));
    }
    /********************************************
    Function: formatNumber
    Purpose:  Format an integer for display
    Inputs:   i - integer value (as string)
    Returns:  string
    ********************************************/
    var formatNumber = function(i) {
        var sEnd = "$";
        i = i.toString();
        if (/\./.test(i))
            sEnd = "\\.";
        var re = new RegExp("(\\d)(\\d{3})(,|" + sEnd + ")");
        if (re.test(i))
            i = formatNumber(i.replace(re, "$1,$2$3"));
        return i;
    }
    /********************************************
    Function: getValueOf
    Purpose:  Return the value of a form field as seen at server
    Inputs:   oElement - form field
    Returns:  boolean
    ********************************************/
    var getValueOf = function(oElement) {
        var sReturnValue = null;
        switch (oElement.type) {
            case "text": case "textarea": case "file": case "password": case "hidden":
                sReturnValue = oElement.value;
                break;
            case "select-one":
                if (oElement.selectedIndex > -1)
                    sReturnValue = oElement.options[oElement.selectedIndex].value;
                break;
            case "select-multiple":
                var i, iOptions = oElement.options.length;
                for (i = 0; i < iOptions; i++)
                    if (oElement.options[i].selected && oElement.options[i].value.toString().trim()) {
                    sReturnValue = true;
                    break;
                }
                break;
            case "radio": case "checkbox":
                if (oElement.checked)
                    sReturnValue = oElement.value ? oElement.value : true;
                break;
        }
        return sReturnValue;
    }
    /*********************************************************************
    Method:   Validation.setup
    Purpose:  Set up methods and event handlers for all forms and elements
    Inputs:   none
    Returns:  undefined
    *********************************************************************/
    this.setup = function() {
        // Fan through forms on page to perform initializations
        var i, oForm, iForms = document.forms.length;
        for (i = 0; i < iForms; i++) {
            oForm = document.forms[i];
            if (!oForm.bProcessed) {
                // Capture and replace onsubmit event handler
                var fnSubmit = oForm.onsubmit;

                // Create new event handlers
                oForm.onsubmit = function() {
                    // Execute onbeforevalidate processing
                    if (typeof this.onbeforevalidate == "function" && this.onbeforevalidate() == false)
                        return false;
                    var i, oElement, iElements = this.elements.length;
                    // Validate individual elements
                    for (i = 0; i < iElements; i++) {
                        oElement = this.elements[i];
                        // Perform default validation for element
                        if (!oElement.valid()) {
                            Validation.Err.raise();
                            return false;
                        }
                    }
                    // Execute onaftervalidate processing
                    if (typeof this.onaftervalidate == "function" && this.onaftervalidate() == false)
                        return false;

                    // Perform original onsubmit event handler
                    if (fnSubmit && fnSubmit() == false)
                        return false;
                }
                oForm.bProcessed = true;
            }
            // Create Input methods
            var j, iElements = oForm.elements.length;
            for (j = 0; j < iElements; j++) {
                var oElement = oForm.elements[j];
                if (!oElement.bProcessed) {
                    /**********************************************
                    Method:   Input.valid
                    Purpose:  Validate an element based on the
                    attributes provided in the HTML text
                    Inputs:   none
                    Returns:  boolean
                    **********************************************/
                    oElement.valid = function() {
                        // Trim leading and trailing spaces
                        if (this.value)
                            this.value = this.value.trim();
                        var sValue = getValueOf(this);
                        // REQUIRED
                        if (this.REQUIRED && !sValue) {
                            Validation.Err.add(this, sz36);
                            return false;
                        }
                        // FLOAT
                        var bSigned = this.SIGNED;
                        if (this.FLOAT && sValue) {
                            var re = new RegExp("^" + ((bSigned) ? "-?" : "") + "(\\d*(,?\\d{3})*\\.?\\d+|\\d+(,?\\d{3})*\\.?\\d*)$");
                            if (!re.test(sValue)) {
                                Validation.Err.add(this, sz37);
                                return false;
                            }
                            // MIN
                            var iMin = this.MIN;
                            if (iMin == parseFloat(iMin) && sValue.replace(/,/, "") < iMin) {
                                Validation.Err.add(this, sz38 + formatNumber(iMin));
                                return false;
                            }
                            // MAX
                            var iMax = this.MAX;
                            if (iMax == parseFloat(iMax) && sValue.replace(/,/, "") > iMax) {
                                Validation.Err.add(this, sz39 + formatNumber(iMax));
                                return false;
                            }
                        }
                        // AMOUNT
                        if (this.AMOUNT && sValue) {
                            var sSigned = "(\\$?-?|-?\\$?)";
                            var re = new RegExp("^" + ((bSigned) ? sSigned : "\\$?") + "((\\d{1,3})*(,?\\d{3})*\\.?\\d{2}|\\d{1,3}(,?\\d{3})*\\.?(\\d{2})?)$");
                            if (!re.test(sValue)) {
                                Validation.Err.add(this, sz40);
                                return false;
                            }
                            // MIN
                            var iMin = this.MIN;
                            if (iMin == parseFloat(iMin) && sValue.replace(/[\$,]/, "") < iMin) {
                                Validation.Err.add(this, sz38 + formatNumber(iMin));
                                return false;
                            }
                            // MAX
                            var iMax = this.MAX;
                            if (iMax == parseFloat(iMax) && sValue.replace(/[\$,]/, "") > iMax) {
                                Validation.Err.add(this, sz39 + formatNumber(iMax));
                                return false;
                            }
                        }
                        // INTEGER
                        if (this.INTEGER && sValue) {
                            var re = new RegExp("^" + ((bSigned) ? "-?" : "") + "\\d{1,3}(,?\\d{3})*$");
                            if (!re.test(sValue)) {
                                Validation.Err.add(this, sz43);
                                return false;
                            }
                            // MIN
                            var iMin = this.MIN;
                            if (iMin == parseInt(iMin) && sValue.replace(/,/, "") < iMin) {
                                Validation.Err.add(this, sz38 + formatNumber(iMin));
                                return false;
                            }
                            // MAX
                            var iMax = this.MAX;
                            if (iMax == parseInt(iMax) && sValue.replace(/,/, "") > iMax) {
                                Validation.Err.add(this, sz39 + formatNumber(iMax));
                                return false;
                            }
                        }
                        // DATE
                        if (this.DATE && sValue) {
                            // Set default date format
                            var sFormat = "MM/DD/YYYY";
                            if (!isDate(sValue, sFormat)) {
                                Validation.Err.add(this, sz46);
                                return false;
                            }
                        }
                        // REGEXP
                        var oRegexp = this.REGEXP;
                        if (oRegexp && oRegexp.constructor == RegExp && sValue) {
                            if (!oRegexp.test(sValue)) {
                                Validation.Err.add(this, sz47);
                                return false;
                            }
                        }
                        // PHONE
                        if (this.PHONE && sValue) {
                            var sPhone = sValue.replace(/\D/g, "");
                            var iDigits = sPhone.length;
                            if (iDigits < 7) {
                                Validation.Err.add(this, sz48);
                                return false;
                            }
                        }
                        // LENGTH
                        if (sValue && this.LENGTH && isNum(this.LENGTH) && sValue.length > this.LENGTH) {
                            Validation.Err.add(this, sz49 + formatNumber(this.LENGTH) + sz50);
                            return false;
                        }
                        // EMAIL
                        if (this.EMAIL && sValue) {
                            if (!/^[\w_-]+(\.[\w_-]+)*@[\w_-]+(\.[\w_-]+)*\.\w{2,3}$/i.test(sValue)) {
                                Validation.Err.add(this, sz51);
                                return false;
                            }
                        }
                        // ZIP Code
                        if (this.ZIP && sValue) {
                            if (!/\d{4}(-?\d{4})?$/.test(sValue)) {
                                Validation.Err.add(this, sz52);
                                return false;
                            }
                        }
                        // AND
                        var vAnd = this.AND;
                        if (vAnd && sValue) {
                            // If not an array, create one
                            if (vAnd.constructor != Array)
                                vAnd = vAnd.toString().split(/,/);
                            // Require each element in the list
                            var i, iFields = vAnd.length;
                            for (i = 0; i < iFields; i++) {
                                var oNewElement =
									(vAnd[i].form)
									? vAnd[i]
									: this.form.elements[vAnd[i].trim()];
                                if (oNewElement) {
                                    if (!getValueOf(oNewElement)) {
                                        Validation.Err.add(oNewElement, sz36);
                                        return false;
                                    }
                                }
                            }
                        }
                        // OR
                        var vOr = typeof this.OR == "object";
                        if (vOr && !sValue) {
                            vOr = this.OR["fields"];
                            if (vOr) {
                                // If not an array, create one
                                if (vOr.constructor != Array)
                                    vOr = vOr.toString().split(/,/);
                                // Require each element in the list
                                var i, iFields = vOr.length;
                                var bValue = false;
                                for (i = 0; i < iFields; i++) {
                                    var oNewElement =
										(vOr[i].form)
										? vOr[i]
										: this.form.elements[vOr[i].trim()];
                                    if (oNewElement)
                                        bValue |= !!getValueOf(oNewElement);
                                }
                                if (!bValue) {
                                    Validation.Err.add(this, this.OR["msg"] ? this.OR["msg"] : sz36);
                                    return false;
                                }
                            }
                        }
                        // NOSPACE
                        if (this.NOSPACE && this.value)
                            this.value = this.value.replace(/\s/g, "");
                        // UPPERCASE
                        if (this.UPPERCASE && this.value)
                            this.value = this.value.toUpperCase();
                        // LOWERCASE
                        if (this.LOWERCASE && this.value)
                            this.value = this.value.toLowerCase();

                        // Perform onvalidate event handler
                        if (typeof this.onvalidate == "function" && this.onvalidate() == false)
                            return false;

                        return true;
                    }
                    oElement.bProcessed = true;
                }
            }
        }
    }
    // Limit use of script to valid environments
    if ("".replace && window.RegExp) {
        /*********************************************************************
        Method:   String.trim
        Purpose:  Removing leading and trailing spaces
        Inputs:   none
        Returns:  string
        *********************************************************************/
        String.prototype.trim = function() {
            return this.replace(/^\s+|\s+$/g, "");
        }

        // Form setup
        if (document.forms) {
            this.setup();
            this.Err = new Err;
        }
    }
}

