jQuery Validate - wymaga wypełnienia co najmniej jednego pola w grupie

98

Używam doskonałej wtyczki jQuery Validate do sprawdzania poprawności niektórych formularzy. Na jednym formularzu muszę się upewnić, że użytkownik wypełni przynajmniej jedno z grup pól. Myślę, że mam całkiem dobre rozwiązanie i chciałem się nim podzielić. Zaproponuj wszelkie ulepszenia, o których możesz pomyśleć.

Nie znajdując wbudowanego sposobu, aby to zrobić, przeszukałem i znalazłem niestandardową metodę walidacji Rebecci Murphey , która była bardzo pomocna.

Poprawiłem to na trzy sposoby:

  1. Aby umożliwić Ci przekazanie selektora dla grupy pól
  2. Aby umożliwić określenie, ile z tej grupy musi zostać wypełnionych, aby walidacja przebiegła pomyślnie
  3. Pokazanie wszystkich wejść w grupie jako pozytywnych, gdy tylko jeden z nich przejdzie walidację. (Zobacz odpowiedź dla Nicka Cravera na końcu).

Możesz więc powiedzieć „co najmniej X wejść pasujących do selektora Y musi być wypełnionych”.

Wynik końcowy, z takimi znacznikami:

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

... to taka grupa reguł:

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

Punkt 3 zakłada, że ​​dodajesz klasę .checkeddo komunikatów o błędach po pomyślnej weryfikacji. Możesz to zrobić w następujący sposób, jak pokazano tutaj .

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

Podobnie jak w powyższej demonstracji, używam CSS, aby nadać każdemu span.errorobraz X jako tło, chyba że ma klasę .checked, w którym to przypadku otrzymuje obraz znacznika wyboru.

Oto mój dotychczasowy kod:

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

Brawo!

Wykrzyczeć

Teraz do tego okrzyku - pierwotnie mój kod po prostu ślepo ukrywał komunikaty o błędach w innych pasujących polach zamiast ich ponownego sprawdzania, co oznaczało, że jeśli był inny problem (na przykład `` dozwolone są tylko cyfry i wpisałeś litery '') , został ukryty, dopóki użytkownik nie próbował go przesłać. Stało się tak, ponieważ nie wiedziałem, jak uniknąć pętli opinii, o której mowa w komentarzach powyżej. Wiedziałem, że musi istnieć sposób, więc zadałem pytanie , a Nick Craver oświecił mnie. Dzięki, Nick!

Pytanie rozwiązane

Pierwotnie było to pytanie typu „pozwól mi się tym podzielić i zobaczę, czy ktoś może zasugerować ulepszenia”. Chociaż nadal chciałbym otrzymywać opinie, myślę, że w tym momencie jest całkiem kompletny. (Może być krótszy, ale chcę, aby był łatwy do odczytania i niekoniecznie zwięzły). Po prostu baw się!

Aktualizacja - teraz część walidacji jQuery

Zostało to oficjalnie dodane do walidacji jQuery w dniu 03.04.2012.

Nathan Long
źródło
Zobacz także ściśle powiązaną regułę - „Albo pomiń te pola, albo wypełnij przynajmniej X z nich” - stackoverflow.com/questions/1888976/…
Nathan Long
Dlaczego jedno dowolne wejście miałoby być odpowiedzialne za sprawdzenie, czy inne dane wejściowe są wypełnione? To nie ma sensu. Może mógłbyś dołączyć trochę znaczników do zaangażowanych elementów?
montrealista
@dalbaeb - trochę wyjaśniłem przykład. Nie chodzi o to, że jedno dowolne wejście jest odpowiedzialne za sprawdzanie innych; chodzi o to, że każde wejście w grupie jest odpowiedzialne za sprawdzenie wszystkich pozostałych.
Nathan Long
Tak właśnie pomyślałem, bardzo dziękuję!
montrealistyczny
3
Dzięki, to dla mnie działa, ale inne wymagane pola w formularzu teraz już nie odpowiadają, chyba że po sprawdzeniu zyskują lub tracą koncentrację. (Ktoś dodał to jako odpowiedź na Twoje drugie pytanie, ale musiało zostać oflagowane, ponieważ nie jest to odpowiedź).
mydoghasworms

Odpowiedzi:

21

To doskonałe rozwiązanie, Nathan. Wielkie dzięki.

Oto sposób, aby powyższy kod działał na wypadek, gdyby ktoś miał problemy z integracją, tak jak ja:

Kod wewnątrz pliku 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"]
});

Kod w pliku 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" />

Nie zapomnij dołączyć pliku dodatkowego-methods.js!


