Formularze

Za pomocą Javascriptu możemy w łatwy sposób kontrolować zachowanie się formularzy znajdujących się na stronie.
Częstokroć do kontroli danych pochodzących z formularzy stosuje się skrypty działające po stronie serwera (np w technologii PHP, CGI).

Czasami chcemy mieć możliwość natychmiastowej reakcji na poczynania użytkownika (np. wyświetlenie informacji gdy użytkownik wprowadzi błędne imię). Wysłane dane można oczywiście dalej sprawdzić za pomocą wyżej wymienionych języków, co da nam o wiele większe szanse na to, że dane te są prawidłowo wprowadzone.

Odwoływanie się do elementów formularza

Istnieje kilka sposobów odwoływania się do elementów formularzy.
Stwórzmy przykładowy formularz na którym będziemy pracować:


    <form id="Formularz" name="Formularz" method="post" action="skrypt_przetwarzajacy.php">
    <fieldset>
        <legend>Pole</legend>
        <label for="Imie">Imię:</label>
        <input type="text" name="Imie" id="Imie" value="" />
        <label for="Mail">E-mail:</label>
        <input type="text" name="Mail" id="Mail" value="" />
    </fieldset>

    <fieldset>
        <legend>Wybierz płeć</legend>
        <input type="radio" value="m" name="plec" checked="checked" /> m
        <input type="radio" value="k" name="plec" /> k
    </fieldset>

    <fieldset>
        <legend>Wybierz dwa (2) ulubione owoce</legend>
        <input type="checkbox" value="jagoda" name="kolor" /> niebieski
        <input type="checkbox" value="jablko" name="kolor" /> czerwony
        <input type="checkbox" value="arbuz" name="kolor" /> zielony
        <input type="checkbox" value="pomarancz" name="kolor" /> pomarańcz
    </fieldset>

    <fieldset>        
        <input type="submit" value="Zapisz" />
    </fieldset>        
    </form>

Aby teraz odwołać się do atrybutu value pola "Imie" skorzystamy z konstrukcji:


document.forms[0].elements[1].value //pierwszym elementem jest pierwszy fieldset
document.forms['Formularz'].elements['Imie'].value 
document.Formularz.elements['Imie'].value 
document.forms['Formularz'].Imie.value

//za pomocą metod DOM
document.getElementById('Imie').value
document.querySelector('input[name="Imie"]').value
document.querySelectorAll('input[name="Imie"]')[0].value

Pola typu TEXT

Aby odczytać wartość pola typu text musimy odwołać się do jego właściwości value:


document.getElementById('Imie').value
document.getElementById('Formularz').Imie.value
document.querySelector('input[name="Imie"]').value

Wartość ta jest ciągiem znaków, możemy więc wykonać na niej wszystkie operacje, które możemy wykonywać na ciągach. I tak na przykład:


var fieldValue = document.getElementById("Tekst").value;

if (fieldValue.length == 0) {
    alert('Nie wpisałeś żadnej wartości!')
} else {
    alert('Twój tekst zaczyna się od: ' + fieldValue.charAt(0) );
}

Kolejny przykład pokazuje, jak możemy sprawdzić czy wpisywany ciąg ma odpowiednią formę. W przykładzie za pomocą wyrażeń regularnych sprawdzamy, czy użytkownik wpisał prawidłowe Imię:


<form method="post" action="...">
    <fieldset>
        <label for="ImieReg">Podaj imię:</label>
        <input type="text" id="ImieReg" name="ImieReg" value="" />
    </fieldset>
</form>

document.addEventListener("DOMContentLoaded", function() {
    document.getElementById('ImieReg').on('change', function() {
        var nameVal = this.value;
        var reg = /^[a-zA-Z]{3,}$/g
        if (!reg.test(nameVal)) {
            alert("Co to za dziwne imię?...");
            this.select();
        }
    });
});

Do naszego pola ImieReg dopięliśmy zdarzenie change, które zostanie wywołane w momencie zmiany wartości w tym polu. Anonimowa funkcja która została przypisana do tego zdarzenia odwołuje się do tego pola tekstowego instrukcją this. Wyrażenie regularne /^[a-zA-Z]{3,}$/g sprawdza, czy tekst w całości składa się tylko z liter i czy zawiera minimum 3 znaki (najkrótsze polskie imię).

