So I typically solve this with a paired extender and custom binding. Standard validation scenario to only want the error after the field has been entered and left. I like this method, since it easier to understand, and reuse. Here it is in a fiddle.
I don't see a problem with referencing a custom binding in the UI, even if it hints at validation. The UI knowing about bindings is standard for knockout, and there is nothing at all wrong with custom bindings. They are encouraged!
extender
ko.extenders.isValid = function (target, validator) {
//Use for tracking whether validation should be used
//The validate binding will init this on blur, and clear it on focus
//So that editing the field immediately clears errors
target.isActive = ko.observable(false);
if (typeof validator !== 'function') {
validator = function(value) {
return value !== undefined && value !== null && value.length > 0;
};
}
target.isValid = ko.computed(function () {
return validator(target());
});
//Just a convienient wrapper to bind against for error displays
//Will only show errors if validation is active AND invalid
target.showError = ko.computed(function () {
//This intentionally does NOT short circuit, to establish dependency
return target.isActive() & !target.isValid();
});
return target;
};
Example Use
var login = {
//use default validation
email: ko.observable('').extend({ isValid: true }),
//Use custom validation function
password: ko.observable('').extend({
isValid: function (value) {
return value.length > 0 && value.length <= passwordMaxLength;
}
})
};
Binding
//Just activate whatever observable is given to us on first blur
ko.bindingHandlers.validate = {
init: function (element, valueAccessor) {
ko.bindingHandlers.value.init.apply(this, arguments); //Wrap value init
//Active will remain false until we have left the field
//Starting the input with validation errors is bad
ko.utils.registerEventHandler(element, 'blur', function() {
valueAccessor().isActive(true);
});
//Validation should turn off while we are in the field
ko.utils.registerEventHandler(element, 'focus', function() {
valueAccessor().isActive(false);
});
},
update: ko.bindingHandlers.value.update //just wrap the update binding handler
};
Example Use
<input class="inputField" data-bind="validate: email"/>