jQuery Validate: requiere que se complete al menos un campo en un grupo


98

Estoy usando el excelente complemento jQuery Validate para validar algunos formularios. En un formulario, necesito asegurarme de que el usuario complete al menos uno de un grupo de campos. Creo que tengo una solución bastante buena y quería compartirla. Sugiera cualquier mejora que se le ocurra.

Al no encontrar una forma incorporada de hacer esto, busqué y encontré el método de validación personalizado de Rebecca Murphey , que fue muy útil.

Mejoré esto de tres maneras:

  1. Para permitirle pasar un selector para el grupo de campos
  2. Para permitirle especificar cuántos de ese grupo se deben completar para que la validación pase
  3. Para mostrar todas las entradas del grupo como validación aprobada tan pronto como una de ellas pasa la validación. (Ver agradecimiento a Nick Craver al final).

Por lo tanto, puede decir "se deben completar al menos X entradas que coincidan con el selector Y".

El resultado final, con un marcado como este:

<input class="productinfo" name="partnumber">
<input class="productinfo" name="description">

... es un grupo de reglas como este:

// Both these inputs input will validate if 
// at least 1 input with class 'productinfo' is filled
partnumber: {
   require_from_group: [1,".productinfo"]
  }
description: {
   require_from_group: [1,".productinfo"]
}

El elemento n. ° 3 asume que está agregando una clase de .checkeda sus mensajes de error después de una validación exitosa. Puede hacer esto de la siguiente manera, como se demuestra aquí .

success: function(label) {  
        label.html(" ").addClass("checked"); 
}

Como en la demostración vinculada anteriormente, uso CSS para dar a cada uno span.erroruna imagen X como fondo, a menos que tenga la clase .checked, en cuyo caso obtiene una imagen de marca de verificación.

Aquí está mi código hasta ahora:

jQuery.validator.addMethod("require_from_group", function(value, element, options) {
    var numberRequired = options[0];
    var selector = options[1];
    //Look for our selector within the parent form
    var validOrNot = $(selector, element.form).filter(function() {
         // Each field is kept if it has a value
         return $(this).val();
         // Set to true if there are enough, else to false
      }).length >= numberRequired;

    // The elegent part - this element needs to check the others that match the
    // selector, but we don't want to set off a feedback loop where each element
    // has to check each other element. It would be like:
    // Element 1: "I might be valid if you're valid. Are you?"
    // Element 2: "Let's see. I might be valid if YOU'RE valid. Are you?"
    // Element 1: "Let's see. I might be valid if YOU'RE valid. Are you?"
    // ...etc, until we get a "too much recursion" error.
    //
    // So instead we
    //  1) Flag all matching elements as 'currently being validated'
    //  using jQuery's .data()
    //  2) Re-run validation on each of them. Since the others are now
    //     flagged as being in the process, they will skip this section,
    //     and therefore won't turn around and validate everything else
    //  3) Once that's done, we remove the 'currently being validated' flag
    //     from all the elements
    if(!$(element).data('being_validated')) {
    var fields = $(selector, element.form);
    fields.data('being_validated', true);
    // .valid() means "validate using all applicable rules" (which 
    // includes this one)
    fields.valid();
    fields.data('being_validated', false);
    }
    return validOrNot;
    // {0} below is the 0th item in the options field
    }, jQuery.format("Please fill out at least {0} of these fields."));

¡Hurra!

Gritar

Ahora, para ese saludo: originalmente, mi código simplemente ocultaba ciegamente los mensajes de error en los otros campos coincidentes en lugar de volver a validarlos, lo que significaba que si había otro problema (como 'solo se permiten números e ingresó letras') , se ocultó hasta que el usuario intentó enviarlo. Esto se debió a que no sabía cómo evitar el ciclo de retroalimentación mencionado en los comentarios anteriores. Sabía que debía haber una forma, así que hice una pregunta y Nick Craver me iluminó. ¡Gracias, Nick!

Pregunta resuelta

Esta fue originalmente una pregunta del tipo "déjame compartir esto y ver si alguien puede sugerir mejoras". Si bien aún agradecería comentarios, creo que en este momento está bastante completo. (Podría ser más corto, pero quiero que sea fácil de leer y no necesariamente conciso). ¡Así que disfrútalo!

Actualización: ahora parte de la validación de jQuery

Esto se agregó oficialmente a jQuery Validation el 4/3/2012.


