Formularze w dobrej formie cz. IV – walidacja jQuery


Parę lat temu korzystałem z bardzo fajnego frameworka Vanadium JS – rozwiązania do walidacji formularzy po stronie klienta sterowanego klasami, wykonanego jak nazwa wskazuje w JS, a jeśli pamięć mnie nie myli konkretniej w jQuery. Niestety, wygląda na to, że projekt nie jest już wspierany

Ale przecież nie po to piszę cykl Formularze w dobrej formie, aby mówić że coś było a nie jest. Dlatego zaraz zajmiemy się walidacją formularzy z wykorzystaniem jQuery na własną rękę.

Kojarzycie wpis na temat wysyłania formularza AJAXem? Tutaj również pójdziemy tropem zdarzenia submit elementu form. W jego obsłudze sprawdzimy warunki na poszczególne elementy. Do nałożenia tych warunków zastosujemy klasy, a ewentualne dodatkowe dane przechowamy w atrybutach data-*, w końcu mamy HTML5, prawda? Do roboty.

Podstawowa forma walidacji wymaganych pól formularza wygląda tak:

$(function($){
    $('form.jQueryValidation').submit(function(submitEvent){
        $(this).find('input.jQueryRequired').each(function(){
            if ($(this).val() == ''){
                submitEvent.preventDefault();
                $(this).parent().addClass('invalid');
            } else {
                $(this).parent().removeClass('invalid');
            }        
        });
    });
});

Banały. Przypinamy się do formularza (lub formularzy), który oznaczamy klasą walidacji jQueryValidation. Zanim wyślemy dane z tego formularza przeszukujemy wszystkie jego elementy pod kątem klasy oznaczającej pole wymagane jQueryRequired. Dla każdego z kolekcji takich pól sprawdzamy, czy istnieje jego wartość. Jeśli nie, to mamy dwa zadania – nie dopuścić do wysłania formularza (submitEvent.preventDefault()) oraz oznaczenie wspólnego pojemnika pola i etykiety klasą invalid – dzięki niej ostylujemy pola i teksty – najprościej czerwonym kolorem, ale to kwestia na pograniczu gustu i usability.

Ważne: pól wymaganych poszukujemy za pomocą find, a nie childrenchildren przeszuka tylko pierwszy poziom (dosłownie dzieci), natomiast find przeszuka wszystkich potomków – ważne, jeśli będziemy mieli po drodze jakiś pojemnik.

Ważne 2: pole i etykieta muszą mieć wspólny pojemnik – zawierający tylko tę parę. W innym przypadku ostylujemy również inne pola i etykiety. Dzięki takiemu rozwiązaniu możemy je właściwie ostylować takimi stylami:

.invalid input { border-color: red;}
.invalid label{ color: red; }

Oczywiście, jeżeli komuś taki sposób stylizacji nie odpowiada (czy to estetycznie, czy to logicznie) – śmiało można podmienić go na inny.

Ważne 3: jeśli warunek walidacji przechodzi – usuwamy klasę stylizacji invalid. Nie przyda się to przy pierwszej próbie walidacji, ale jeśli spróbujemy ponownie walidować formularz, który wcześniej nie przeszedł – musimy o tym pamiętać. Nie wolno kazać użytkownikowi domyślać się, czy dane pole już jest OK, czy jeszcze nie.

Mamy już pole wymagane, teraz coś ciekawszego – pole zawierające adres email:

$(this).find('input.jQueryEmail').each(function(){
    if ($(this).val() == ''){
        return;
    }
    var filter = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
    if (!filter.test($(this).val())){
        submitEvent.preventDefault();
        $(this).parent().addClass('invalid');
    } else {
        $(this).parent().removeClass('invalid');
    }
});

Oczywiście ten fragment wstawiamy do funkcji obsługi wysyłania formularza. Najpierw sprawdzamy, czy w polu w ogóle jest jakaś wartość – jeśli nie ma, to w ogóle się tym nie przejmujemy. Czemu? Bo pole email może nie być wymagane, a pusty ciąg znaków nie przejdzie przez wyrażenie regularne. Jeśli pole ma być wymagane, po prostu oznaczamy jest klasą walidacji z poprzedniego przykładu. Jeśli natomiast w polu mamy jakąś wartość, to przepuszczamy ją przez regexp – tworzymy obiekt RegExp i testujemy w nim zawartość pola.

