jQuery Validate - nécessite au moins un champ dans un groupe à remplir

98

J'utilise l'excellent plug-in jQuery Validate pour valider certains formulaires. Sur un formulaire, je dois m'assurer que l'utilisateur remplit au moins l'un d'un groupe de champs. Je pense que j'ai une assez bonne solution et je voulais la partager. Veuillez suggérer les améliorations auxquelles vous pourriez penser.

Ne trouvant aucun moyen intégré de le faire, j'ai recherché et trouvé la méthode de validation personnalisée de Rebecca Murphey , ce qui était très utile.

J'ai amélioré cela de trois manières:

  1. Pour vous laisser passer un sélecteur pour le groupe de champs
  2. Pour vous permettre de spécifier combien de ce groupe doit être rempli pour que la validation réussisse
  3. Pour afficher toutes les entrées du groupe comme ayant réussi la validation dès que l'une d'elles réussit la validation. (Voir le cri à Nick Craver à la fin.)

Vous pouvez donc dire "au moins X entrées correspondant au sélecteur Y doivent être renseignées".

Le résultat final, avec un balisage comme celui-ci:

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

... est un groupe de règles comme celui-ci:

// 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"]
}

L'élément n ° 3 suppose que vous ajoutez une classe de .checkedà vos messages d'erreur après une validation réussie. Vous pouvez le faire comme suit, comme illustré ici .

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

Comme dans la démo liée ci-dessus, j'utilise CSS pour donner à chaque span.errorimage X comme arrière-plan, à moins qu'elle n'ait la classe .checked, auquel cas elle obtient une image de coche.

Voici mon code pour l'instant:

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

Hourra!

Hurler

Maintenant, pour ce cri - à l'origine, mon code cachait aveuglément les messages d'erreur sur les autres champs correspondants au lieu de les revalider, ce qui signifiait que s'il y avait un autre problème (comme `` seuls les nombres sont autorisés et vous avez entré des lettres '') , il a été masqué jusqu'à ce que l'utilisateur tente de soumettre. C'était parce que je ne savais pas comment éviter la boucle de rétroaction mentionnée dans les commentaires ci-dessus. Je savais qu'il devait y avoir un moyen, alors j'ai posé une question et Nick Craver m'a éclairé. Merci Nick!

Question résolue

C'était à l'origine une question «laissez-moi partager ceci et voir si quelqu'un peut suggérer des améliorations». Bien que j'apprécie toujours les commentaires, je pense qu'ils sont assez complets à ce stade. (Cela pourrait être plus court, mais je veux que ce soit facile à lire et pas nécessairement concis.) Alors profitez-en!

Mise à jour - fait désormais partie de jQuery Validation

Cela a été officiellement ajouté à jQuery Validation le 4/3/2012.

Nathan Long
la source
En outre, voir la règle étroitement liée - « Soit ignorer ces champs, ou remplir au moins X d'entre eux » - stackoverflow.com/questions/1888976/...
Nathan long
Pourquoi une entrée arbitraire serait-elle chargée de vérifier si d'autres entrées sont remplies? Cela n'a pas de sens. Peut-être pourriez-vous inclure un peu de balisage avec les éléments impliqués?
montréaliste
@dalbaeb - J'ai clarifié un peu l'exemple. Ce n'est pas qu'une entrée arbitraire soit responsable de la vérification des autres; c'est que chaque entrée d'un groupe est chargée de vérifier toutes les autres.
Nathan Long
C'est ce que j'ai pensé, merci beaucoup!
montrealist
3
Merci, cela fonctionne pour moi, mais les autres champs obligatoires du formulaire ne répondent plus à moins qu'ils ne gagnent et perdent leur concentration après la vérification. (Quelqu'un a ajouté ceci comme réponse à votre autre question, mais elle a dû être signalée car ce n'est pas une réponse)
mydoghasworms

Réponses:

21

C'est une excellente solution Nathan. Merci beaucoup.

Voici un moyen de faire fonctionner le code ci-dessus, au cas où quelqu'un aurait du mal à l'intégrer, comme je l'ai fait:

Code dans le fichier 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"]
});

Code à l'intérieur du fichier 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" />

N'oubliez pas d'inclure le fichier additional-methods.js!


