Knockout-Validation icon indicating copy to clipboard operation
Knockout-Validation copied to clipboard

Async Validation

Open duxa opened this issue 13 years ago • 16 comments
trafficstars

When we have two fields

  1. Sync valid
  2. Async valid When you put validation in first. And call is deffered to another field, Validation is valid in any case.

duxa avatar Sep 26 '12 12:09 duxa

@duxa

Can you give some more detail or show an example? I'm not really seeing what you're getting at.

ericmbarnard avatar Sep 27 '12 03:09 ericmbarnard

OK.

  1. We have two fields - is sync - is async And button check
  2. 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

  1. Until the query come from server validation must be invalid.

duxa avatar Sep 27 '12 06:09 duxa

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.

IFYates avatar Sep 27 '12 07:09 IFYates

I want when I push button submit if all field (sync too) are not valid I have alert form in not valid now.

duxa avatar Sep 27 '12 07:09 duxa

IanYates83, ericmbarnard Any changes ?

duxa avatar Oct 01 '12 08:10 duxa

I'm also having a tough time figuring out how to use async validators. Consider the following:

  1. User changes value and/or clicks form submit button.

  2. An observable extended with an async validator gets mutated, causing ko.validation to kick in.

  3. ko.validation.validateObservable loops through the rules, invoking the validation functions.

  4. When it gets to the async rule, it executes validateAsync (a private function).

  5. The validateAsync function invokes observable.isValidating(true). This notifies any isValidating subscribers (what IanYates83 mentioned) that async validation has started.

  6. The validateAsync function then invokes the async validation function (which will send out a remote request), passing in a closed / private callBack. It is this private callback that will be invoked after the remote request completes.

  7. This is the first part that's confusing me: after the validateAsync function returns to ko.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. (8) In my viewmodel, observable.isValid() === true && observable.error === null while the validation is taking place. If I have a ko submit binding on a form, I cannot use viewModel.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?

danludwig avatar Oct 03 '12 21:10 danludwig

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/

IFYates avatar Oct 05 '12 06:10 IFYates

@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 avatar Oct 05 '12 12:10 danludwig

@danludwig can you share a fiddle of how you solved this issue?

bradbamford avatar Aug 21 '13 15:08 bradbamford

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 avatar Jan 15 '15 21:01 W3Max

@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 avatar Jan 16 '15 09:01 crissdev

@crissdev Thanks. I will try it and see if it resolves the issues I have. I'll let you know the results.

W3Max avatar Jan 22 '15 01:01 W3Max

@W3Max Any update on this?

crissdev avatar Feb 09 '15 20:02 crissdev

@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 avatar Feb 10 '15 18:02 W3Max

@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 avatar Feb 10 '15 18:02 crissdev

@crissdev Perfect! Thank you for your support!

W3Max avatar Feb 10 '15 20:02 W3Max