我真的很喜欢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
函数是作为第三个参数传递给validator
在MyViewModel
. 然而,在此之前,isValidating
会调用 observable 的订阅者来通知异步验证已经开始。服务器调用完成后,将调用回调(在这种情况下,只需传递 true 或 false)。
现在这就是为什么在MyViewModel
服务器端验证失败时断点会导致无限乒乓循环的原因:在callBack
上面的函数中,请注意__valid__
当验证失败时 observable 是如何设置为 false 的。这是发生的事情:
- 无效的用户输入会改变
nestedModel2.prop2
observable。 - 通过
ko.validation.validateObservable
订阅此更改通知。 - 该
validateAsync
函数被调用。 - 调用自定义异步验证器,向服务器提交异步
$.ajax
调用并退出。 - 将observable 设置为
ko.validation.validateObservable
并退出__valid__
true
。 - 服务器返回无效响应,并被
callBack(false)
执行。 callBack
函数设置__valid__
为。false
- 被
ko.validation.validateObservable
通知对__valid__
可观察对象的更改(callBack
将其从更改true
为false
)这基本上重复了上面的步骤 2。 - 重复上述步骤 3、4 和 5。
- 由于 observable 的值没有改变,服务器返回另一个无效响应,触发上面的步骤 6、7、8 和 9。
- 我们自己有一场乒乓球比赛。
因此,问题似乎在于ko.validation.validateObservable
订阅处理程序不仅在监听用户输入值的变化,而且还在监听其嵌套的__valid__
observable 的变化。这是一个错误,还是我做错了什么?
次要问题
您可以从ko.validation
上面的源代码中看到,带有异步验证器的用户输入值在服务器验证时被视为有效。nestedModel2.isValid()
正因为如此, “真相”不能依靠呼唤。相反,看起来我们必须使用isValidating
钩子来创建对异步验证器的订阅,并且只有在它们通知false
. 这是设计使然吗?与库的其他部分相比,这似乎是最反直觉的,因为非异步验证器不需要isValidating
订阅,并且可以依靠它来说.isValid()
出真相。这也是设计使然,还是我在这里也做错了什么?