la source
Heureux que cela vous soit utile, et merci de nous avoir fourni des informations. Cependant, au lieu de faire la méthode addClassRules, je préfère utiliser un tableau de règles sur chaque formulaire individuel. Si vous allez sur cette page ( jquery.bassistance.de/validate/demo/milk ) et cliquez sur "Afficher le script utilisé sur cette page", vous verrez un exemple. Je vais encore plus loin: je déclare un tableau appelé "rules", puis séparément, je les utilise avec var validator = $ ('# formtovalidate'). Validate (rules);
Nathan Long
Une autre pensée: la classe 'fillone' que vous montrez ici pourrait être problématique. Que faire si, sur le même formulaire, vous devez exiger au moins un numéro de pièce ET au moins un nom de contact? Votre règle autorisera 0 noms de contact tant qu'il y aura au moins un numéro de pièce. Je pense qu'il vaut mieux définir des règles comme require_from_group: [1,".partnumber"]et ...[1,".contactname"]s'assurer que vous validez les bonnes choses.
Nathan Long
6

Belle solution. Cependant, j'ai eu le problème d'autres règles obligatoires qui ne fonctionnaient pas. L'exécution de .valid () sur le formulaire a résolu ce problème pour moi.

if(!$(element).data('being_validated')) {
  var fields = $(selector, element.form);
  fields.data('being_validated', true); 
  $(element.form).valid();
  fields.data('being_validated', false);
}
sean
la source
1
Merci Sean, j'avais aussi ce problème. Il y a cependant un problème avec cette solution, lorsque l'utilisateur accède au formulaire pour la première fois - dès qu'il remplit le premier champ require-from-group, tous les autres champs du formulaire seront validés et donc marqués comme défectueux. La façon dont j'ai résolu ce problème était d'ajouter un gestionnaire form.submit () avant de créer une instance du validateur, dans laquelle j'ai défini un indicateur validator.formSubmit = true. Dans la méthode require-from-group, je vérifie ensuite cet indicateur; si c'est là, je le fais $(element.form).valid();, sinon je le fais fields.valid();.
Christof
Quelqu'un peut-il expliquer ce qui se passe réellement ici? J'ai une règle assez similaire, qui a fonctionné mais dans laquelle nous n'avions pas résolu le problème de revalidation (les autres champs du groupe sont toujours marqués comme invalides). Mais maintenant, je constate que le formulaire se soumet même s'il n'est pas valide. Si les champs groupés sont valides, alors il n'entre pas dans le submithandler et s'il n'est pas valide, il entre invalidHandler mais soumet quand même! Je dirais que c'est un bug assez sérieux dans le plugin de validation? Le fait qu'une règle renvoie valide s'applique uniquement à cette règle (pas même à l'ensemble du champ), alors pourquoi un formulaire non valide est-il soumis?
Adam
J'ai enquêté plus avant et ce sont les champs avant le groupe qui ne valident pas correctement. J'ai posé cela comme une question distincte (avec une solution de contournement partielle que j'ai découverte): stackoverflow.com/questions/12690898/...
Adam
4

Merci sean. Cela a résolu le problème que j'avais avec le code ignorant les autres règles.

J'ai également apporté quelques modifications afin que le message «Veuillez remplir au moins 1 champ ..» s'affiche dans un div distinct au lieu de chaque champ après tout.

mettre en forme valider le script

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

ajoutez ceci quelque part dans la page

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

ajouter à la méthode require_from_group fonction addMethod

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

J'ai soumis un correctif qui ne souffre pas des problèmes de la version actuelle (par lequel l'option "requis" cesse de fonctionner correctement sur d'autres champs, une discussion des problèmes avec la version actuelle est sur github .

Exemple sur 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."));
docflabby
la source
3

Commencer un nom de variable avec $ est requis en PHP, mais assez bizarre (IMHO) en Javascript. De plus, je crois que vous l'appelez deux fois "$ module" et "module" une fois, non? Il semble que ce code ne devrait pas fonctionner.

De plus, je ne suis pas sûr que ce soit la syntaxe normale du plugin jQuery, mais je pourrais ajouter des commentaires au-dessus de votre appel addMethod, expliquant ce que vous accomplissez. Même avec votre description textuelle ci-dessus, il est difficile de suivre le code, car je ne sais pas à quel jeu de champs,: rempli, valeur, élément ou sélecteur fait référence. Peut-être que la plupart de cela est évident pour quelqu'un qui connaît le plugin Validate, alors faites preuve de jugement pour savoir quelle est la bonne quantité d'explications.

Vous pourriez peut-être sortir quelques variables pour auto-documenter le code; comme,

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

(en supposant que j'ai compris la signification de ces morceaux de votre code! :))

Je ne sais pas exactement ce que signifie "module", en fait - y a-t-il un nom plus spécifique que vous pourriez donner à cette variable?

Beau code, dans l'ensemble!

Michael Gundlach
la source
Merci pour les suggestions - j'ai clarifié les noms des variables et décomposé le code pour être un peu plus lisible.
Nathan Long
2

Parce que le formulaire sur lequel je travaille a plusieurs régions clonées avec des entrées groupées comme celles-ci, j'ai passé un argument supplémentaire au constructeur require_from_group, en changeant exactement une ligne de votre fonction addMethod:

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

et de cette façon, un sélecteur, un ID ou un nom d'élément peut être passé une fois:

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

et le validateur limitera la validation aux éléments avec cette classe uniquement à l'intérieur de chaque fieldset, plutôt que d'essayer de compter tous les éléments classés .reqgrp dans le formulaire.

Andrew Roazen
la source
2

Voici ma réponse à la réponse de Rocket Hazmat, en essayant de résoudre le problème des autres champs définis qui doivent également être validés, mais en marquant tous les champs comme valides lors du remplissage réussi de l'un.

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

Le seul problème restant avec cela maintenant est le cas limite où le champ est vide, puis rempli, puis à nouveau vide ... auquel cas l'erreur sera appliquée au champ unique et non au groupe. Mais cela semble si peu probable que cela se produise avec une fréquence quelconque et cela fonctionne toujours techniquement dans ce cas.

bonbon
la source
Cette réponse ne sert à rien puisque cette méthode / règle a été intégrée dans le plugin en avril 2012.
Sparky
J'ai le même problème que Rocket Hazmat avec la méthode qui est maintenant livrée avec le validateur. Il valide qu'un groupe de champs, mais aucun autre champ utilisant d'autres méthodes n'est validé. Cette réponse est une tentative de résoudre ce problème. Si vous avez une meilleure solution, faites-le moi savoir.
squarecandy
Jusqu'à ce que le développeur corrige définitivement le problème, plutôt que d'ajouter à la confusion, je recommande simplement la solution temporaire approuvée ici: github.com/jzaefferer/jquery-validation/issues/412
Sparky
1

J'avais des problèmes avec d'autres règles qui n'étaient pas vérifiées en conjonction avec cela, alors j'ai changé:

fields.valid();

Pour ça:

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

J'ai également apporté quelques améliorations (personnelles), et voici la version que j'utilise:

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."));
Fusée Hazmat
la source
Fonctionne pour que les autres champs soient à nouveau validés, mais maintenant, lorsque tous les champs d'un groupe sont marqués comme non valides et que vous en remplissez un, seul celui-ci est validé. Au moins pour moi?
Christof
@Chris - voyez ma nouvelle réponse qui s'appuie sur celle-ci et aborde ce problème.
squarecandy
0

Merci, Nathan. Vous m'avez sauvé une tonne de temps.

Cependant, je dois remarquer que cette règle n'est pas prête pour jQuery.noConflict (). Donc, il faut remplacer tout $ par jQuery pour travailler avec, disons,var $j = jQuery.noConflict()

Et j'ai une question: comment le faire se comporter comme une règle intégrée? Par exemple, si je saisis un e-mail, le message "Veuillez saisir un e-mail valide" disparaît automatiquement mais si je remplis l'un des champs du groupe, le message d'erreur reste.

Rinat
la source
Vous avez raison - je n'ai pas considéré la situation de non-conflit. Je peux mettre à jour cela à l'avenir, mais vous pouvez facilement faire une recherche et un remplacement si vous le souhaitez. Pour votre deuxième question, je ne vois pas le même problème. Avec un test rapide, si un champ d'un groupe est requis, dès que je tape quelque chose, tout le groupe passe cette règle. Si plus d'un est requis, dès que le dernier requis est rempli et perd le focus, tout le groupe réussit. C'est ça que tu vois?
Nathan Long
hmm pour une raison quelconque, le balisage est foutu et je n'ai pas réussi à le réparer
Rinat
Rinat - pouvez-vous simplifier et affiner le problème? Essayez d'utiliser mon code sur une forme plus simple qui n'a pas besoin des modifications sans conflit. Faites le formulaire le plus simple sur lequel vous pouvez le tester et faites-le fonctionner en premier.
Nathan Long