I have managed to create a simple wizard based on an answer given by Niemeyer. This works fine. I want to add validation. I have managed to add a required validion on the fi
This was a tricky one, but I'll offer a couple of solutions for you...
If you simply want to prevent the Next button from proceeding with an invalid model state, then the easiest solution I found is to start by adding a class to each of the <span>
tags that are used for displaying the validation messages:
<span class="validationMessage"
data-bind='visible: FirstName.hasError, text: FirstName.validationMessage'>
(odd formatting to prevent horizontal scrolling)
Next, in the goNext function, change the code to include a check for whether or not any of the validation messages are visible, like this:
self.goNext = function() {
if (
(self.currentIndex() < self.stepModels().length - 1)
&&
($('.validationMessage:visible').length <= 0)
)
{
self.currentStep(self.stepModels()[self.currentIndex() + 1]);
}
};
Now, you may be asking "why not put that functionality in the canGoNext
dependent observable?", and the answer is that calling that function wasn't working like one might thing it would.
Because canGoNext
is a dependentObservable
, its value is computed any time the model that it's a member of changes.
However, if its model hasn't changed, canGoNext simply returns the last calculated value, i.e. the model hasn't changed, so why recalculate it?
This wasn't vital when only checking whether or not there were more steps remaining, but when I tried to include validation in that function, this came into play.
Why? Well, changing First Name, for example, updates the NameModel
it belongs to, but in the ViewModel
, self.nameModel is not set as an observable, so despite the change in the NameModel, self.nameModel is still the same. Thus, the ViewModel hasn't changed, so there's no reason to recompute canGoNext. The end result is that canGoNext always sees the form as valid because it's always checking self.nameModel, which never changes.
Confusing, I know, so let me throw a bit more code at you...
Here's the beginning of the ViewModel
, I ended up with:
function ViewModel(model) {
var self = this;
self.nameModel = ko.observable(new NameModel(model));
self.addressModel = ko.observable(new AddressModel(model));
...
As I mentioned, the models need to be observable to know what's happening to them.
Now the changes to the goNext
and goPrevious
methods will work without making those models observable, but to get the true real-time validation you're looking for, where the buttons are disabled when the form is invalid, making the models observable is necessary.
And while I ended up keeping the canGoNext
and canGoPrevious
functions, I didn't use them for validation. I'll explain that in a bit.
First, though, here's the function I added to ViewModel
for validation:
self.modelIsValid = ko.computed(function() {
var isOK = true;
var theCurrentIndex = self.currentIndex();
switch(theCurrentIndex)
{
case 0:
isOK = (!self.nameModel().FirstName.hasError()
&& !self.nameModel().LastName.hasError());
break;
case 1:
isOK = (!self.addressModel().Address.hasError()
&& !self.addressModel().PostalCode.hasError()
&& !self.addressModel().City.hasError());
break;
default:
break;
};
return isOK;
});
[Yeah, I know... this function couples the ViewModel to the NameModel and AddressModel classes even more than simply referencing an instance of each of those classes, but for now, so be it.]
And here's how I bound this function in the HTML:
<button data-bind="click: goPrevious,
visible: canGoPrevious,
enable: modelIsValid">Previous</button>
<button data-bind="click: goNext,
visible: canGoNext,
enable: modelIsValid">Next</button>
Notice that I changed canGoNext
and canGoPrevious
so each is bound to its button's visible
attribute, and I bound the modelIsValid
function to the enable
attribute.
The canGoNext
and canGoPrevious
functions are just as you provided them -- no changes there.
One result of these binding changes is that the Previous button is not visible on the Name step, and the Next button is not visible on the Confirm step.
In addition, when validation is in place on all of the data properties and their associated form fields, deleting a value from any field instantly disables the Next and/or Previous buttons.
Whew, that's a lot to explain!
I may have left something out, but here's the link to the fiddle I used to get this working: http://jsfiddle.net/jimmym715/MK39r/
I'm sure that there's more work to do and more hurdles to cross before you're done with this, but hopefully this answer and explanation helps.