15

Me gusta mucho cómo Eric Barnard's knockout validation lib se integra con observables, permite la agrupación, & ofrece pluggabilidad validador personalizado (incluidos los validadores sobre la marcha). Hay un par de lugares donde podría ser más flexible/amigable con UX, pero en general está razonablemente bien documentado ... except, imo, when it comes to async validators.Validadores asincrónicos de validación no autorizada: ¿Esto es un error o estoy haciendo algo mal?

Luché con esto por unas pocas horas hoy antes de hacer una búsqueda y landing on this. I think 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 de la pregunta, así que también estoy preguntando 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 n. ° 1 no tiene validadores asincrónicos, y el modelo anidado n. ° 2 tiene una sincronización (requerida) y una asincrónica. La función async invoca una llamada al servidor para validar las entradas. Cuando el servidor responde, el argumento callback se usa para indicar ko.validation si la entrada del usuario es buena o mala. Si coloca puntos de interrupción en las líneas indicadas y desencadena la validación utilizando un valor inválido conocido, termina con un bucle infinito donde la función ajax success hace que se vuelva a llamar a la función validator. Rompí la fuente 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 es en una cadena de suscripción unido a la entrada del usuario observable de manera que cuando sus cambios de valor, se validarán el nuevo valor. El algoritmo realiza un ciclo sobre cada validador conectado a la entrada y ejecuta funciones separadas dependiendo de si el validador es asincrónico o no. Si la validación de sincronización falla, el ciclo se interrumpe y la función completa validateObservable se cierra. Si se aprueban todos los validadores de sincronización, se ejecutan las últimas 3 líneas, indicando esencialmente 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); 

Dos cosas se lleve de esto: __valid__ es un observable, y que se establece en true después de los validateAsync sale de la función. Ahora vamos a echar 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 sólo la primera y la última línea de esta función se ejecutan antes ko.validation.validateObservable establece el __valid__ observables en true y salidas. La función callBack es lo que se pasa como el tercer parámetro de la función async validator declarada en MyViewModel. Sin embargo, antes de que esto ocurra, se invocan los suscriptores de un observador isValidating para notificar que la validación asíncrona ha comenzado. Cuando se completa la llamada al servidor, se invoca la devolución de llamada (en este caso, solo pasa verdadero o falso).

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

  1. La entrada de usuario no válida cambia el nestedModel2.prop2 observable.
  2. El ko.validation.validateObservable se notifica mediante 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 asincrónica $.ajax al servidor y se cierra.
  5. ko.validation.validateObservableestablece __valid__ observable en true y sale.
  6. El servidor devuelve una respuesta no válida y se ejecuta callBack(false).
  7. La función callBack establece __valid__ en false.
  8. El ko.validation.validateObservable se notifica el cambio a la __valid__ observable (callBack cambiaron de true a false) Esto se repite esencialmente el paso 2 anterior.
  9. Se repiten los pasos 3, 4 y 5 anteriores.
  10. Dado que el valor del observable no ha cambiado, el servidor devuelve otra respuesta no válida, activando los pasos 6, 7, 8, & 9 arriba.
  11. Tenemos nosotros mismos un partido de ping pong.

así que parece que el problema es que el manejador de suscripción ko.validation.validateObservable está escuchando a los cambios no sólo al valor de entrada del usuario, sino que también cambia a su anidada __valid__ observable. ¿Es esto un error, o estoy haciendo algo mal?

Una cuestión secundaria

Se puede ver en las fuentes anteriores ko.validation que un valor de entrada del usuario con un validador asíncrono se trata como válida mientras el servidor está validando la misma. Debido a esto, no se puede confiar en llamar al "nestedModel2.isValid()" por "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. ¿Esto es por diseño? Comparado con el resto de la biblioteca, este parece ser el más intuitivo porque no validadores asincrónicos no tienen un isValidating para suscribirse, y puede confiar en .isValid() para decir la verdad. ¿Esto también es por diseño, o estoy haciendo algo mal aquí también?

+0

Ha hecho un gran esfuerzo en esta cuestión, lamento ver que no ha recibido ninguna respuesta. Solo soy nuevo en la validación KO, así que realmente no puedo ayudarte. – GONeale

+3

Hay algunos problemas con esta pregunta. La pregunta real que se hace no es clara y está enterrada en un enlace externo. Tuve que leer varios párrafos de texto antes de darme cuenta de esto. Esta pregunta tiene demasiado contexto; No puedo ver el bosque por los árboles. Y parece que estás haciendo preguntas múltiples. Sugerencia: cree la reproducción más simple del problema en un JSFiddle, enlace a él en su pregunta y formule una pregunta concisa en el primer párrafo. Haga esto y será más probable que obtenga algunas respuestas. –

+0

¿Se puede actualizar o posiblemente responder a su pregunta? Veo que ha encontrado una solución a partir de esta url ... https://github.com/ericmbarnard/Knockout-Validation/issues/145. Su respuesta ayudará a muchas personas ya que no hay mucha información sobre la validación asincrónica. – bflemi3

Respuesta

14

Así que la pregunta que hice realmente tenía que ver con el uso de validadores asíncronos en ko.validation. Hay 2 grandes de comida para llevar que he aprendido de mi experiencia:

  1. No cree asyncAnonymous or Single-Use Custom Rule validators. En cambio, créelos como Custom Rules. De lo contrario, terminará con la coincidencia infinito bucle/ping ping descrito en mi pregunta.

  2. Si utiliza async validadores, no confían en isValid() hasta que todos los validadores asyncisValidatingsubscriptions cambio en falso.

Si tiene varios validadores asincrónicos, se puede utilizar 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 see this for another reference with similar alternate solutions.

Aquí es un ejemplo de una "regla personalizada" asíncrono:

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 arg callback a la función validator para contar ko.validation si o no la validación tuvo éxito. Esa llamada es lo que activará los observables isValidating en los elementos observables de la propiedad validados para volver a cambiar a false (es decir, la validación asincrónica se ha completado y ahora se sabe si la entrada fue válida o no).

Lo anterior funcionará si sus puntos finales de validación del lado del servidor devuelven un estado HTTP 200 (OK) cuando la validación tiene éxito. Eso causará que se ejecute la función .done, ya que es el equivalente al $.ajaxsuccess. Si su servidor devuelve un estado de HTTP 400 (Solicitud incorrecta) cuando la validación falla, activará la función .fail para que se ejecute. Si su servidor devuelve un mensaje de validación personalizado con el 400, puede obtenerlo desde xhr.responseText para anular efectivamente el mensaje predeterminado you suck because your input was wrong fix it or else.

+0

Gracias, esto me ayudó mucho :) – bflemi3

+0

¿Te importaría también dar un ejemplo de tu regla de validación 'validateProp1Async'? – bflemi3

+0

En la página de reglas de sincronización en la wiki de validación directa (https://github.com/ericmbarnard/Knockout-Validation/wiki/Async-Rules) el ejemplo de ajax que se muestra me confunde. El 'validator' no devuelve verdadero o falso. ¿Cómo validará la regla el valor si no devuelve verdadero o falso? Es por eso que pedí ver su regla de validación. Perdón por todas las preguntas, no hay mucho por ahí en términos de documentación y que no sean de Eric Barnard, eres el único que he visto que parece comprender exactamente cómo funciona la sincronización. – bflemi3

Cuestiones relacionadas