Walidacja formularzy HTML5 za pomocą JS

W ostatnim poście stworzyliśmy podstawę walidacji, którą udostępnia nam HTML5.

W chwili obecnej walidacja taka ma jednak trochę niedoróbek i nie działa tak jak byśmy chcieli. Po pierwsze nie mamy kontroli nad tym, czy walidacja ta ma być widoczna od razu po wejściu na stronę. Po drugie nie mamy kontroli nad zachowaniem tej walidacji (chociażby na wygląd podpowiedzi). Po trzecie nie działa ona dla wszystkich pól formularzy (nie działa z checkboxami). Wreszcie po czwarte nie działa ona na IE8 nie mówiąc o starszych przeglądarkach.

Stosowanie jej jest jednak bardzo dobrym pomysłem, bo dzięki niej zapewniamy dodatkową walidację dla osób, które mają wyłączone JS. Niby jest to margines użytkowników, ale dobrze stworzona strona powinna móc być obsługiwana i przez takie osoby. Taki plus, który nikomu nie szkodzi a może pomóc.

Kolejnym krokiem będzie rozbudowanie naszej walidacji za pomocą skryptów. Napiszemy prostą bibliotekę, która będzie służyć do sprawdzania formularzy. Będzie ona działać ze starszymi przeglądarkami, będzie mocniej konfigurowalna niż podstawowa walidacja HTML5 oraz co bardzo ważne – będzie bardzo prosta w użyciu.

 

Co więc musimy zrobić?

  1. Robimy pętlę po wszystkich polach formularza.
  2. sprawdzamy czy dane pole ma atrybut required
  3. jeżeli pole posiada taki atrybut, wtedy:
  4. usuwamy z pola ten atrybut
  5. dodajemy do pola klasę requred – dzięki temu będziemy mogli pobrać grupę pól, które były/są wymagane
  6. dodajemy js-ową obsługę zdarzeń dla danego typu pola
  7. dodajemy sprawdzanie pól przy wysyłaniu formularza

 

Czemu chcemy usunąć atrybut requred? Ponieważ jak wiemy domyślnie przeglądarka obsługuje po swojemu pola z tym atrybutem (zarówno przy walidacji jak i przy wysyłaniu formularza). My chcemy sami obsłużyć sprawdzanie danych za pomocą JS, dlatego musimy „wyczyścić” całą walidację po stronie HTML5.

Na nasz warsztat weźmiemy stworzony uprzednio formularz (chociaż później troszkę się zmieni), który znajduje się pod adresem

http://cssdeck.com/labs/uyg8nds5/0

Nasz skrypt będzie nadawał polom formularza stosowne klasy w zależności od tego czy dane pole jest poprawne, czy nie. Porzednio elementy otrzymywały pseudoklasy :valid i :invalid. Dopiszmy do naszych styli analogiczne klasy .valid i .invalid, które będziemy aplikować za pomocą JS:

.form *.valid {
      box-shadow:0 0 0 1px #39E312; 
      border:1px solid #39E312; 
      color:#39E312; 
      background-image:url(icon-ok.png);
} 
.form *.invalid { 
      box-shadow:0 0 0 2px #E02E00; 
      border:1px solid #E02E00; 
      background-image:url(icon-error.png); 
}

Dodajemy je w oddzielnych deklaracjach (nowe linie). Czemu nie powinniśmy ich dodawać do pozostałych deklaracji po przecinku (np :valid, .valid)? Przeglądarki, które nie rozumieją danych deklaracji pomijają następne, które są zamieszczone po przecinku. Gdybyśmy dodali powyższe deklaracje do poprzednich, oddzielając je kolejnym przecinkiem, w IE8 by one nie zadziałały, bo przeglądarka ta nie rozumie selektora :valid. Zasada ta nie dotyczy IE7, chociaż to nie ma znaczenia :)

Nasz formularz bez skryptowania ma postać:

Rozpoczynamy pracę

Jeżeli nie jesteś zaznajomiony z pisaniem pluginów w jQuery, wtedy zapraszam cię do mojego starszego wpisu w Kursie Javascript i do tej lektury o jQuery. Tym razem wskakujemy na głęboką wodę i od razu robimy nura w kod pluginu.

Zaczynamy więc od podstawowego szkieletu:

$.fn.ownFormValidate = function(options) {
    options = $.extend({
        classError   : 'invalid',
        classOk      : 'valid'
    }, options);        

    var $form = $(this);

    var prepareElements = function() {
        $form.find('input[required], textarea[required], select[required]').each(function() {         
            .....
        });
    }
    prepareElements();    
}

Mamy więc prosty wyjściowy format pluginu.
Będzie on przy odpalaniu pozwalał na podanie klas walidacji (domyślnie invalid i valid).

Pętla po elementach formularza

Realizujemy więc pierwsze kroki naszej listy – czyli przygotowujemy/obsługujemy pola. Pierwsze co robimy to podstawiamy dany element pod zmienną $t. Dzięki temu zyskamy na objętości kodu i szybkości działania skryptu. Dalsza część kodu to sprawdzenie czym dany element jest. Jak wiesz, inputy występują w kilku odmianach. Mamy więc typ text, email, url, radio i checkbox. Tak naprawdę tych typów istnieje jeszcze kilka, chociaż wspomniane są najbardziej popularne. Aby zbytnio nie rozgrzebywać kodu skupimy się właśnie na nich.

var prepareElements = function() {
    $form.find('input[required], textarea[required], select[required]').each(function() {
        var $t = $(this);
        $t.removeAttr('required');
        $t.addClass('required');

        if ($t.is('input')) {
               ...
        }
        if ($t.is('textarea')) {
               ...
        if ($t.is('select')) {
               ...
        }
    });
}
prepareElements();

 

Na samym początku skoncentrujmy się na elementach typu input.

Skoro wiemy, że nasz obecny element to input, musimy tylko pobrać jego typ (type), a następnie każdy z nich odpowiednio obsłużyć.Co zrobimy już za chwilę.

var prepareElements = function() {    
     $form.find('input[required], textarea[required], select[required]').each(function() {
            var $t = $(this);
            $t.removeAttr('required');
            $t.addClass('required');

            if ($t.is('input')) {
                var type = $t.attr('type').toLowerCase();
                if (type == 'email') {
                    ...
                } 
                if (type == 'url') {
                    ...
                }
                if (type == 'text') {
                    ...
                }
                if (type == 'checkbox') {                
                    ...
                }
                if (type == 'radio') {                
                    ...
                }
            }
            if ($t.is('textarea')) {
                ...
            }
            if ($t.is('select')) {
                ...
            }
        });
}
prepareElements();

Mamy już sprawdzone czym właściwie jest dany element, pozostaje więc podpiąć pod niego i napisać metody sprawdzające.

Sprawdzamy tekst

Zacznijmy od metody dla elementów z tekstem:

var testInputText = function($input) {                        
    if ($input.val()=='') {            
        $object.removeClass(options.classOk).addClass(options.classError);
        options.isInvalid($input);
        return false;
    } else {
        $object.removeClass(options.classError).addClass(options.classOk);
        options.isValid($input);        
        return true;
    }
}

Oraz podpinamy to do naszego tekstowego elementu we wcześniejszej pętli:

var prepareElements = function() { 
    $form.find('input[required], textarea[required], select[required]').each(function() {
        var $t = $(this);
        $t.removeAttr('required');
        $t.addClass('required');

        if ($t.is('input')) {
            var type = $t.attr('type').toLowerCase();
            ...
            if (type == 'text') {
                $t.on('blur keyup', function() {testInputText($t)});
            } 
        }
        ...
    });
}

Od wersji 1.7 jQuery udostępnia nam metodę on, która jest połączeniem metod bind i live (różnią się nieco w wykorzystaniu), czyli podpina zdarzenia pod aktualnie istniejące i dynamicznie tworzone (w przyszłości) elementy. My podpinamy naszą funkcję pod zdarzenia blur i keyup, tak by sprawdzenie było dynamicznie podczas pisania (i opuszczenia danego pola).

To jak? Testujemy? Póki co tylko dla pola tekstowego:

Zostańmy na chwilę przy polach tekstowych. W powyższym skrypcie sprawdzamy czy dane pole jest puste. Jak wiemy, pole takie może też mieć atrybut pattern, który definiuje wymaganą wartość. Dodajmy więc obsługę i tego atrybutu. Zmieniamy więc metodę testInputText:

var testInputText = function($input) {                
    if ($input.attr('pattern')!=undefined) {                
        var reg = new RegExp($input.attr('pattern'), 'gi');
        if (!reg.test($input.val())) {
            $input.removeClass(options.classOk).addClass(options.classError);
            return false;
        } else {
            $input.removeClass(options.classError).addClass(options.classOk);
            return true;
        }                
    } else {
        if ($input.val()!='') {            
            $input.removeClass(options.classOk).addClass(options.classError);
            return false;
        } else {
            $input.removeClass(options.classError).addClass(options.classOk);
            return true;
        }
    }
}

Idąc za tropem specyfikacji sprawdzamy czy atrybut pattern jest różny od undefined. Jeżeli tak się dzieje (czyli atrybut ten istnieje), tworzymy nowe wyrażenie regularne (czy zapoznałem cię z obowiązkową lekturą?) na jego podstawie i wykonujemy prosty test. Reszta metody zostaje bez zmian.

To co? Zabieramy się za kolejne pola? Jeszcze chwilkę. Wpierw zajmijmy się do końca powyższą metodą.
W większości przypadków będziemy chcieli nadawać klasę błędu nie tyle danemu polu, co elementowi, w którym dane pole się znajduje (a może nawet innemu?). Dlatego rozbudowujemy dalej naszą metodę o możliwość podania wybranego elementu nadrzędnego (parents):

var testInputText = function($input, showWalid) {
    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $input;

    if ($input.attr('pattern')!=undefined) {                
        var reg = new RegExp($input.attr('pattern'), 'gi');
        if (!reg.test($input.val())) {
            $object.removeClass(options.classOk).addClass(options.classError);
            return false;
        } else {
            $object.removeClass(options.classError).addClass(options.classOk);
            return true;
        }                
    } else {
        if ($input.val()=='') {            
            $object.removeClass(options.classOk).addClass(options.classError);            
            return false;
        } else {
            $object.removeClass(options.classError).addClass(options.classOk);
            return true;
        }
    }
}

Na samym początku pod zmienną $obiect podstawiamy albo podany w opcjach obiekt nadrzędny parentElement, albo jak wcześniej sprawdzany element. Oczywiście do opcji naszego pluginu musimy dodać wspomniany parametr:

$.fn.ownFormValidate = function(options) {
        options = $.extend({
            classError           : 'invalid',
            classOk              : 'valid',
            parentElement        : '', //nazwa elementu parents - np fieldset, div itp
        }, options);  
       ...
}

Jeszcze poświęcimy kilka sekund naszej metodzie. Z pozostałymi pójdzie za to jak z automatu. Obiecuję!

 

Nasz plugin działa całkiem sprawnie gdy „obsługujemy” dane pole. Częstokroć jednak będziemy chcieli pokazać walidację od razu po wejściu na stronę (np po ponownym jej przeładowaniu). Dodajmy więc kolejną opcję do naszego pluginu, która będzie określała czy pokazywać walidację od samego początku:

$.fn.ownFormValidate = function(options) {
    options = $.extend({
        classError           : 'invalid',
        classOk              : 'valid',
        parentElement        : '',
        initialValidate      : true, //czy pokazywać walidację początkową?
    }, options);  
   ...
}

Dodatkowo w zależności od jej wartości odpalamy tą metodę w miejscu gdzie podpinaliśmy ją pod zdarzenia:

...
if (type == 'email') {
    $t.on('blur keyup', function() {testInputEmail($t)});
    if (options.initialValidate) testInputEmail($t); //odpalamy na starcie
}  
...

Ostatnia rzecz, jaką dodamy do naszej metody jest już czystą kosmetyką. Jak zauważyłeś w przykładzie powyżej, przy początkowej walidacji pokazywane są nie tylko błędy, ale i poprawne odpowiedzi. Nie zawsze jest to dla nas wygodne czy też pożądane. Zresztą plugin miał być konfigurowalny prawda? Dodajemy kolejną opcję :)

