16

我真的很喜欢Eric Barnard 的淘汰验证库如何与 observables 集成,允许分组,并提供自定义验证器可插入性(包括即时验证器)。有几个地方可以使用户体验更加灵活/友好,但总体而言,它的文档记录得当……除了 imo,当涉及到异步验证器时

我今天为此挣扎了几个小时,然后进行了搜索并登陆。我我和原作者有同样的问题/疑问,但我同意不清楚 duxa 到底要什么。我想引起更多关注,所以我也在这里问。

function MyViewModel() {
    var self = this;
    self.nestedModel1.prop1 = ko.observable().extend({
        required: { message: 'Model1 Prop1 is required.' },
        maxLength: {
            params: 140,
            message: '{0} characters max please.'
        }
    });
    self.nestedModel2.prop2 = ko.observable().extend({
        required: { message: 'Model2 Prop2 is required' },
        validation: {
            async: true,
            validator: function(val, opts, callback) {
                $.ajax({                                  // BREAKPOINT #1
                    url: '/validate-remote',
                    type: 'POST',
                    data: { ...some data... }
                })
                .success(function(response) {
                    if (response == true) callback(true); // BREAKPOINT #2
                    else callback(false);
                });
            },
            message: 'Sorry, server says no :('
        }
    });
}

ko.validation.group(self.nestedModel1);
ko.validation.group(self.nestedModel2);

关于上面代码的几点说明: 有 2 个单独的验证组,一个用于每个嵌套模型。嵌套模型 #1 没有异步验证器,嵌套模型 #2 同时具有同步(必需)和异步。异步调用服务器调用来验证输入。当服务器响应时,该callback参数用于ko.validation判断用户输入是好是坏。如果您在指示的行上放置断点并使用已知的无效值触发验证,则最终会出现无限循环,其中 ajaxsuccess函数会导致validator再次调用该函数。我破解了ko.validation源代码以查看发生了什么。

ko.validation.validateObservable = function(observable) {
    // set up variables & check for conditions (omitted for brevity)

    // loop over validators attached to the observable
    for (; i < len; i++) {
        if (rule['async'] || ctx['async']) {
            //run async validation
            validateAsync();
        } else {
            //run normal sync validation
            if (!validateSync(observable, rule, ctx)) {
                return false; //break out of the loop
            }
        }
    }

    //finally if we got this far, make the observable valid again!
    observable.error = null;
    observable.__valid__(true);
    return true;
}

此函数位于附加到用户输入 observable 的订阅链中,因此当其值更改时,将验证新值。该算法在每个附加到输入的验证器上循环,并根据验证器是否异步执行单独的函数。如果同步验证失败,则循环中断并且整个validateObservable函数退出。如果所有同步验证器都通过,则执行最后 3 行,本质上告诉ko.validation该输入是有效的。库中的__valid__函数如下所示:

//the true holder of whether the observable is valid or not
observable.__valid__ = ko.observable(true);

有两件事要从这里拿走:__valid__是一个可观察的,并且在函数退出true后设置为。validateAsync现在让我们来看看validateAsync

function validateAsync(observable, rule, ctx) {
    observable.isValidating(true);

    var callBack = function (valObj) {
        var isValid = false,
            msg = '';

        if (!observable.__valid__()) {
            // omitted for brevity, __valid__ is true in this scneario
        }

        //we were handed back a complex object
        if (valObj['message']) {
            isValid = valObj.isValid;
            msg = valObj.message;
        } else {
            isValid = valObj;
        }

        if (!isValid) {
            //not valid, so format the error message...
            observable.error = ko.validation.formatMessage(...);
            observable.__valid__(isValid);
        }

        // tell it that we're done
        observable.isValidating(false);
    };

    //fire the validator and hand it the callback
    rule.validator(observable(), ctx.params || true, callBack);
}

需要注意的是,在ko.validation.validateObservable__valid__observable 设置为 true 并退出之前,只执行此函数的第一行和最后一行。该callBack函数是作为第三个参数传递给validatorMyViewModel. 然而,在此之前,isValidating会调用 observable 的订阅者来通知异步验证已经开始。服务器调用完成后,将调用回调(在这种情况下,只需传递 true 或 false)。

