Мне действительно нравится, как lib валидация Эрика Барнарда интегрируется с наблюдаемыми, позволяет группировать и предлагает настраиваемую подключаемость валидатора (включая валидаторы на лету). Есть пара мест, где он может быть более гибким/дружественным для UX, но в целом это достаточно хорошо задокументировано... за исключением imo, когда дело доходит до асинхронных валидаторов.
Я боролся с этим несколько часов сегодня, прежде чем заняться поиском и посадкой. Я думаю, что у меня есть те же проблемы/вопросы, что и у оригинального автора, но согласен, что не совсем понятно, о чем просил Дукса. Я хочу привлечь к этому вопросу больше внимания, поэтому я также задаю этот вопрос здесь.
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);
Несколько замечаний по поводу кода выше: есть две отдельные группы проверки, по одной для каждой вложенной модели. Вложенная модель # 1 не имеет асинхронных валидаторов, а вложенная модель # 2 имеет как синхронизацию (обязательно), так и асинхронность. Асинхронизация вызывает серверный вызов для проверки входных данных. Когда сервер отвечает, аргумент callback
используется, чтобы сообщить ko.validation
, является ли пользовательский ввод хорошим или плохим. Если вы поместите точки останова в указанные строки и инициируете проверку с использованием известного недопустимого значения, вы получите бесконечный цикл, в котором функция success
ajax вызывает повторный вызов функции 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;
}
Эта функция находится в цепочке подписки, прикрепленной к наблюдаемому пользователем вводу, поэтому при изменении ее значения будет проверяться новое значение. Алгоритм проходит по каждому валидатору, подключенному к входу, и выполняет отдельные функции в зависимости от того, является ли валидатор асинхронным или нет. Если проверка синхронизации не удалась, цикл прерывается и завершается вся функция validateObservable
. Если все валидаторы синхронизации ko.validation
, выполняются последние 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__
наблюдаемого значение true и завершается. callBack
функция получает то, что передается в качестве 3 - го параметра в асинхронном validator
функции, объявленной в MyViewModel
. Однако, прежде чем это произойдет, isValidating
наблюдаемые подписчики isValidating
, чтобы уведомить о начале асинхронной проверки. Когда серверный вызов завершен, вызывается обратный вызов (в этом случае просто передается значение true или false).
Теперь вот почему точки останова в MyViewModel
вызывают бесконечный цикл пинг-понга при сбое проверки на стороне сервера: в callBack
выше функции callBack
обратите внимание на то, что __valid__
наблюдаемой __valid__
установлено значение false, если проверка не удалась. Вот что происходит:
-
nestedModel2.prop2
пользовательский ввод изменяет наблюдаемуюnestedModel2.prop2
. -
ko.validation.validateObservable
уведомляется посредством подписки об этом изменении. - Функция
validateAsync
вызывается. -
$.ajax
пользовательский асинхронный валидатор, который отправляет асинхронный вызов$.ajax
на сервер и завершает работу. -
ko.validation.validateObservable
устанавливает для__valid__
наблюдаемого значениеtrue
и завершается. - Сервер возвращает неверный ответ, и
callBack(false)
выполняется. - Функция
callBack
устанавливает для__valid__
значениеfalse
. -
ko.validation.validateObservable
уведомляется об изменении наблюдаемой__valid__
(callBack
изменил его сtrue
наfalse
). По сути, это повторяет шаг 2 выше. - Шаги 3, 4 и 5 выше повторяются.
- Поскольку наблюдаемое значение не изменилось, сервер возвращает другой недопустимый ответ, инициируя шаги 6, 7, 8 и 9 выше.
- У нас есть матч для пинг-понга.
Таким образом, похоже, что проблема в том, что ko.validation.validateObservable
подписки ko.validation.validateObservable
прослушивает изменения не только входного значения пользователя, но и его вложенного __valid__
наблюдаемого. Это ошибка или я что-то не так делаю?
Вторичный вопрос
Из ko.validation
источников ko.validation
что пользовательское ko.validation
значение с асинхронным валидатором считается действительным, пока его ko.validation
сервер. Из-за этого на вызов nestedModel2.isValid()
нельзя полагаться как на "правду". Вместо этого, похоже, что мы должны использовать хуки isValidating
для создания подписок на асинхронные валидаторы и принимать эти решения только после того, как они сообщат значение false
. Это по замыслу? По сравнению с остальной частью библиотеки это кажется наиболее противоречивым, поскольку не асинхронные валидаторы не имеют isValidating
для подписки и могут полагаться на .isValid()
чтобы сказать правду. Это тоже задумано, или я здесь тоже что-то не так делаю?