IT:AD:Knockout-Validation.JS:HowTo:Validation of the ViewModel
Summary
As already expressed on IT:AD:Knockout-Validation.JS, validation on the ViewModel/ has advantages over validation of the View/markup.
Instead of validating a View/ serialiazation of the data in an error-prone way (eg: “$3.00” in an input@value has to be first parsed/converted to a number), just validate the actual ViewModel/ property directly.
Working with a number directly, and letting binding take care of the rendering, is far better than the older solution of using something like JQuery validation, where you are checking the formatted version of the number. In those cases, where you have to grab the element, get its string value, stript the '$' prefix off, converting the “0.1” to a number , checking it for range, then converting it back to a string with two decimal points “0.10” then adding back on the “$” suffix, something is wrong…spending way too much time coding for format and output, rather than just getting on with validation.
This way is much simpler. Much less error prone.
Process
Preparation
Download library from:
Documentation
As for all JS libraries (or any non-compiled, script language api where intellisense is not available), not going to get very far without reading the documentation:
Installation
I'm installing it into the Durandal application by including it in the base index.html page:
<script type="text/javascript" charset="utf-8" src="Scripts/jquery-1.9.1.js"></script> <script type="text/javascript" charset="utf-8" src="Scripts/jquery-ui.js"></script> <script type="text/javascript" charset="utf-8" src="Scripts/knockout-2.2.1.js"></script> <script type="text/javascript" charset="utf-8" src="Scripts/knockout.mapping-latest.js"></script> <!-- include validation library --> <script type="text/javascript" charset="utf-8" src="Scripts/knockout.validation.js"></script> <script type="text/javascript" charset="utf-8" src="Scripts/sammy-0.7.4.js"></script> <div id="applicationHost"/> <!-- after applicationHost --> <script type="text/javascript" src="App/durandal/amd/require.js" data-main="App/main"></script>
Init the library
Not going to work unless you enable the validation framework first.
I'm doing it in in a Durandal main.js file:
app.start().then(function() {
//Start Knockout.Validation before any views are loaded:
ko.validation.init();
...
}
Note that you could probably do it earlier as well, in the cshtml page:
<div id="applicationHost"/> <!-- after applicationHost --> <script> ko.validation.init(); </script> <script type="text/javascript" src="App/durandal/amd/require.js" data-main="App/main"></script>
Validating Properties
KO properties – which are methods – can be validated by extending them with ko.validation extension methods, passing config properties as appropriate:
property1: ko.observable().extend({ required: true }),
Create a Validated Output object
Unlike ASP.NET – which just validates properties one at a time – KO validation validates the whole object in one go. The results of which is one isValid flag, and an array of messages.
These you pickup from a
var myValidationObject = ko.validatedObservable(myViewModelWhosePropertiesHaveBeenExtended);
Note that their documentation implies that one should make the whole view model a Validation model, in which the properties are embedded:
var myViewModel = ko.validatedObservable({
property1: ko.observable().extend({ required: true }),
property2: ko.observable().extend({ max: 10 })
});
Don't.
The above sample is fine for trivial POC conditions, or documentation purposes, but not production.
For one, the ViewModel – in most cases – is going to contain more than just the object (eg: Contact, or Invoice) you are rendering. For another reason, making a validatedObservable observable object is going to add other properties (isValid for one) that are not part of the model – and this will be problematic later when serializing the object across the wire (sending properties irrelevant to the Contact service api).
Therefore:
var myViewModel = {
contact : {
first : ko.observable("first");
last : ko.observable("last").extend({ max: 10 }).extend({required: {onlyIf: function(){return someRule;}, message: 'Please supply a first name.' } });
}
//Keep the validation output separate:
validation: ko.validatedObservable(contact);
//other parts of the viewmodel
viewStuff : {title: 'New Contact Form', description: '....' };
});
Decorating the ViewModel Properties
The basic syntax for the validation that can be applied is one or more of the following:
//Extend a normal ko.observable with one or more validations:
this.myProp
= ko.observable()
.extend(
{
required:true, //field is required
required: {onlyIf: function(){return true;}}}); //beautiful :-)
min:2, //min value
max:99, //max value
step: 3, //has to be step value between min/max
minLength:3, //min str length
maxLength:12, //max str length
pattern:'^[a-z0-9].$', //define a regex pattern
pattern:{ //and/or define a specific message...
message: 'Hey this doesnt match my pattern',
params: '^[A-Z0-9].$'
}
number:true,
digit:true,
email:true, //regex as email
date: true, //it's a date
dateISO:true,
equal :otherKoObserable, //ensure its the same (eg: validate password and redo of password)
notEqual :otherKoObserable, //ensure its not the same
//Give it a message:
message: 'Hey this doesnt match my pattern',
}).
//You could -- not sure why -- chain them:
.extend({
pattern:{
message: 'Hey this doesnt match my pattern',
params: '^[A-Z0-9].$'
}
});
}
Validating the Model
Once the model has been converted to ko.validatedObservable, you can call it's methods:
// is the validationModel valid? myViewModel.validation.isValid(); // what are the error messages? myViewModel.validation.errors();
Displaying a Form with Validation Summary
Within the form:
<p data-bind="validationMessage: someValue"></p>
Further Example
//start using it!
var myValue = ko.observable().extend({ required: true });
//oooh complexity
var myComplexValue = ko.observable().extend({
required: true,
minLength: 3,
pattern: {
message: 'Hey this doesnt match my pattern',
params: '^[A-Z0-9].$'
}
});
//or chaining if you like that
var myComplexValue = ko.observable()
myComplexValue.extend({ required: true })
.extend({ minLength: 3 })
.extend({ pattern: {
message: 'Hey this doesnt match my pattern',
params: '^[A-Z0-9].$'
}});
//want to know if all of your ViewModel's properties are valid?
var myViewModel = ko.validatedObservable({
property1: ko.observable().extend({ required: true }),
property2: ko.observable().extend({ max: 10 })
});
console.log(myViewModel.isValid()); //false
myViewModel().property1('something');
myViewModel().property2(9);
console.log(myViewModel.isValid()); //true
Disabling Validation
Disabling Validation is important is an important case to master.
According to http://stackoverflow.com/questions/13400063/knockout-validation-disable-validation it should be just
this.property.extend({ validatable: false });
but I could not get it to work.
And after looking at the code I still can't tell if it should be working, or never will.
I ended up using the onlyIf in the following example:
self.searchRequest.nsn.extend({required: {onlyIf: function(){return self.searchRequest.searchByNSNOnly();}, message: 'Please supply an NSN.' } });
self.searchRequest.name.extend({required:{onlyIf: function(){return !self.searchRequest.searchByNSNOnly();}, message: 'Please supply a Name.'} });
…although that leaves me a bit concerned that the field could contain incorrect data.
Obviously, a WIP, and YMMV.