Zastosowanie okienek alert jest bardzo proste w implementacji, jednak nie jest najbardziej przyjemną metodą informowania o błędnie wprowadzanych danych. Informację o złych danych lepiej wyświetlać w nieco bardziej subtelny sposób. Można np wyświetlać przy błędnie wypełnionym polu komunikat lub zaznaczać pole za pomocą dodatkowej klasy:


<input type="text" name="Imie" id="ImieReg2" value="" /><span class="zleDane">źle</span>

document.addEventListener("DOMContentLoaded", function() {
    document.getElementById('ImieReg2').addEventListener('change', function() {
        var classText = 'errorField';
        var reg = new RegExp('^[a-zA-Z]{3,}$', 'g');
    
        if (!reg.test(this.value)) {
            this.className += " "+classText; //dodaję klase do pola
        } else {
            var regError = new RegExp('(\\s|^)'+classText+'(\\s|$)');
            this.className = this.className.replace(regError, ''); //usuwam klasę
        }
    });
});

Do usunięcia klasy wykorzystałem RegExp. Można tutaj też skorzystać z classList, ale trzeba mieć na uwadze, że nie każda przeglądarka obecnie wspiera tą metodę. Kto co lubi...

Logiczne wydaje się, by sprawdzać dane po wywołaniu zdarzenia change. W końcu chcemy sprawdzić czy nowa (czyli zmieniona) wartość jest właściwa. Jest jednak pewne małe ALE. Przeglądarki nie odpalają tego zdarzenia, jeżeli wprowadzaną wartość wybierzemy z auto uzupełnienia (czyli pola, które pojawia nam się przy wypełnianym polu i daje możliwość wyboru wcześniej wprowadzonych odpowiedzi). Dlatego też w chwili obecnej nie możemy do końca polegać na zdarzeniu onChange. Są z tego dwa wyjścia. Jedno - stosowanie zdarzenia blur (czyli - po odznaczeniu/opuszczeniu danego elementu - w tym przypadku musimy przerywać sprawdzanie, gdy w pole zostanie wprowadzony pusty ciąg znaków (czyli gdy użytkownik tylko przeszedł przez pole).), lub ustawienie danemu polu atrybutu autocomplete="false". Bardziej logicznym rozwiązaniem wydaje się to drugie, jednak wiąże się z nim wprowadzenie utrudnienia dla użytkownika.


Kolejne pole w naszym formularzu powinno zawierać prawidłowy e-mail. Aby sprawdzić tą wartość, wystarczy zastosować wyrażenie regularne, które opisuje wygląd prawidłowego maila:


document.addEventListener("DOMContentLoaded", function() {
    document.getElementById('Mail').addEventListener('change', function() {
        var classText = 'errorField';
        var mailReg = new RegExp('^[0-9a-z_.-]+@[0-9a-z.-]+\.[a-z]{2,3}$', 'i');

        if (!mailReg.test(this.value)) {
            this.className += " "+classText; //dodaję klase do pola
        } else {
            var regError = new RegExp('(\\s|^)'+classText+'(\\s|$)');
            this.className = this.className.replace(regError, ''); //usuwam klasę
        }
    });
});
W poniższym formularzu sprawdzam tylko email:

Podobne techniki możemy stosować do większości typów danych - np do sprawdzania kodów pocztowych, miejscowości itp. Wystarczy odpowiednio opisywać takie dane za pomocą wyrażeń regularnych. Z drugiej strony nie powinniśmy też przesadzać z dokładnością sprawdzania takich danych. W końcu nie zawsze przeciętny użytkownik chce podawać dokładnie swoje dane. Czasami po prostu chcę sobie stworzyć konto "na szybko".

Pola typu RADIO

Przyciski radiowe umożliwiają wybór tylko jednej opcji z pośród kilku. Tak więc powinniśmy je stosować w przypadkach "albo". Albo lubię czekoladę, albo nie lubię ;).
Każde pole należące do jednej grupy powinno mieć wspólną nazwę, ale indywidualną wartość (no chyba, że w szczególnych przypadkach...).


<fieldset>
    <legend>Wybierz płeć</legend>
    <input type="radio" value="m" name="plec" /> m
    <input type="radio" value="k" name="plec" /> k
</fieldset>

