knockout validation spike

by manchagnu

HTML

<script src="http://github.com/downloads/SteveSanderson/knockout/jquery.tmpl.js"></script>
<script src="https://github.com/SteveSanderson/knockout/raw/master/build/output/knockout-latest.debug.js"></script>
<script id="errorTemplate" type="text/html">
    ${$data}}
</script>

<form data-bind="submit: onSubmit">
    <label>First Name</label>
    <input data-bind="value: firstName, valueUpdate: 'afterkeydown'" type="text">
    <span data-bind="template: { name: 'errorTemplate', data: validation.getError(firstName) }"></span>
    <br/>
    
    <label>Last Name</label>
    <input data-bind="value: lastName, valueUpdate: 'afterkeydown'" type="text">
    <span data-bind="template: { name: 'errorTemplate', data: validation.getError(lastName) }"></span>
    <br/>
    
    <label>Gender</label>
    <select data-bind="value: gender">
        <option value=""></option>
        <option value="m">Male</option>
        <option value="f">Female</option>
    </select>
    <span data-bind="template: { name: 'errorTemplate', data: validation.getError(gender) }"></span>
    <br/>
    <input type="submit">
</form>

JavaScript

ko.validation = function() {
    var fields_ = [];

    function requiredValidator(value) {
        return value != null && value !== "";
    }

    function addFieldValidation(field, validator, message) {
        if (!ko.isObservable(field)) {
            throw "passed field is not an observable";
        }

        var validationItem = {
            field: field,
            validator: validator,
            message: message,
            error: ko.observable(null)
        };

        field.subscribe(function(newValue) {
            validateField(validationItem, newValue);
        });

        fields_.push(validationItem);
    }

    function validateField(validationItem, value) {
        if (validationItem.validator(value)) {
            validationItem.error(null);
            return true;
        }
        else {
            validationItem.error(validationItem.message);
            return false;
        }
    }

    var result = {
        required: function(field, message) {
            addFieldValidation(field, requiredValidator, message);
        },
        custom: function(field, validator, message) {
            addFieldValidation(field, validator, message);
        },
        validate: function() {
            var isValid = true;
            
            for (var i = 0; i < fields_.length; i++) {
                var validationItem = fields_[i];
                var validationResult = validateField(validationItem, validationItem.field());
                if (!validationResult) {
                    isValid = false;
                }
            }

            return isValid;
        },
        hasError: function(field) {
            return this.getError(field)() != null;
        },
        getError: function(field) {
            for (var i = 0; i < fields_.length; i++) {
                var validationItem = fields_[i];
                if (field === validationItem.field) {
                    return validationItem.error;
                }
            }
     ...