Мне очень нравится, как библиотека проверки нокаута Эрика Барнарда интегрируется с наблюдаемыми, позволяет группировать и предлагать возможность подключения настраиваемых валидаторов (включая валидаторы «на лету»). Есть несколько мест, где он мог бы быть более гибким/дружественным к UX, но в целом он достаточно хорошо документирован... за исключением, 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
, является ли ввод пользователя хорошим или плохим. Если вы поставите точки останова на указанные строки и активируете проверку, используя заведомо недопустимое значение, вы получите бесконечный цикл, в котором функция 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
завершает работу. Если все валидаторы синхронизации проходят успешно, выполняются последние 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
— это то, что передается в качестве третьего параметра асинхронной функции validator
, объявленной в MyViewModel
. Однако до того, как это произойдет, вызываются подписчики наблюдаемого isValidating
, чтобы уведомить о начале асинхронной проверки. Когда вызов сервера завершен, вызывается обратный вызов (в данном случае просто передается значение true или false).
Теперь вот почему точки останова в MyViewModel
вызывают бесконечный цикл пинг-понга при сбое проверки на стороне сервера: Обратите внимание, что в вышеприведенной функции callBack
наблюдаемое значение __valid__
устанавливается в false, когда проверка не удалась. Вот что происходит:
- Недопустимый пользовательский ввод изменяет наблюдаемую
nestedModel2.prop2
. ko.validation.validateObservable
уведомляется через подписку об этом изменении.- Вызывается функция
validateAsync
. - Вызывается настраиваемый асинхронный валидатор, который отправляет асинхронный вызов
$.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
прослушивает изменения не только значения пользовательского ввода, но также изменений своего вложенного наблюдаемого объекта __valid__
. Это баг, или я что-то не так делаю?
Второстепенный вопрос
Вы можете видеть из ko.validation
источников выше, что введенное пользователем значение с асинхронным валидатором рассматривается как действительное, пока сервер проверяет его. Из-за этого на звонок nestedModel2.isValid()
нельзя полагаться на «правду». Вместо этого похоже, что мы должны использовать хуки isValidating
для создания подписок на асинхронные валидаторы и принимать эти решения только после того, как они уведомят о значении false
. Это по дизайну? По сравнению с остальной частью библиотеки это кажется наиболее нелогичным, потому что не асинхронные валидаторы не имеют isValidating
для подписки и могут полагаться на .isValid()
, чтобы говорить правду. . Это тоже так задумано, или я тоже что-то не так делаю?