function checkRadio() {
    for (var x=0; x<this.form.plec.length; x++) {
        if (this.form.plec[x].checked) {
            alert('Zaznaczyłeś opcję nr: ' + x);
            break;
        }
    }
}

document.addEventListener("DOMContentLoaded", function() {
    var form = document.getElementById('Formularz');
    for (var x=0; x<form.plec.length; x++) {
        form.plec[x].addEventListener('click', checkRadio);
    }
});
Wybierz płeć m k

Jak sprawdzić czy cokolwiek zostało wybrane (powiedzmy, że nie ustawimy właściwości checked dla żadnego pola)?
Wystarczy na samym początku funkcji ustawić dodatkową zmienną na -1. Następnie wykonując pętlę sprawdzamy, czy któreś pole jest zaznaczone. Jeżeli tak, naszej zmiennej przypisujemy numer zaznaczonego pola (czyli wartość od 0 do guziki_radio.length). Na przykład:


var checkedIndex = -1;
var sex = document.getElementById('Formularz').plec

for (var x=0; x<plec.length; x++) {
    if (sex[x].checked) {
        checkedIndex = x;
        break;
    }
}
if (checkedIndex == -1) {
    alert('Nie zaznaczyłeś żadnego pola!');
}

Pola typu CHECKBOX


<fieldset>
    <legend>Wybierz dwa (2) ulubione owoce</legend>
    <input type="checkbox" value="jagoda" name="kolor" /> niebieski
    <input type="checkbox" value="jablko" name="kolor" /> czerwony
    <input type="checkbox" value="arbuz" name="kolor" /> zielony
    <input type="checkbox" value="pomarancz" name="kolor" /> pomarańcz
</fieldset>

Przyciski typu checkbox umożliwiają wybór równocześnie kilku opcji.
Aby sprawdzić, czy dany checkbox jest zaznaczony, musimy odczytać jego właściwość checked, np:


if (document.getElementById('checkbox').checked) {
    alert("zaznaczony!");
}

Tak samo jak w przypadku pól radio, także pola checkbox grupujemy w tablicę dzięki nadaniu wspólnej nazwy. Dzięki temu możemy operować na checkboxach jak na tablicy:


function checkCheckbox() {
    var checkedCount = 0;
    var checkbox = this.form.kolor;

    for (var x=0; x<checkbox.length; x++) {
        if (checkbox[x].checked) {
            checkedCount++;
        }
    }
    if (checkedCount!=2) {
        alert('Wybrałeś ' +checkedCount+' owoce, a musisz wybrać 2');
    } else {
        alert('Wybrałeś ' +checkedCount+' owoce. Bądź pozdrowiony!');
    }
}

Do formy w której znajduje się przycisk wywołujący funkcję odwołałem się poprzez instrukcję this.form. Dzięki temu skrypt jest jeszcze bardziej uniwersalny, ponieważ nie musimy pobierać nazwy formularza.

Wybierz dwa (2) ulubione owoce
niebieski
czerwony
zielony
pomarańcz

Jeżeli w nazwie zastosujemy kwadratowe nawiasy, wysłane przez formularz wartości naszych zaznaczonych pól będą dostępne w PHP w postaci tablicy:


<input type="checkbox" name="hobby[]" value="rower"> rower<br />
<input type="checkbox" name="hobby[]" value="kino"> kino<br />
<input type="checkbox" name="hobby[]" value="komputer"> komputer<br />
<input type="checkbox" name="hobby[]" value="rysowanie"> rysowanie<br />

W powyższym przykładzie odwołanie się poprzez nazwę (name) nie będzie możliwe. Metoda getElementById też odpada, gdyż nasze elementy musiały by mieć identyczne ID, co jest niedopuszczalne. Aby odwołać się do tak nazwanych elementów trzeba zastosować konstrukcję:

var tablicaHobby = document.Formularz.elements['hobby[]'];

Więcej o przypadku stosowania nawiasów kwadratowych w nazwach możesz dowiedzieć się tutaj.

Pola typu SELECT


<select name="kolorOczu">
    <option value="-">wybierz kolor</option>
    <option value="niebieskie">niebieskie</option>
    <option value="zielone">zielone</option>
    <option value="bronzowe">bronzowe</option>
    <option value="czarne">czarne</option>
</select>