źródło
Cieszę się, że jest to pomocne dla Ciebie i dziękuję za informacje. Jednak zamiast wykonywać metodę addClassRules, wolę używać tablicy reguł dla każdego formularza. Jeśli przejdziesz do tej strony ( jquery.bassistance.de/validate/demo/milk ) i klikniesz „pokaż skrypt używany na tej stronie”, zobaczysz przykład. Idę o krok dalej: deklaruję tablicę o nazwie „reguły”, a następnie osobno używam ich z var validator = $ ('# formtovalidate'). Validate (rules);
Nathan Long
Inna myśl: klasa „fillone”, którą tu pokazujesz, może być problematyczna. Co jeśli na tym samym formularzu potrzebujesz co najmniej jednego numeru części ORAZ co najmniej jednej osoby kontaktowej? Twoja reguła zezwoli na 0 nazw kontaktów, o ile istnieje co najmniej jeden numer części. Myślę, że lepiej jest ustalić takie zasady require_from_group: [1,".partnumber"]i ...[1,".contactname"]upewnić się, że sprawdzasz właściwe rzeczy.
Nathan Long
6

Niezłe rozwiązanie. Jednak miałem problem z niedziałającymi innymi wymaganymi regułami. Wykonanie .valid () w formularzu rozwiązało ten problem.

if(!$(element).data('being_validated')) {
  var fields = $(selector, element.form);
  fields.data('being_validated', true); 
  $(element.form).valid();
  fields.data('being_validated', false);
}
Sean
źródło
1
Dzięki Sean, ja też miałem ten problem. Jest jednak jeden problem z tym rozwiązaniem, gdy użytkownik dostaje się do formularza po raz pierwszy - gdy tylko wypełni pierwsze pole wymagania z grupy, wszystkie inne pola formularza zostaną sprawdzone i oznaczone jako błędne. Sposób, w jaki to rozwiązałem, polegał na dodaniu procedury obsługi form.submit () przed utworzeniem instancji walidatora, w którym ustawiam flagę validator.formSubmit = true. Następnie w metodzie require-from-group sprawdzam tę flagę; jeśli to tam robię $(element.form).valid();, w przeciwnym razie robię fields.valid();.
Christof
Czy ktoś może wyjaśnić, co się tu właściwie dzieje? Mam dość podobną regułę, która zadziałała, ale nie rozwiązaliśmy problemu z rewalidacją (inne pola w grupie nadal są oznaczone jako nieprawidłowe). Ale teraz doświadczam, że formularz jest przesyłany, nawet jeśli jest nieprawidłowy. Jeśli zgrupowane pola są prawidłowe, to nie wchodzi do submithandler, a jeśli jest niepoprawne, wchodzi do invalidHandler, ale i tak przesyła! Powiedziałbym, że to dość poważny błąd we wtyczce walidacji? To, że reguła zwraca poprawną, dotyczy tylko tej reguły (nawet nie całego pola), dlaczego więc przesyłany jest nieprawidłowy formularz?
Adam
Zbadałem dalej i są to pola przed grupą, które nie sprawdzają poprawności. Zadałem to jako osobne pytanie (z częściowym obejściem, które znalazłem): stackoverflow.com/questions/12690898/…
Adam
4

Dzięki, sean. To rozwiązało problem, który miałem z kodem ignorującym inne reguły.

Wprowadziłem też kilka zmian, aby komunikat „Proszę wypełnić co najmniej 1 pole…” był wyświetlany w osobnym div zamiast po każdym polu.

umieścić w formie skryptu walidacyjnego

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

dodaj to gdzieś na stronie

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

dodaj do funkcji addMethod metody require_from_group

 if(validOrNot){
    $("#form_error").hide();
}else{
    $("#form_error").show();
}
......
}, jQuery.format(" &nbsp;(!)"));
Walter Kelly
źródło
4

Przesłałem łatkę , która nie cierpi z powodu problemów występujących w obecnej wersji (gdy opcja „wymagana” przestaje działać poprawnie na innych polach, dyskusja o problemach z aktualną wersją jest na githubie .

Przykład na 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
źródło
3

Rozpoczynanie nazwy zmiennej od $ jest wymagane w PHP, ale dość dziwne (IMHO) w JavaScript. Sądzę też, że nazywasz to dwukrotnie „$ module” i raz „module”, prawda? Wygląda na to, że ten kod nie powinien działać.

Nie jestem również pewien, czy to normalna składnia wtyczki jQuery, ale mogę dodać komentarze nad wywołaniem addMethod, wyjaśniając, co osiągasz. Nawet przy powyższym opisie tekstowym trudno jest śledzić kod, ponieważ nie wiem, do jakiego zestawu pól: wypełnione, wartość, element lub selektor się odnoszą. Być może większość z tego jest oczywista dla kogoś zaznajomionego z wtyczką Validate, więc kieruj się oceną, jaka jest odpowiednia ilość wyjaśnień.

Być może mógłbyś wydzielić kilka zmiennych, aby samodzielnie udokumentować kod; lubić,

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

(zakładając, że rozumiem znaczenie tych fragmentów twojego kodu! :))