$.fn.ownFormValidate = function(options) {
    options = $.extend({
        classError             : 'invalid',
        classOk                : 'valid',
        parentElement          : '',
        initialValid           : true,
        showValidOnCheck       : false //czy w początkowej walidacji pokazywać poprawne?
    }, options);    

    ....
}

Niestety, w tym przypadku ingerencja w metodę sprawdzającą będzie ciut większa niż w przypadku ostatnich zmian. Ale bez obaw. I tym razem świat się nie zawali.

Podczas wpisywania tekstu w pole walidacja powinna być zawsze wyświetlana. Ale podczas sprawdzania początkowego, walidacja prawidłowa powinna być pokazywana w zależności od parametru showValidOnCheck. Oba przypadki możemy przekazać jako kolejny parametr naszej metody (showValid):

var testInputText = function($input, showValid) {
    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $input;

    if ($input.attr('pattern')!=undefined) {                
        var reg = new RegExp($input.attr('pattern'), 'gi');
        if (!reg.test($input.val())) {
            $object.removeClass(options.classOk).addClass(options.classError);
            return false;
        } else {
            if (showWValid) {
                $object.removeClass(options.classError).addClass(options.classOk);
            }
            return true;
        }                
    } else {
        if ($input.val()=='') {            
            $object.removeClass(options.classOk).addClass(options.classError);
            return false;
        } else {
            if (showValid) {
                $object.removeClass(options.classError).addClass(options.classOk);
            }
            return true;
        }
    }
}