Además, consulte la regla estrechamente relacionada: "O salte estos campos o complete al menos X de ellos" - stackoverflow.com/questions/1888976/…
Nathan Long

¿Por qué una entrada arbitraria sería responsable de verificar si se llenan otras entradas? Esto no tiene sentido. ¿Quizás podría incluir un poco de marcado con los elementos involucrados?
Montreal

@dalbaeb - Aclaré un poco el ejemplo. No es que una entrada arbitraria sea responsable de verificar otras; es que cada entrada en un grupo es responsable de verificar todas las demás.
Nathan Long

Eso es lo que pensé, ¡muchas gracias!
Montreal

3
Gracias, esto funciona para mí, pero los otros campos obligatorios en el formulario ya no responden a menos que ganen y pierdan el foco después de la verificación. (Alguien agregó esto como respuesta a su otra pregunta, pero tuvo que marcarlo porque no es una respuesta).
mydoghasworms

Respuestas:


21

Esa es una excelente solución Nathan. Muchas gracias.

Aquí hay una forma de hacer que el código anterior funcione, en caso de que alguien tenga problemas para integrarlo, como hice yo:

Código dentro del archivo additional-methods.js :

jQuery.validator.addMethod("require_from_group", function(value, element, options) {
...// Nathan's code without any changes
}, jQuery.format("Please fill out at least {0} of these fields."));

// "filone" is the class we will use for the input elements at this example
jQuery.validator.addClassRules("fillone", {
    require_from_group: [1,".fillone"]
});

Código dentro del archivo html :

<input id="field1" class="fillone" type="text" value="" name="field1" />
<input id="field2" class="fillone" type="text" value="" name="field2" />
<input id="field3" class="fillone" type="text" value="" name="field3" />
<input id="field4" class="fillone" type="text" value="" name="field4" />

¡No olvide incluir el archivo additional-methods.js!


Me alegra que te sea útil y gracias por aportar información. Sin embargo, en lugar de utilizar el método addClassRules, prefiero utilizar una serie de reglas en cada formulario individual. Si va a esta página ( jquery.bassistance.de/validate/demo/milk ) y hace clic en "mostrar el script utilizado en esta página", verá un ejemplo. Doy un paso más: declaro una matriz llamada "reglas", luego, por separado, las uso con var validator = $ ('# formtovalidate'). Validate (reglas);
Nathan Long

Otro pensamiento: la clase 'fillone' que muestra aquí podría ser problemática. ¿Qué sucede si, en el mismo formulario, necesita solicitar al menos un número de pieza Y al menos un nombre de contacto? Su regla permitirá 0 nombres de contacto siempre que haya al menos un número de pieza. Creo que es mejor establecer reglas como require_from_group: [1,".partnumber"]y ...[1,".contactname"]asegurarse de que está validando las cosas correctas.
Nathan Long

6

Buena solucion. Sin embargo, tuve el problema de que otras reglas requeridas no funcionaban. La ejecución de .valid () contra el formulario solucionó este problema.

if(!$(element).data('being_validated')) {
  var fields = $(selector, element.form);
  fields.data('being_validated', true); 
  $(element.form).valid();
  fields.data('being_validated', false);
}

1
Gracias Sean, yo también estaba teniendo este problema. Sin embargo, hay un problema con esta solución, cuando el usuario accede al formulario por primera vez: tan pronto como complete el primer campo requerido del grupo, todos los demás campos del formulario se validarán y, por lo tanto, se marcarán como defectuosos. La forma en que resolví esto fue agregar un controlador form.submit () antes de crear una instancia del validador, en la que configuré una bandera validator.formSubmit = true. En el método require-from-group, luego verifico esa bandera; si está ahí lo hago $(element.form).valid();, de lo contrario lo hago fields.valid();.
Christof

¿Alguien puede explicar qué está sucediendo realmente aquí? Tengo una regla bastante similar, que ha funcionado, pero en la que no hemos abordado el problema de revalidación (otros campos del grupo todavía están marcados como no válidos). Pero ahora estoy experimentando que el formulario se envía incluso si no es válido. Si los campos agrupados son válidos, entonces no ingresa al submithandler y si no es válido ingresa invalidHandler, ¡pero envía de todos modos! ¿Diría que este es un error bastante grave en el complemento de validación? Que una regla devuelva válida se aplica solo a esa regla (ni siquiera a todo el campo), entonces, ¿por qué se envía un formulario no válido?
Adam