Nie jestem do końca pewien, co właściwie oznacza „moduł” - czy jest jakaś bardziej szczegółowa nazwa, którą mógłbyś nadać tej zmiennej?

Ogólnie niezły kod!

Michael Gundlach
źródło
Dzięki za sugestie - wyjaśniłem nazwy zmiennych i podzieliłem kod, aby był bardziej czytelny.
Nathan Long
2

Ponieważ formularz, nad którym pracuję, ma kilka sklonowanych regionów z takimi pogrupowanymi danymi wejściowymi, przekazałem dodatkowy argument do konstruktora require_from_group, zmieniając dokładnie jedną linię funkcji addMethod:

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

iw ten sposób selektor, identyfikator lub nazwę elementu można przekazać raz:

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

a walidator ograniczy walidację do elementów z tą klasą tylko w każdym zestawie pól, zamiast próbować policzyć wszystkie sklasyfikowane elementy .reqgrp w formularzu.

Andrew Roazen
źródło
2

Oto moje pęknięcie w odpowiedzi Rocket Hazmat, próbując rozwiązać problem innych zdefiniowanych pól, które również wymagają walidacji, ale oznaczając wszystkie pola jako prawidłowe po pomyślnym wypełnieniu jednego.

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

Jedynym pozostałym problemem z tym teraz jest przypadek skrajny, w którym pole jest puste, następnie wypełnione, a następnie ponownie puste ... w takim przypadku błąd zostanie zastosowany do pojedynczego pola, a nie do grupy. Ale wydaje się to tak mało prawdopodobne z jakąkolwiek częstotliwością i nadal działa technicznie w tym przypadku.

squarecandy
źródło
Ta odpowiedź nie ma sensu, ponieważ ta metoda / reguła została zintegrowana z wtyczką w kwietniu 2012 r.
Sparky
Mam ten sam problem, który ma Rocket Hazmat z metodą, która jest teraz dostarczana z walidatorem. Sprawdza, czy jedna grupa pól, ale żadne inne pola przy użyciu innych metod nie są sprawdzane. Ta odpowiedź jest próbą rozwiązania tego problemu. Jeśli masz lepsze rozwiązanie, daj mi znać.
Squarecandy
Dopóki programista nie naprawi problemu na stałe, zamiast dodawać zamieszanie, po prostu polecam dowolne tymczasowe rozwiązanie, które jest zalecane tutaj: github.com/jzaefferer/jquery-validation/issues/412
Sparky
1

Miałem problemy z innymi regułami, które nie były sprawdzane w związku z tym, więc zmieniłem:

fields.valid();

Do tego:

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

Wprowadziłem też kilka (osobistych) ulepszeń i to jest wersja, której używam:

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."));
Rocket Hazmat
źródło
Działa, sprawiając, że pozostałe pola są ponownie sprawdzane, ale teraz, gdy wszystkie pola grupy są oznaczone jako nieprawidłowe i wypełnisz jedno, tylko to jedno zostanie zatwierdzone. Przynajmniej dla mnie?
Christof
@Chris - zobacz moją nową odpowiedź, która opiera się na tej i dotyczy tego.
squarecandy
0

Dzięki, Nathan. Zaoszczędziłeś mi mnóstwo czasu.

Muszę jednak zauważyć, że ta reguła nie jest gotowa na jQuery.noConflict (). Tak więc, aby pracować z, powiedzmy, trzeba zamienić wszystkie $ na jQueryvar $j = jQuery.noConflict()

I mam pytanie: jak sprawić, by zachowywał się jak wbudowana reguła? Na przykład, jeśli wpiszę adres e-mail, komunikat „Wprowadź prawidłowy adres e-mail” znika automatycznie, ale jeśli wypełnię jedno z pól grupy, komunikat o błędzie pozostaje.

Rinat
źródło
Masz rację - nie brałem pod uwagę sytuacji bez konfliktu. Mogę to zaktualizować w przyszłości, ale jeśli chcesz, możesz łatwo znaleźć i wymienić. Jeśli chodzi o drugie pytanie, nie widzę tego samego problemu. Przy szybkim teście, jeśli wymagane jest jedno pole z grupy, jak tylko cokolwiek wpiszę, cała grupa przejdzie tę regułę. Jeśli wymagana jest więcej niż jedna, jak tylko ostatnia wymagana zostanie wypełniona i straci skupienie, cała grupa przechodzi. Czy to jest to, co widzisz?
Nathan Long
hmm z jakiegoś powodu znaczniki są schrzanione i nie udało mi się to naprawić
Rinat
Rinat - czy możesz uprościć i zawęzić problem? Spróbuj użyć mojego kodu w prostszym formularzu, który nie wymaga zmian noconflict. Zrób najprostszy formularz, na którym możesz go przetestować, i zacznij działać.
Nathan Long