Dlaczego ten regexp wygląda właśnie tak? To dobre pytanie. W sieci mamy mnóstwo materiałów na temat wyrażenia regularnego odpowiedniego dla adresów email. Większość z tych materiałów dowodzi, że to nie jest łatwa sprawa. Tutaj garść linków

Ostatecznie zdecydowałem się na regexp z tego ostatniego linka (po aktualizacji, tak aby przechodziły plusy w adresach na Gmailu – polecam ten feature). Ogólnie muszę się zgodzić z tym komentarzem – nie przesadzajmy z walidacją adresów email, sprawdźmy tylko, czy to nie są zupełne śmieci bez @ etc. – ostatecznie, czy adres jest właściwy, dowiemy się wysyłając maila potwierdzającego.

Gratis w sprawie adresów email w formularzach komentarz rysunkowy xkcd: The Important Field – kto nie bywa na xkcd, może nie wiedzieć, że warto zatrzymać kursor nad rysunkiem, aby wyświetlił się alt.

Skoro mamy już taki kawałek kodu, to może sprawdzajmy zawartość pola pod kątem dowolnego wyrażenia regularnego. Proszę bardzo:

$(this).find('input.jQueryRegexp').each(function(){
    if ($(this).val() == ''){
        return;
    }
    var regexp = new RegExp($(this).data('pattern'),$(this).data('modifiers'));
    if (!regexp.test($(this).val())){
        submitEvent.preventDefault();
        $(this).addClass('invalid');
        $(this).parent().addClass('invalid');
    } else {
        $(this).parent().removeClass('invalid');
    }
});

Wykorzystamy tutaj regexp zdefiniowany w atrybutach data-pattern i data-modifiers sprawdzanego pola

Tak zdefiniujemy wyrażenie dla samych cyfr:

<input class="jQueryRegexp" data-pattern="^[0-9]+$" />

a tak dla samych liter łacińskich bez względu na wielkość (modyfikator i):

<input class="jQueryRegexp" data-pattern="^[a-z]+$" data-modifiers="i" />

Więcej na temat obiektu RegExp na przykład w w3schools.

Mam jeszcze jeden ciekawy przykład – jedna z moich ulubionych funkcjonalności przy rejestracji, czyli AJAXowe sprawdzanie dostępności loginu:

$(this).find('input.jQueryAjax').each(function(){
    if ($(this).val() == ''){
        return;
    }
    var validatedField = $(this);
    $.ajax({
        'url' : validatedField.data('url'),
        'data' : { 'value' : validatedField.val() },
        'type' : 'post',
        'dataType' : 'json',
        'success' : function(response){
            if (response.valid != true){
                submitEvent.preventDefault();
                validatedField.parent().addClass('invalid');
            } else {
                validatedField.parent().removeClass('invalid');
            }
        }
    })
});

HTML wykorzystujący tę walidację wygląda tak:

<input class="jQueryAjax" data-url="validateLogin.php"/>

Odwołujemy się tutaj do adresu podanego w atrybucie data-url elementu pola. Wysyłamy wartość sprawdzanego pola jako value w żądaniu POST. Zadaniem docelowego skryptu jest zwrócenie JSONa, w którym pole valid będzie miało wartość true albo false (w sumie to ta druga wartość ma marginalne znaczenie – byle „not true„). Jak sprawdzimy login? Zajrzymy do bazy, czy takiego jeszcze nie ma, porównamy z listą stopwords, banów etc. – to już kwestia biznesowa. Oczywiście tak samo możemy obsłużyć inne zadania, w których zweryfikujemy wartość pola AJAXem – nie tylko dostępność loginu.

Mamy więc walidację formularza przeprowadzoną za pomocą jQuery. Jest w zasadzie gotowa do użytku, w spójnej formie znajduje się na stronie http://wkh24.pl/showcase/forms/jquery.html – jak zwykle zapraszam do źródła strony. Ale najważniejsza informacja w tym wpisie dopiero przed nami. WALIDACJA PO STRONIE KLIENTA TO TYLKO OZDOBA. Zawsze należy przeprowadzać walidację po stronie serwera. Zawsze. Czy mówiłem już, że zawsze? A o tym, dlaczego i jak ją wykonać, będzie następny wpis tego cyklu.