mangroveorg/datawinners

View on GitHub
datawinners/media/javascript/Knockout-Validation/Src/rules.js

Summary

Maintainability
F
6 days
Test Coverage
//Validation Rules:
// You can view and override messages or rules via:
// ko.validation.rules[ruleName]
//
// To implement a custom Rule, simply use this template:
// ko.validation.rules['<custom rule name>'] = {
//      validator: function (val, param) {
//          <custom logic>
//          return <true or false>;
//      },
//      message: '<custom validation message>' //optionally you can also use a '{0}' to denote a placeholder that will be replaced with your 'param'
// };
//
// Example:
// ko.validation.rules['mustEqual'] = {
//      validator: function( val, mustEqualVal ){
//          return val === mustEqualVal;
//      },
//      message: 'This field must equal {0}'
// };
//
ko.validation.rules = {};
ko.validation.rules['required'] = {
    validator: function (val, required) {
        var stringTrimRegEx = /^\s+|\s+$/g,
            testVal;

        if (val === undefined || val === null) {
            return !required;
        }

        testVal = val;
        if (typeof (val) === "string") {
            testVal = val.replace(stringTrimRegEx, '');
        }

        if (!required) {// if they passed: { required: false }, then don't require this
            return true;
        }

        return ((testVal + '').length > 0);
    },
    message: 'This field is required.'
};

function minMaxValidatorFactory(validatorName) {
    var isMaxValidation = validatorName === "max";

    return function (val, options) {
        if (ko.validation.utils.isEmptyVal(val)) {
            return true;
        }

        var comparisonValue, type;
        if (options.typeAttr === undefined) {
            // This validator is being called from javascript rather than
            // being bound from markup
            type = "text";
            comparisonValue = options;
        } else {
            type = options.typeAttr;
            comparisonValue = options.value;
        }

        // From http://www.w3.org/TR/2012/WD-html5-20121025/common-input-element-attributes.html#attr-input-min,
        // if the value is parseable to a number, then the minimum should be numeric
        if (!isNaN(comparisonValue)) {
            type = "number";
        }

        var regex, valMatches, comparisonValueMatches;
        switch (type.toLowerCase()) {
            case "week":
                regex = /^(\d{4})-W(\d{2})$/;
                valMatches = val.match(regex);
                if (valMatches === null) {
                    throw "Invalid value for " + validatorName + " attribute for week input.  Should look like " +
                        "'2000-W33' http://www.w3.org/TR/html-markup/input.week.html#input.week.attrs.min";
                }
                comparisonValueMatches = comparisonValue.match(regex);
                // If no regex matches were found, validation fails
                if (!comparisonValueMatches) {
                    return false;
                }

                if (isMaxValidation) {
                    return (valMatches[1] < comparisonValueMatches[1]) || // older year
                        // same year, older week
                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2]));
                } else {
                    return (valMatches[1] > comparisonValueMatches[1]) || // newer year
                        // same year, newer week
                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
                }
                break;

            case "month":
                regex = /^(\d{4})-(\d{2})$/;
                valMatches = val.match(regex);
                if (valMatches === null) {
                    throw "Invalid value for " + validatorName + " attribute for month input.  Should look like " +
                        "'2000-03' http://www.w3.org/TR/html-markup/input.month.html#input.month.attrs.min";
                }
                comparisonValueMatches = comparisonValue.match(regex);
                // If no regex matches were found, validation fails
                if (!comparisonValueMatches) {
                    return false;
                }

                if (isMaxValidation) {
                    return ((valMatches[1] < comparisonValueMatches[1]) || // older year
                        // same year, older month
                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2])));
                } else {
                    return (valMatches[1] > comparisonValueMatches[1]) || // newer year
                        // same year, newer month
                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
                }
                break;

            case "number":
            case "range":
                if (isMaxValidation) {
                    return (!isNaN(val) && parseFloat(val) <= parseFloat(comparisonValue));
                } else {
                    return (!isNaN(val) && parseFloat(val) >= parseFloat(comparisonValue));
                }
                break;

            default:
                if (isMaxValidation) {
                    return val <= comparisonValue;
                } else {
                    return val >= comparisonValue;
                }
        }
    };
}

ko.validation.rules['min'] = {
    validator: minMaxValidatorFactory("min"),
    message: 'Please enter a value greater than or equal to {0}.'
};