Elementy option pola select są zgrupowane w tablicy która ma nazwę taką samą jak element select (w naszym przykładzie "kolorOczu").


var select = document.getElementById('Formularz').kolorOczu;
var optionsLength = obiektSelect.length

//lub

var select = document.getElementById('Formularz').kolorOczu;
var optionsLength = obiektSelect.options.length

Każdy z obiektów typu SELECT udostępnia nam tablicę options w której znajdują się kolejne pozycje (obiekty) option.

Aby sprawdzić który element został wybrany skorzystamy z właściwości selectedIndex:


var select = document.getElementById('Formularz').kolorOczu;
alert(select.selectedIndex); //wypisze numer wybranego indeksu

Aby sprawdzić wartość value wybranego elementu skorzystamy z instrukcji:


var select = document.getElementById('Formularz').kolorOczu;
alert( select.options[select.selectedIndex].value );
Przykładowo przy zmianie (change) selekta wypiszmy jego aktualną wartość:

document.addEventListener("DOMContentLoaded", function() {
    document.getElementById('formularzSelect').kolorOczu.addEventListener('change', function() {
        alert('Wybrałeś: ' + this.options[this.selectedIndex].value);
    });
});
Wybierz kolor

Javascript daje także możliwość tworzenie nowych pól option. Tak samo jak przy tworzeniu nowych obiektów skorzystamy tutaj z instrukcji new:


var optionText = "zaznacz mnie";
var optionValue = "opcja";

var newOption = new Option(optionText, optionValue);
var select = document.getElementById('Formularz').kolorOczu;
    select.options[x] = NowyOption;

//lub

document.getElementById('Formularz').kolorOczu.options[x] = new Option(optionText, optionValue, 0, 0);

Tworząc nowy obiekt option musimy podać dla niego właściwości:

  • tekst - czyli tekst dla danego option
  • value - wartość danego option
  • defaultSelected - dodatkowy parametr - wskazuje, czy dany elemeny jest domyślnie wybrany
  • selectedIndex - dodatkowy parametr - wskazuje czy dany element jest wybrany. W polach select dla którego nie ustawiliśmy atrybutu multiply="multiply" wskazuje index wybranego elementu. Dla pól z atrybutem multiply wskazuje index tylko pierwszego wybranego elementu. W takim przypadku musimy wykonać pętlę po wszystkich elementach danego selecta i sprawdzać każdy element z osobna.

Jeżeli dodamy element o indeksie który już istnieje, to stary element zostanie zastąpiony. Jeżeli dodamy element o indeksie nr 10, a lista ma ich tylko 5, to powstaną puste, niezdefiniowane pola, a na miejscu 10 nowy element (w FireFoxie można definiować tylko elementy które są kolejnymi elementami dla SELECTA).

Dodawanie nowych elementów - szczególnie z wykorzystaniem pętli jest bardzo wygodną metodą tworzenia elementów typu select.

Przykład zastosowania pętli do konstruowania select możesz zobaczyć w formularzu wyboru daty.


Stwórzmy selecta, który będzie zawierał wybrane elementy innego selecta. Aby wykonać to zadanie, wykonujemy pętlę po elementach option pierwszego selecta. Gdy dany element option jest zaznaczony (selected), wówczas tworzymy w drugim selekcie nowy option, który ma tekst i value takie same jak dany element w pierwszym selekcie.


<form>
    <select id="pierwszy" multiple="multiple">
        <option value="1"> opcja 1 </option>
        <option value="2"> opcja 2 </option>
        <option value="3"> opcja 3 </option>
    </select>

    <select id="drugi" multiple="multiple">
    </select>

    <input type="button" id="kopiuj" value="Kopiuj zaznaczone elementy option" />
</form>

function moveOptions(selectFrom, selectTo) {
    var select1 = document.getElementById(selectFrom);
    var select2 = document.getElementById(selectTo);

    if (select1.length>0) {
        for (var x=0; x<select1.length; x++){
            if (select1.options[x].selected) {
                var newOption = new Option(select1.options[x].firstChild.nodeValue, select1.options[x].value, false, false);
                select2[select2.length] = newOption;
            }
        }
    }
}

document.addEventListener("DOMContentLoaded", function() {
    document.getElementById('kopiuj').addEventListener('click', function(){
        moveOptions('pierwszy', 'drugi')
    });
});