I ustawienie tego parametru przy podpinaniu tej metody:

if (type == 'text') {
    $t.on('blur keyup', function() {testInputText($t, true)}); //podczas pisania pokazujemy zawsze
    if (options.initialValidate) testInputText($t, options.showValidOnCheck) //przy początkowej zależnie od opcji
}

Właśnie zakończyliśmy najcięższa pracę jaka nas czekała. Serio i bez ściemniania!
Pozostaje nam już tylko obsłużyć pozostałe typu pól oraz dodać walidację przy wysyłaniu. Obie rzeczy wcale ciężkie nie są.

Sprawdzamy email

Zacznijmy od obsługi inputów email:

var testInputEmail = function($input, showValid) {
    var wzorMaila = /^[0-9a-zA-Z_.-]+@[0-9a-zA-Z.-]+\.[a-zA-Z]{2,3}$/

    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $input;

    if (!wzorMaila.test($input.val())) {
        $object.removeClass(options.classOk).addClass(options.classError);
        return false;
    } else {
        if (showValid) {
            $object.removeClass(options.classError).addClass(options.classOk);
        }
        return true;
    }    
}

Podobnie co poprzednio prawda? Jedyny wyjątek jest taki, że tutaj już nie musimy obsługiwać patternu. Samo sprawdzanie robimy w identyczny sposób jak w kursie JS. Podpięcie też jest takie samo jak poprzednio:

if (type == 'email') {     
    $t.on('blur keyup', function() {testInputEmail($t, true)});     
    if (options.initialValidate) testInputEmail($t, options.showValidOnCheck) 
}

Oraz mały wycinek kodu:

$form.find('input[required], textarea[required], select[required]').each(function() {
    var $t = $(this);
    $t.removeAttr('required');
    $t.addClass('required');

    if ($t.is('input')) {
        var type = $t.attr('type').toLowerCase();
        if (type == 'email') {
            $t.on('blur keyup', function() {testInputEmail($t, true)});
            if (options.initialValid) testInputEmail($t, options.showValidOnCheck)
        } 
        if (type == 'text') {
            $t.on('blur keyup', function() {testInputText($t, true)});
            if (options.initialValid) testInputText($t, options.showValidOnCheck)
        }
       ...
    }
});

Sprawdzamy URL

Kolejnym polem będzie url. Znowu brat bliźniak powyższych. Adres url powinien zaczynać się od http://, więc wykorzystajmy metodę indexOf, ktora idealnie się tutaj nadaje:

var testInputURL = function($input, showValid) {
    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $input;

    if ($input.val().indexOf('http://')!==0) {
        $object.removeClass(options.classOk).addClass(options.classError);
        return false;
    } else {
        if (showValid) {
            $object.removeClass(options.classError).addClass(options.classOk);                   
        }
        return true;
    }    
}

I analogicznie z innymi podpięcie:

if (type == 'url') {
    $t.on('blur keyup', function() {testInputURL($t, true)});
    if (options.initialValidate) testInputURL($t, options.showValidOnCheck);
}

Sprawdzamy radio i checkbox

Szybko zbliżamy się do końca typów.
Z inputów pozostały nam :radio i :checkbox, które obsłużymy bardzo podobnie (a i naprawimy przy okazji błąd walidacji html5!).
W przypadku pól typu :radio sprawdzamy, czy w danej grupie pól któreś z pól nie jest :checked (zaznaczone). Wystarczy więc pobrać grupę inputów, a następnie przefiltrować ją (filter) w poszukiwaniu zaznaczonego (:checked). W przypadku :checkboxów sprawdzamy po prostu, czy dane pole jest zaznaczone.