ko.validation.rules['max'] = {
    validator: minMaxValidatorFactory("max"),
    message: 'Please enter a value less than or equal to {0}.'
};
    
ko.validation.rules['minLength'] = {
    validator: function (val, minLength) {
        return ko.validation.utils.isEmptyVal(val) || val.length >= minLength;
    },
    message: 'Please enter at least {0} characters.'
};

ko.validation.rules['maxLength'] = {
    validator: function (val, maxLength) {
        return ko.validation.utils.isEmptyVal(val) || val.length <= maxLength;
    },
    message: 'Please enter no more than {0} characters.'
};

ko.validation.rules['pattern'] = {
    validator: function (val, regex) {
        return ko.validation.utils.isEmptyVal(val) || val.toString().match(regex) !== null;
    },
    message: 'Please check this value.'
};

ko.validation.rules['step'] = {
    validator: function (val, step) {

        // in order to handle steps of .1 & .01 etc.. Modulus won't work
        // if the value is a decimal, so we have to correct for that
        if (ko.validation.utils.isEmptyVal(val) || step === 'any') { return true; }
        var dif = (val * 100) % (step * 100);
        return Math.abs(dif) < 0.00001 || Math.abs(1 - dif) < 0.00001;
    },
    message: 'The value must increment by {0}'
};

ko.validation.rules['email'] = {
    validator: function (val, validate) {
        if (!validate) { return true; }

        //I think an empty email address is also a valid entry
        //if one want's to enforce entry it should be done with 'required: true'
        return ko.validation.utils.isEmptyVal(val) || (
            // jquery validate regex - thanks Scott Gonzalez
            validate && /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(val)
        );
    },
    message: 'Please enter a proper email address'
};

ko.validation.rules['date'] = {
    validator: function (value, validate) {
        if (!validate) { return true; }
        return ko.validation.utils.isEmptyVal(value) || (validate && !/Invalid|NaN/.test(new Date(value)));
    },
    message: 'Please enter a proper date'
};

ko.validation.rules['dateISO'] = {
    validator: function (value, validate) {
        if (!validate) { return true; }
        return ko.validation.utils.isEmptyVal(value) || (validate && /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value));
    },
    message: 'Please enter a proper date'
};

ko.validation.rules['number'] = {
    validator: function (value, validate) {
        if (!validate) { return true; }
        return ko.validation.utils.isEmptyVal(value) || (validate && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value));
    },
    message: 'Please enter a number'
};

ko.validation.rules['digit'] = {
    validator: function (value, validate) {
        if (!validate) { return true; }
        return ko.validation.utils.isEmptyVal(value) || (validate && /^\d+$/.test(value));
    },
    message: 'Please enter a digit'
};

ko.validation.rules['phoneUS'] = {
    validator: function (phoneNumber, validate) {
        if (!validate) { return true; }
        if (ko.validation.utils.isEmptyVal(phoneNumber)) { return true; } // makes it optional, use 'required' rule if it should be required
        if (typeof (phoneNumber) !== 'string') { return false; }
        phoneNumber = phoneNumber.replace(/\s+/g, "");
        return validate && phoneNumber.length > 9 && phoneNumber.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
    },
    message: 'Please specify a valid phone number'
};

ko.validation.rules['equal'] = {
    validator: function (val, params) {
        var otherValue = params;
        return val === ko.validation.utils.getValue(otherValue);
    },
    message: 'Values must equal'
};

ko.validation.rules['notEqual'] = {
    validator: function (val, params) {
        var otherValue = params;
        return val !== ko.validation.utils.getValue(otherValue);
    },
    message: 'Please choose another value.'
};

//unique in collection
// options are:
//    collection: array or function returning (observable) array
//              in which the value has to be unique
//    valueAccessor: function that returns value from an object stored in collection
//              if it is null the value is compared directly
//    external: set to true when object you are validating is automatically updating collection
ko.validation.rules['unique'] = {
    validator: function (val, options) {
        var c = ko.validation.utils.getValue(options.collection),
            external = ko.validation.utils.getValue(options.externalValue),
            counter = 0;

        if (!val || !c) { return true; }

        ko.utils.arrayFilter(c, function (item) {
            if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
        });
        // if value is external even 1 same value in collection means the value is not unique
        return counter < (!!external ? 1 : 2);
    },
    message: 'Please make sure the value is unique.'
};


//now register all of these!
(function () {
    ko.validation.registerExtenders();
}());