现在这就是为什么在MyViewModel服务器端验证失败时断点会导致无限乒乓循环的原因:在callBack上面的函数中,请注意__valid__当验证失败时 observable 是如何设置为 false 的。这是发生的事情:

  1. 无效的用户输入会改变nestedModel2.prop2observable。
  2. 通过ko.validation.validateObservable订阅此更改通知。
  3. validateAsync函数被调用。
  4. 调用自定义异步验证器,向服务器提交异步$.ajax调用并退出。
  5. observable 设置为ko.validation.validateObservable 并退出__valid__true
  6. 服务器返回无效响应,并被callBack(false)执行。
  7. callBack函数设置__valid__为。false
  8. ko.validation.validateObservable通知对__valid__可观察对象的更改(callBack将其从更改truefalse)这基本上重复了上面的步骤 2。
  9. 重复上述步骤 3、4 和 5。
  10. 由于 observable 的值没有改变,服务器返回另一个无效响应,触发上面的步骤 6、7、8 和 9。
  11. 我们自己有一场乒乓球比赛。

因此,问题似乎在于ko.validation.validateObservable订阅处理程序不仅在监听用户输入值的变化,而且还在监听其嵌套的__valid__observable 的变化。这是一个错误,还是我做错了什么?

次要问题

您可以从ko.validation上面的源代码中看到,带有异步验证器的用户输入值在服务器验证时被视为有效。nestedModel2.isValid()正因为如此, “真相”不能依靠呼唤。相反,看起来我们必须使用isValidating钩子来创建对异步验证器的订阅,并且只有在它们通知false. 这是设计使然吗?与库的其他部分相比,这似乎是最反直觉的,因为异步验证器不需要isValidating订阅,并且可以依靠它来说.isValid()出真相。这也是设计使然,还是我在这里也做错了什么?

4

2 回答 2

15

所以我问的问题确实与如何在 ko.validation 中使用异步验证器有关。我从我的经验中学到了两个重要的东西:

  1. 不要创建async 匿名或一次性自定义规则验证器。相反,将它们创建为自定义规则。否则,您将得到我的问题中描述的无限循环/ping ping 匹配。

  2. 如果您使用验证器,请在所有验证器更改为 false之前async不要信任。isValid()asyncisValidating subscriptions

如果您有多个异步验证器,则可以使用如下模式:

var viewModel = {
    var self = this;
    self.prop1 = ko.observable().extend({validateProp1Async: self});
    self.prop2 = ko.observable().extend({validateProp2Async: self});
    self.propN = ko.observable();
    self.isValidating = ko.computed(function() {
        return self.prop1.isValidating() || self.prop2.isValidating();
    });
    self.saveData = function(arg1, arg2, argN) {

        if (self.isValidating()) {
            setTimeout(function() {
                self.saveData(arg1, arg2, argN);
            }, 50);
            return false;
        }

        if (!self.isValid()) {
            self.errors.showAllMessages();
            return false;
        }

        // data is now trusted to be valid
        $.post('/something', 'data', function() { doWhatever() });
    }
};

您还可以通过类似的替代解决方案查看此内容以获取另一个参考

这是一个异步“自定义规则”的示例:

var validateProp1Async = {
    async: true,
    message: 'you suck because your input was wrong fix it or else',
    validator: function(val, otherVal, callback) {
        // val will be the value of the viewmodel's prop1() observable
        // otherVal will be the viewmodel itself, since that was passed in
        //     via the .extend call
        // callback is what you need to tell ko.validation about the result
        $.ajax({
            url: '/path/to/validation/endpoint/on/server',
            type: 'POST', // or whatever http method the server endpoint needs
            data: { prop1: val, otherProp: otherVal.propN() } // args to send server
        })
        .done(function(response, statusText, xhr) {
            callback(true); // tell ko.validation that this value is valid
        })
        .fail(function(xhr, statusText, errorThrown) {
            callback(false); // tell ko.validation that his value is NOT valid
            // the above will use the default message. You can pass in a custom
            // validation message like so:
            // callback({ isValid: false, message: xhr.responseText });
        });
    }
};

基本上,您使用函数的callbackarg 来validator告诉 ko.validation 验证是否成功。该调用将触发已isValidating验证属性 observables 上的 observables 更改回false(意思是,异步验证已完成,现在知道输入是否有效)。

如果您的服务器端验证端点在验证成功时返回 HTTP 200 (OK) 状态,则上述方法将起作用。这将导致.done函数执行,因为它相当于$.ajax success. 如果您的服务器在验证失败时返回 HTTP 400 (Bad Request) 状态,它将触发.fail函数执行。如果您的服务器返回带有 400 的自定义验证消息,您可以从中获取该消息xhr.responseText以有效地覆盖默认you suck because your input was wrong fix it or else消息。

于 2013-04-09T22:38:45.820 回答
0

我有同样的问题,带有验证的嵌套可观察对象。所以一个魔术: self.errors = ko.validation.group(self.submissionAnswers, { deep: true, live: true }); 注意特殊的附加参数:包含字段的对象live: true

于 2019-06-05T11:56:39.077 回答