Knockout-Validation
Knockout-Validation copied to clipboard
Async Validation
When we have two fields
- Sync valid
- Async valid When you put validation in first. And call is deffered to another field, Validation is valid in any case.
@duxa
Can you give some more detail or show an example? I'm not really seeing what you're getting at.
OK.
- We have two fields - is sync - is async And button check
- By async server have defer 5 seconds.
When I put value in first label and click button - validation is valid. After the request come from server with params False - validation is invalid.
Symmary
- Until the query come from server validation must be invalid.
You could subscribe to the isValidating property of the Email field and show a message or "working" image while it's validating. I can't do a fiddle right now, but if it would help I should be able to in a few hours.
I want when I push button submit if all field (sync too) are not valid I have alert form in not valid now.
IanYates83, ericmbarnard Any changes ?
I'm also having a tough time figuring out how to use async validators. Consider the following:
-
User changes value and/or clicks form submit button.
-
An observable extended with an async validator gets mutated, causing ko.validation to kick in.
-
ko.validation.validateObservableloops through the rules, invoking the validation functions. -
When it gets to the async rule, it executes
validateAsync(a private function). -
The
validateAsyncfunction invokesobservable.isValidating(true). This notifies anyisValidatingsubscribers (what IanYates83 mentioned) that async validation has started. -
The
validateAsyncfunction then invokes the async validation function (which will send out a remote request), passing in a closed / privatecallBack. It is this private callback that will be invoked after the remote request completes. -
This is the first part that's confusing me: after the
validateAsyncfunction returns toko.validation.validateObservable, the loop completes and we have the following lines, which essentially make the extended observable valid while the async validation is in process (after it has been started, but before it has been completed):// finally, if we got this far, make the observable valid again! observable.error = null; observable.valid(true); return true;
-
(8) In my viewmodel,
observable.isValid() === true && observable.error === nullwhile the validation is taking place. If I have a ko submit binding on a form, I cannot useviewModel.isValid()to determine whether or not to submit the form, because ko.validation considers the viewModel to be valid before the async operation completes.
So, is the recommended practice to perform the form the user input submit action within the isValidating subscription? And if you have multiple async / remote validators, each of them should check viewModel.isValid() before submitting the user input to the server? Something like this?
self.observable1.isValidating.subscribe(function (isValidating) {
if (!isValidating) { // only do this when async has finished
if (self.isValid()) self.submitUserInput();
}
});
self.observable2.isValidating.subscribe(function (isValidating) {
if (!isValidating) { // only do this when async has finished
if (self.isValid()) self.submitUserInput();
}
});
Or is there another recommended practice for dealing with async validation?
Another issue I have found has to do with the validation function itself. Consider this:
self.observable1 = ko.observable().extend({
required: 'My required message',
validation: {
async: true,
validator: function (val, opts, callback) {
$.ajax({... this is not important...})
.success(function (response) {
if (response === true) callback(true);
else callback(false); // this causes problems
});
},
message: 'My async message'
}
});
The problem with the above code is that when callback(false) is invoked, it changes the observable's error property to a non-null value (the formatted message string), and sets the __valid__ function on the observable to false. However, since these properties were already set to null and true respectively during ko.validation.validateObservable (step 7 in example above), this causes subscribers to re-evaluate, which causes the validation function to be invoked again, resulting in an infinite loop as long as validation fails. This seems to be because either (both?) the error property or (and?) the __valid__ function are observable / computed, and keep getting changed back and forth by the async callback and ko.validation.validateObservable. Ping pong style.
Help please?
I thought I had an interesting solution to your problem of knowing whether you still had something being validated, but the issue you mentioned last has scuppered it.
My idea was simply to keep a count of fields that are validating and check that the number is 0 before allowing a save. http://jsfiddle.net/IanYates83/DtDm2/10/ It works, but any attempt to change an observable (specifically, notify its subscribers) within the async response (even with my setTimeout) caused the validation to loop as you described.
Making the count non-observable solves that problem, but you could no longer have the UI react to being able to save: http://jsfiddle.net/IanYates83/DtDm2/11/
@IanYates83 thanks for the fiddles. I was able to squash the ping pong loop yesterday by moving the async validator from an on-the-fly validator to a proper ko.validation.rules['rulename'] validator. It seems that changes to the __valid__ observable only notifies the field-under-validation of mutation and retriggers validateObservable when the async validator is created on the fly, within an extend call.
I ended up doing basically the same thing as you, creating an isValidating observable on the viewmodel. The field under validation's isValidating subscription updates its parent viewmodel's isValidating observable to true when while the async is occurring, and then to false when it is finished (instead of counting like your fiddles do). I can then subscribe to the parent viewmodel's isValidating observable and decide there (by checking viewmodel.isValid()) whether or not to submit updates.
Shall I repost this on-the-fly behavior as a new issue to report as a bug?
@danludwig can you share a fiddle of how you solved this issue?
Any news on this subject?
Shouldn't isValid (on validatedObservable for example) be async and only resolved when all properties are done validating?
Thanks!
@W3Max The group created by ko.validation.group now is decorated with some useful methods that gives you access to validatables #500 . Here's an example that uses an async rule and also defines a isValidating computed http://jsfiddle.net/whvse6qw/
@crissdev Thanks. I will try it and see if it resolves the issues I have. I'll let you know the results.
@W3Max Any update on this?
@crissdev I ended creating an extender (which I think could be integrated in knockout-validation). Here's what I've came up with:
function(validatedObservable) {
validatedObservable.isValidating = ko.computed(function() {
return !!validatedObservable.errors.find(function(obsv) {
return ko.validation.utils.isValidatable(obsv) && obsv.isValidating();
});
});
validatedObservable.isValidAsync = function() {
var deferred = new $.Deferred();
for (var i in validatedObservable()) {
if (ko.validation.utils.isValidatable(validatedObservable()[i])) {
validatedObservable()[i].validate();
}
}
if (!validatedObservable.isValidating()) {
deferred.resolve(validatedObservable.isValid());
} else {
var subscription = validatedObservable.isValidating.subscribe(function() {
deferred.resolve(validatedObservable.isValid());
subscription.dispose();
});
}
return deferred.promise();
};
//return the original observable
return validatedObservable;
}
@W3Max I like it but integrating this in the library is a bit difficult due to using promises. One may use jQuery while others may use Q or ES6 promises. The good thing though is that they all have then which gives some hopes :smile:.
One thing you should be careful in the above code is that if an exception is thrown the promise might remain in an unfulfilled state.
In conslusion, the library will definitely support something like this in a future release.
@crissdev Perfect! Thank you for your support!