Validation in Knockout
Tim Kye
There is already a library for doing validation in Knockout, aply named Knockout-Validation. I don't really remember what issues I ran into when I used it so long ago, but I remember giving up on it. It also doesn't appear to play well with RequireJS, which is critical if you work in Durandal as I do.
It's syntax isn't that bad, but I think it gets a bit verbose for multiple validations-per-value. I built my own validation extender/binding-handler pair for Knockout that I use, and it doesn't have any issue with RequireJs.
It consists of an extender and a binding handler.
The binding handler
<input type="text" data-bind="valipublish_date: title" />
validate
wraps the standard value
binding, and sets the isModified
sub-observable on the extended observable. This stops errors from showing before the element has been ineteracted with.
The extender
Like ko-validation I use an extender, but unlike ko-validation I use just one: isValid
. It add's four sub-observables to the extended observable:
isValid()
- boolean indicating the validity.isModified()
- boolean indicating whether the value has been touched. It is set to true by thevalidate
binding handlershowError()
- boolean indicating whether the error message should be shown. It is!isValid() && isModified()
.errorMessage()
- the error message to display.
It supports several syntaxes, ranging from "bare-minimum" to "totally customized."
Just Required
this.title = ko.observable().extend({ isValid: true});
This will use the default validation method, value !== undefined && value !== null && (value.length === undefined || value.length > 0)
, and uses the default message Invalid Value
.
Custom message
this.title = ko.observable().extend({ isValid: { message: 'Error' } });
Will use the default validation method, and show the specified error.
Standard options
this.title = ko.observable().extend({ isValid: { valipublish_date: { min: 2}, message: 'must be at least 2' });
Passing an object to validate
will create a validation function based on some standard properties. Currently, it supports the following options:
min
andmax
. Will check these values as numbers.minLength
andmaxLength
will check these values as strings.options
will check values against this array. Any value in the array is valid.
All of these can also be primitive or observable values. validate
and messsage
can (and probably should) both be passed to the extender.
Custom validation
this.title = ko.observable().extend({ isValid: { valipublish_date: function (value) { return value !== undefined && value instanceof Array; });
If you pass a function to validate
it will be used to test the validity of the observable. The function will recieve any value written, and the value will be invalid if the function returns false
. If the custom function accesses any observables, it will establish a dependency and re-run when those observables change (just like a computed
).
Multiple validations
self.payLow = ko.observable(data.payLow || 0).extend({
numeric: { precision: 2, min: 0, max: payMax },
isValid: [
{
valipublish_date: { min: 1 },
message: 'Required'
},
{
valipublish_date: { max: payMax },
message: ko.computed(function() {
return 'Must be at most $' + payMax();
})
}
]
});
The extender can also take an array of validation objects, each of which will be used to determine validity. The errorMessage
sub-observable will be set to the message of the first failing specification only.
That's it
From this, you should be able to create any kind of validation you need. Let me know what you think. The source is located on Github.
This code was developed at work, and is being open sourced because I have an awesome manager. Thanks Ken!