var testRadio = function($input, showValid) {

    var name = $input.attr('name');
    var $group = $form.find('input[name="'+name+'"]');

    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $group;

    if (!$group.filter(':checked').length) {
        $object.removeClass(options.classOk).addClass(options.classError);
        options.afterInvalid($input);
        return false;
    } else {
        if (showValid) {
            $object.removeClass(options.classError).addClass(options.classOk);
            options.afterValid($input);
        }
        return true;
    }
};

var testCheckbox = function($input, showValid) {

    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $group;

    if (!$input.is(':checked')) {
        $object.removeClass(options.classOk).addClass(options.classError);
        options.afterInvalid($input);
        return false;
    } else {
        if (showValid) {
            $object.removeClass(options.classError).addClass(options.classOk);
            options.afterValid($input);
        }
        return true;
    }
};

Pod zmienną $group pobieramy wszystkie inputy, które mają taką samą nazwę jak dany input (bo taka jest natura radio i checkboxów – występują w stadach o takiej samej nazwie, zupełnie jak antylopy!). Dzięki temu możemy bardzo łatwo sprawdzić, czy w grupie tej jest element :checked, czyli zaznaczony.
Podpięcie jest takie samo jak w pozostałych przypadkach:

if (type == 'checkbox') {                
    $t.on('change', function() {testCheckbox($t, true)});                
    if (options.initialValidate) testCheckbox($t, options.showValidOnCheck)
}
if (type == 'radio') {                
    $t.on('change', function() {testRadio($t, true)});                
    if (options.initialValidate) testRadio($t, options.showValidOnCheck)
}

Sprawdzamy select

Jeszcze tylko dwa pola i jesteśmy w domu. Pierwszy pod obstrzał idzie selekt. Pamiętaj, że to już nie jest typ input.

if ($t.is('input')) {
   ...
   ...
}
if ($t.is('select')) {
    $t.on('change keyup', function() {testInputSelect($t, true)});
    if (options.initialValidate) testInputSelect($t, options.showValidOnCheck)
}

Specyfikacja mówi, że błędny selekt oznacza ni mniej ni więcej, że wybrany options ma wartość równą , czyli pustemu ciągu znaków. Znowu czeka nas złożony jedno-linijkowy test:

var testInputSelect = function($select, showValid) {
    $object = (options.parentElement!='')?$select.parents(options.parentElement) : $select;

    if ($select.children('option:selected').val()=='') {
        $object.removeClass(options.classOk).addClass(options.classError);
        return false;
    } else {
        if (showValid) {
            $object.removeClass(options.classError).addClass(options.classOk);
        }
        return true;
    }
}

Sprawdzamy textarea

Ostatnim elemenem jest textarea. I tutaj nastaje logiczna niespodzianka. Logiczna, bo przecież textarea działa tak samo jak input:text, tyle tylko, że wprowadzamy do niej trochę więcej tekstu. Tak więc nie musimy pisać nowej metody, a podpiąć tylko naszą pierwszą:

if ($t.is('textarea')) {
    $t.on('blur keyup', function() {testInputText($t, true)});
    if (options.initialValidate) testInputText($t, options.showValidOnCheck)
}


Powyższy fiddler pokazuje praktycznie skończoną bibliotekę walidacji.

Obsługujemy Submit

Pozostał nam już ostatni punkt do realizacji, czyli sprawdzenie danych podczas wysyłania formularza (submit formularza). Wbrew pozorom jest to już bułka z masłem (czy jak kto woli ze smalcem).
Podpinamy funkcję pod zdarzenie submit. Na początku wywołujemy metodę przygotowującą elementy, którą tak skrupulatnie pisaliśmy do tej pory. Czemu to robimy?
Bo chcemy być pewni, że podczas końcowego sprawdzenia wszystkie pola są przygotowane. Od pierwszego sprawdzenia, do momentu wysyłania formularza inne skrypty na stronie mogły przecież dodać dodatkowe dynamiczne pola :required. Je także powinniśmy obsłużyć. Jeżeli takich pól nie będzie, to nic się nie stanie. W końcu nasza funkcja prepareElements działa na elementach :required. Najwyżej nie zrobi żadnej iteracji.
Po upewnieniu się, że wszystkie pola są obsłużone przez naszą walidację rozpoczynamy działania.
I tutaj dzięki sprytowi sprawa też jest bardzo prosa. Podczepiając naszą walidację pod pola nasza funkcja prepareElements dodała do tych pól klasę .required. Wystarczy więc zrobić pętlę po tych polach i wykorzystując wcześniej napisane metody sprawdzające dokonać odpowiednich testów.
Przed testami ustawiamy zmienną validated, która określa czy formularz może być wysłany czy też nie. Jeżeli któryś z testów się nie powiedzie, wtedy zmienna ta przyjmie wartość false, a formularz nie zostanie wysłany.

