javascript asynchronous knockout.js knockout-2.0 knockout-validation

javascript - Validación de Knockout Validadores asíncronos: ¿es esto un error o estoy haciendo algo mal?



asynchronous knockout.js (2)

Así que la pregunta que hice realmente tenía que ver con cómo usar los validadores asíncronos en ko.validation. Hay 2 grandes conclusiones que he aprendido de mi experiencia:

  1. No cree validadores de regla personalizados async anónimos o de uso único . En su lugar, crearlos como reglas personalizadas . De lo contrario, terminará con la combinación de ping / ping infinito que se describe en mi pregunta.

  2. Si usa validadores async , no confíe en isValid() hasta que todos los validadores async las subscriptions isValidating cambien a falso.

Si tiene varios validadores asíncronos, puede usar un patrón como el siguiente:

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() }); } };

También puede ver esto para otra referencia con soluciones alternativas similares .

Aquí hay un ejemplo de una "regla personalizada" asíncrona:

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 }); }); } };

Básicamente, se utiliza el argumento de callback a la función de validator para indicar a ko.validation si la validación tuvo éxito o no. Esa llamada es lo que hará que los observables de validación en la propiedad validada observable vuelvan a ser false (es decir, se haya completado la validación asíncrona y ahora se sabe si la entrada fue válida o no).

Lo anterior funcionará si los puntos finales de validación del lado del servidor devuelven un estado HTTP 200 (OK) cuando la validación se realiza correctamente. Eso hará que se .done función .done , ya que es el equivalente al success $.ajax . Si su servidor devuelve un estado HTTP 400 (Solicitud incorrecta) cuando falla la validación, se activará la función .fail . Si su servidor devuelve un mensaje de validación personalizado con el 400, puede obtenerlo de xhr.responseText para anular de manera efectiva el valor predeterminado you suck because your input was wrong fix it or else mensaje.

Realmente me gusta la forma en que la biblioteca de validación de eliminación directa de Eric Barnard se integra con los observables, permite la agrupación y ofrece conectividad del validador personalizado (incluidos los validadores sobre la marcha). Hay un par de lugares donde podría ser más flexible y amigable con el UX, pero en general está razonablemente bien documentado ... excepto, imo, cuando se trata de validadores asíncronos .

Luché con esto por unas horas hoy antes de hacer una búsqueda y aterrizar en esto . Creo que tengo los mismos problemas / preguntas que el autor original, pero estoy de acuerdo en que no estaba claro exactamente qué estaba pidiendo Duxa. Quiero llamar más la atención a la pregunta, así que también pregunto aquí.

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);

Un par de notas sobre el código anterior: hay 2 grupos de validación separados, uno para cada modelo anidado. El modelo anidado # 1 no tiene validadores asíncronos, y el modelo anidado # 2 tiene una sincronización (requerida) y una asíncrona. El async invoca una llamada del servidor para validar las entradas. Cuando el servidor responde, el argumento de callback se utiliza para indicar a ko.validation si la entrada del usuario es buena o mala. Si coloca puntos de interrupción en las líneas indicadas y activa la validación utilizando un valor inválido conocido, terminará con un bucle infinito donde la función de success ajax hace que se vuelva a llamar a la función de validator . ko.validation fuente de ko.validation para ver qué estaba pasando.

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; }

Esta función se encuentra en una cadena de suscripción adjunta a la entrada del usuario observable, de modo que cuando su valor cambie, el nuevo valor será validado. El algoritmo recorre cada validador adjunto a la entrada y ejecuta funciones separadas dependiendo de si el validador es asíncrono o no. Si la validación de la sincronización falla, el bucle se rompe y la función validateObservable completa se cierra. Si todos los validadores de sincronización pasan, las últimas 3 líneas se ejecutan, esencialmente indicando a ko.validation que esta entrada es válida. La función __valid__ en la biblioteca se ve así:

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

Hay dos cosas que quitar de esto: __valid__ es un observable, y se establece en true después de que la función validateAsync sale. Ahora echemos un vistazo a 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); }

Es importante tener en cuenta que solo las primeras y últimas líneas de esta función se ejecutan antes de que ko.validation.validateObservable establezca el __valid__ observable en verdadero y salga. La función callBack es lo que se pasa como el tercer parámetro a la función de validator asíncrona declarada en MyViewModel . Sin embargo, antes de que esto suceda, se isValidating los suscriptores de un observable isValidating para notificar que la validación asíncrona ha comenzado. Cuando se completa la llamada del servidor, se invoca la devolución de llamada (en este caso, simplemente se pasa verdadero o falso).

Ahora bien, aquí está la razón por la cual los puntos de interrupción en MyViewModel están causando un bucle infinito de ping pong cuando falla la validación del lado del servidor: En la función callBack anterior, observe cómo el observable __valid__ se establece en falso cuando falla la validación. Esto es lo que sucede:

  1. La entrada de usuario no válida cambia el observable nestedModel2.prop2 .
  2. El ko.validation.validateObservable se notifica a través de la suscripción de este cambio.
  3. Se invoca la función validateAsync .
  4. Se invoca el validador asíncrono personalizado, que envía una llamada asíncrona $.ajax al servidor y sale.
  5. El ko.validation.validateObservable establece el __valid__ observable en true y sale .
  6. El servidor devuelve una respuesta no válida y se callBack(false) .
  7. La función callBack establece __valid__ en false .
  8. El ko.validation.validateObservable se notifica del cambio al __valid__ observable ( callBack cambió de true a false ) Esto esencialmente repite el paso 2 anterior.
  9. Los pasos 3, 4 y 5 anteriores se repiten.
  10. Como el valor del observable no ha cambiado, el servidor devuelve otra respuesta no válida, lo que desencadena los pasos 6, 7, 8 y 9 anteriores.
  11. Tenemos un partido de ping pong.

Entonces, parece que el problema es que el controlador de suscripción ko.validation.validateObservable está escuchando los cambios no solo en el valor de entrada del usuario, sino también en su __valid__ anidado observable. ¿Es esto un error, o estoy haciendo algo mal?

Una pregunta secundaria

Puede ver en las fuentes de ko.validation arriba que un valor de entrada de usuario con un validador asíncrono se considera válido mientras el servidor lo está validando. Debido a esto, no se puede confiar en que se llame a nestedModel2.isValid() para "la verdad". En su lugar, parece que tenemos que usar los ganchos isValidating para crear suscripciones a los validadores asíncronos, y solo tomar estas decisiones después de que notifiquen un valor de false . ¿Es esto por diseño? Comparado con el resto de la biblioteca, este parece ser el más intuitivo, ya que los validadores no asíncronos no tienen una isValidating de isValidating a la cual suscribirse, y pueden confiar en .isValid() para decir la verdad. ¿Es esto también por diseño, o estoy haciendo algo mal aquí también?


Tuve el mismo problema, anidados observables con validación. Así que la única magia: in self.errors = ko.validation.group(self.submissionAnswers, { deep: true, live: true }); preste atención en param adicional adicional: objeto que contiene el campo en live: true