web-dev-qa-db-fra.com

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

J'utilise l'excellent jQuery Validate Plugin 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 toutes les améliorations auxquelles vous pouvez penser.

Ne trouvant aucun moyen intégré de le faire, j'ai cherché et trouvé 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 passant la validation dès que l'une d'elles passe la validation. (Voir citation à 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 lors de la 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.error une image X comme arrière-plan, sauf si elle a la classe .checked, auquel cas il obtient une image de coche.

Voici mon code jusqu'à présent:

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 a simplement caché 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 chiffres sont autorisés et vous avez entré des lettres '') , il a été masqué jusqu'à ce que l'utilisateur essaie de soumettre. C'est 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 cela et voir si quelqu'un peut suggérer des améliorations". Bien que j'accueillerais toujours les commentaires, je pense que c'est assez complet à ce stade. (Il pourrait être plus court, mais je veux qu'il soit facile à lire et pas nécessairement concis.) Alors profitez-en!

Mise à jour - fait désormais partie de la validation jQuery

C'était officiellement ajouté à jQuery Validation le 04/03/2012.

98
Nathan Long

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 à l'intérieur du 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!

21
Gerasimos

Belle solution. Cependant, j'ai eu le problème des autres règles requises 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);
}
6
sean

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

J'ai également apporté quelques modifications pour que le message "Veuillez remplir au moins 1 champ .." s'affiche dans une div séparée au lieu de tous les champs.

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;(!)"));
4
Walter Kelly

J'ai soumis un patch qui ne souffre pas des problèmes de la version actuelle (où l'option "requis" cesse de fonctionner correctement sur d'autres champs, une discussion des problèmes avec la version actuelle est activée 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."));
4
Kingsley

Le démarrage d'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, n'est-ce pas? Il semble que ce code ne devrait pas fonctionner.

De plus, je ne sais pas si c'est 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 la description de votre texte ci-dessus, il est difficile de suivre le code, car je ne sais pas à quoi le champ,: rempli, valeur, élément ou sélecteur fait référence. La plupart de cela est peut-être évident pour quelqu'un qui connaît le plugin Validate, alors faites preuve de jugement sur la bonne quantité d'explication.

Peut-être pourriez-vous éclater quelques vars 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 "module" signifie, en fait - y a-t-il un nom plus spécifique que vous pourriez donner à cette variable?

Beau code, dans l'ensemble!

3
Michael Gundlach

Étant donné 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, modifiant 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 de cette classe uniquement à l'intérieur de chaque jeu de champs, plutôt que d'essayer de compter tous les éléments classés .reqgrp dans le formulaire.

2
Andrew Roazen

Voici ma fissure à la réponse de Rocket Hazmat, en essayant de résoudre le problème d'autres champs définis devant également être validés, mais en marquant tous les champs comme valides lors du remplissage réussi d'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 qui reste avec cela maintenant est le cas Edge où le champ est vide, puis rempli, puis vide à nouveau ... auquel cas l'erreur sera appliquée au champ unique et non au groupe. Mais cela semble si peu probable de se produire avec n'importe quelle fréquence et cela fonctionne toujours techniquement dans ce cas.

2
squarecandy

J'avais des problèmes avec d'autres règles non vérifiées en même temps, j'ai donc 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."));
1
Rocket Hazmat

Merci, Nathan. Tu m'as sauvé une tonne de temps.

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

Et j'ai une question: comment pourrais-je le faire se comporter comme une règle intégrée? Par exemple, si j'entre 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.

0
Rinat