...
...

$form.submit(function() {
    prepareElements();

    var validated = true;
    var $inputs = $form.find('.required');

    $inputs.each(function() {
        var $t = $(this);                
        if ($t.is('input')) {
            if ($t.attr('type').toLowerCase() == 'email') {
                if (!testInputEmail($t, options.showValidOnCheck)) validated = false;
            } 
            if ($t.attr('type').toLowerCase() == 'url') {
                if (!testInputURL($t, options.showValidOnCheck)) validated = false;
            }
            if ($t.attr('type').toLowerCase() == 'text') {
                if (!testInputText($t, options.showValidOnCheck)) validated = false;
            }
            if ($t.attr('type').toLowerCase() == 'checkbox') {
                if (!testCheckbox($t, options.showValidOnCheck)) validated = false;
            }
            if ($t.attr('type').toLowerCase() == 'radio') {
                if (!testRadio($t, options.showValidOnCheck)) validated = false;
            }
        }
        if ($t.is('textarea')) {
            if (!testInputText($t, false)) validated = false;
        }
        if ($t.is('select')) {
            if (!testInputSelect($t, options.showValidOnCheck)) validated = false;
        }
    });
    return validated;
})

Zakończenie


W ten oto sposób stworzyliśmy sobie własną bibliotekę do walidacji. Tak, wiem. Jest setka, może nawet tysiąc o wiele lepszych, bardziej wspaniałych gotowców. Wiedz jednak, że żaden z nich nie jest tak uroczy jak nasz. Żaden z nich też nie nauczył nas tyle co nasza własna biblioteka. I tym pozytywnym akcentem kończymy dzisiejszy odcinek.
A nie. Przepraszam. Pozostał mały bonus.

Bonus:

Czy zostało coś do zrobienia? Jakże by nie inaczej :)
Warto do naszego dzieła dodać dodatkowe parametry, które pozwolą wywołać funkcje dla każdego sprawdzanego pola.
Poniżej pokażę ci jak zamontować takie funkcje dla metody np sprawdzającej emaile:

$.fn.ownFormValidate = function(options) {
    options = $.extend({
        classError          : 'invalid',
        classOk             : 'valid',
        initialValidate     : true,
        parentElement       : '',
        showValidOnCheck    : false,
        isValid             : function($input) {}, //jak pole jest poprawne
        isInvalid           : function($input) {} //jak pole jest niepoprawne
    }, options);
var testInputEmail = function($input, showValid) {
    var wzorMaila = /^[0-9a-zA-Z_.-]+@[0-9a-zA-Z.-]+\.[a-zA-Z]{2,3}$/

    $object = (options.parentElement!='')?$input.parents(options.parentElement) : $input;

    if (!wzorMaila.test($input.val())) {
        $object.removeClass(options.classOk).addClass(options.classError);
        options.isInvalid($input); //odpal funkcję dla błędnego
        return false;
    } else {
        if (showValid) {
            $object.removeClass(options.classError).addClass(options.classOk);
            options.isValid($input); //odpal funkcję dla poprawnego
        }
        return true;
    }    
}

Zauważ, że do tego parametru naszej biblioteki przekazujemy aktualny $input, czyli wywołujemy podaną w parametrach biblioteki funkcję dla danego $inputa. Dzięki temu taka funkcja może np pokazywać tooltip z podpowiedzią pobraną z dodatkowego atrybutu data-…. tego pola lub robić cokolwiek innego.

$(function() {
    $('.form').ownFormValidate({
        isValid : function($elem) {
             console.log($elem + ' jest poprawny'
        },
        isInvalid : function($elem) {
             console.log($elem + ' jest niepoprawny');
        }
    });
});

Dla pozostałych metod dodanie tych dwóch parametrów jest analogiczne, pozostawiam je więc tobie jako zadanie domowe :) Skoro zaszedłeś aż tutaj, nie powinno to być olbrzymim wyzwaniem… Serdecznie pozdrawiam i do zoba… właściwie przeczytania.