He investigado más y son los campos antes del grupo los que no se validan correctamente. He hecho esto como una pregunta separada (con una solución parcial que he descubierto): stackoverflow.com/questions/12690898/…
Adam

4

Gracias sean. Eso solucionó el problema que tuve con el código ignorando otras reglas.

También hice algunos cambios para que el mensaje 'Por favor complete al menos 1 campo ...' se muestre en un div separado en lugar de después de todos los campos.

poner en forma validar script

showErrors: function(errorMap, errorList){
            $("#form_error").html("Please fill out at least 1 field before submitting.");
            this.defaultShowErrors();
        },

agregue esto en algún lugar de la página

<div class="error" id="form_error"></div>

agregar al método require_from_group función addMethod

 if(validOrNot){
    $("#form_error").hide();
}else{
    $("#form_error").show();
}
......
}, jQuery.format(" &nbsp;(!)"));

4

Envié un parche que no sufre los problemas de la versión actual (por lo que la opción "requerida" deja de funcionar correctamente en otros campos, una discusión sobre los problemas con la versión actual está en github .

Ejemplo en http://jsfiddle.net/f887W/10/

jQuery.validator.addMethod("require_from_group", function (value, element, options) {
var validator = this;
var minRequired = options[0];
var selector = options[1];
var validOrNot = jQuery(selector, element.form).filter(function () {
    return validator.elementValue(this);
}).length >= minRequired;

// remove all events in namespace require_from_group
jQuery(selector, element.form).off('.require_from_group');

//add the required events to trigger revalidation if setting is enabled in the validator
if (this.settings.onkeyup) {
    jQuery(selector, element.form).on({
        'keyup.require_from_group': function (e) {
            jQuery(selector, element.form).valid();
        }
    });
}

if (this.settings.onfocusin) {
    jQuery(selector, element.form).on({
        'focusin.require_from_group': function (e) {
            jQuery(selector, element.form).valid();
        }
    });
}

if (this.settings.click) {
    jQuery(selector, element.form).on({
        'click.require_from_group': function (e) {
            jQuery(selector, element.form).valid();
        }
    });
}

if (this.settings.onfocusout) {
    jQuery(selector, element.form).on({
        'focusout.require_from_group': function (e) {
            jQuery(selector, element.form).valid();
        }
    });
}

return validOrNot;
}, jQuery.format("Please fill at least {0} of these fields."));

3

En PHP se requiere comenzar un nombre de variable con $, pero bastante extraño (en mi humilde opinión) en Javascript. Además, creo que se refiere a él como "$ módulo" dos veces y "módulo" una vez, ¿verdad? Parece que este código no debería funcionar.

Además, no estoy seguro de si es la sintaxis normal del complemento jQuery, pero podría agregar comentarios sobre su llamada addMethod, explicando lo que logra. Incluso con la descripción de texto anterior, es difícil seguir el código, porque no estoy familiarizado con el conjunto de campos,: relleno, valor, elemento o selector al que se refieren. Quizás la mayor parte de esto sea obvio para alguien familiarizado con el complemento Validate, así que use su juicio sobre cuál es la cantidad correcta de explicación.

Quizás podría romper algunas vars para autodocumentar el código; me gusta,

var atLeastOneFilled = module.find(...).length > 0;
if (atLeastOneFilled) {
  var stillMarkedWithErrors = module.find(...).next(...).not(...);
  stillMarkedWithErrors.text("").addClass(...)

(¡asumiendo que entendí el significado de estos fragmentos de su código! :))

No estoy exactamente seguro de lo que significa "módulo", en realidad, ¿hay un nombre más específico que pueda darle a esta variable?

Buen código, en general!


Gracias por las sugerencias. He aclarado los nombres de las variables y desglosado el código para que sea un poco más legible.
Nathan Long

2

Debido a que el formulario en el que estoy trabajando tiene varias regiones clonadas con entradas agrupadas como estas, pasé un argumento adicional al constructor require_from_group, cambiando exactamente una línea de su función addMethod:

var commonParent = $(element).parents(options[2]);

y de esta manera un selector, ID o nombre de elemento se puede pasar una vez:

jQuery.validator.addClassRules("reqgrp", {require_from_group: [1, ".reqgrp", 'fieldset']});

y el validador restringirá la validación a elementos con esa clase solo dentro de cada conjunto de campos, en lugar de intentar contar todos los elementos clasificados .reqgrp en el formulario.


2

Aquí está mi respuesta a la respuesta de Rocket Hazmat, tratando de resolver el problema de otros campos definidos que también necesitan ser validados, pero marcando todos los campos como válidos en el llenado exitoso de uno.

jQuery.validator.addMethod("require_from_group", function(value, element, options){
    var numberRequired = options[0],
    selector = options[1],
    $fields = $(selector, element.form),
    validOrNot = $fields.filter(function() {
        return $(this).val();
    }).length >= numberRequired,
    validator = this;
    if(!$(element).data('being_validated')) {
        $fields.data('being_validated', true).each(function(){
            validator.valid(this);
        }).data('being_validated', false);
    }
    if (validOrNot) {
    $(selector).each(function() {
            $(this).removeClass('error');
            $('label.error[for='+$(this).attr('id')+']').remove();
        });
    }
    return validOrNot;
}, jQuery.format("Please fill out at least {0} of these fields."));

El único problema que queda con esto ahora es el caso límite en el que el campo está vacío, luego se llena, luego se vacía nuevamente ... en cuyo caso el error se aplicará al campo único, no al grupo. Pero eso parece muy poco probable que suceda con alguna frecuencia y todavía funciona técnicamente en ese caso.


No tiene sentido esta respuesta ya que este método / regla se integró en el complemento en abril de 2012.
Sparky

Tengo el mismo problema que tiene Rocket Hazmat con el método que ahora se envía con el validador. Valida que un grupo de campos, pero ningún otro campo que utilice otros métodos está validado. Esta respuesta es un intento de resolver ese problema. Si tiene una mejor solución, hágamelo saber.
Squarecandy

Hasta que el desarrollador solucione permanentemente el problema, en lugar de aumentar la confusión, simplemente recomiendo cualquier solución temporal que se respalde
Sparky

1

Tenía problemas con otras reglas que no se verificaban junto con esto, así que cambié:

fields.valid();

A esto:

var validator = this;
fields.each(function(){
   validator.valid(this);
});

También hice algunas mejoras (personales), y esta es la versión que estoy usando:

jQuery.validator.addMethod("require_from_group", function(value, element, options){
    var numberRequired = options[0],
    selector = options[1],
    $fields = $(selector, element.form),
    validOrNot = $fields.filter(function() {
        return $(this).val();
    }).length >= numberRequired,
    validator = this;
    if(!$(element).data('being_validated')) {
        $fields.data('being_validated', true).each(function(){
            validator.valid(this);
        }).data('being_validated', false);
    }
    return validOrNot;
}, jQuery.format("Please fill out at least {0} of these fields."));

Funciona haciendo que los otros campos se validen nuevamente, pero ahora, cuando todos los campos de un grupo están marcados como inválidos y usted llena uno, solo ese se valida. ¿Al menos para mi?
Christof

@Chris: vea mi nueva respuesta que se basa en esta y aborda esto.
squarecandy

0

Gracias, Nathan. Me ahorraste un montón de tiempo.

Sin embargo, debo notar que esta regla no está lista para jQuery.noConflict (). Entonces, uno debe reemplazar todos $ con jQuery para trabajar, digamos,var $j = jQuery.noConflict()

Y tengo una pregunta: ¿cómo haría que se comportara como una regla incorporada? Por ejemplo, si ingreso un correo electrónico, el mensaje "Ingrese un correo electrónico válido" desaparece automáticamente, pero si completo uno de los campos del grupo, el mensaje de error permanece.


Tienes razón, no consideré la situación sin conflicto. Puedo actualizar eso en el futuro, pero puede buscar y reemplazar fácilmente si lo desea. Para su segunda pregunta, no veo el mismo problema. Con una prueba rápida, si se requiere un campo de un grupo, tan pronto como escriba algo, todo el grupo pasa esa regla. Si se requiere más de uno, tan pronto como el último requerido se llena y pierde el enfoque, todo el grupo pasa. ¿Es eso lo que ves?
Nathan Long

hmm, por alguna razón, el marcado está arruinado y no tuve éxito en arreglarlo
Rinat

Rinat: ¿puede simplificar y reducir el problema? Intente usar mi código en una forma más simple que no necesite los cambios sin conflictos. Haz la forma más simple en la que puedas probarlo y haz que funcione primero.
Nathan Long
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.