Formularze

Formularze to w sumie jedne z najczęściej występujących komponentów na stronach. W poniższym tekście przyjrzymy się niektórym możliwościom jakie daje JavaScript jeżeli chodzi manipulowanie elementami formularza.

Forms

Wszystkie formularze na stronie mamy dostępne w postaci kolekcji document.forms:


const firstForm = document.forms[0];
//ale też możemy odwoływać się klasycznie
const firstForm = document.querySelector("form");

console.dir(firstForm);

Dla każdego formularza najczęściej wykorzystywanymi rzeczami będą:


//właściwości
form.elements - kolekcja wszystkich elementów formularza (osobiście rzadko z tego korzystam na rzecz ręcznego wybierania za pomocą querySelectorów)
form.action - atrybut action formularza, czyli adres na który formularz zostanie wysłany
form.method - metoda jaką zostanie wysłany formularz (omawiana w dziale Ajax)

//metody
form.submit(); //funkcja wysyłająca formularz
form.reset(); //funkcja resetująca formularz

Pola formularza

Każdy formularz składa się z różnego rodzaju pól. W zależności od tego jakiego typu takie pole jest, powinniśmy je nieco inaczej obsługiwać. Jest jednak kilka wspólnych rzeczy, które będą dla nich pomocne.

Jeżeli chodzi o właściwości, które nas interesują są to najczęściej:

input.value wartość pola, selekta itp.
input.disabled czy pole jest wyłączone
input.readOnly czy pole jest w trybie tylko do odczytu
input.checked czy pole jest zaznaczone (używane tylko dla checkbox, radio)

Jeżeli chodzi o zdarzenia, to głównie będą nas interesować:

change dla inputów tekstowych (:text, :url, :email, textarea itp) odpalany po zatwierdzeniu nowej wartości. Dla pól tekstowych oznacza to zmianę wartości i opuszczenie pola, dla checkboxów/radio kliknięcie takiego przycisku, a dla innych zmianę wartości
focus / blur eventy odpalane po wejściu i opuszczeniu danego pola. Stosowane raczej na pól tekstowych, gdy chcemy pokazać walidację gdy użytkownik opuści pole
keypress, keyup, keydown eventy związane z klawiaturą stosowane dla pól tekstowych
input event odpalany gdy wartość pola zostanie zmieniona przez użytkownika (np. gdy ją wpisuje, gdy klika na checkbox itp.)

I znowu - wszystko zależy od sytuacji. Może chcemy pokazać w tle duży tekst, który został wpisany do pola? A może za pomocą input:range chcemy sterować jakimś elementem na stronie?

Przyjrzyjmy się najczęściej spotykanym sytuacjom.

Pola typu TEXT

Prawdopodobnie główną właściwością, która będzie nas interesować będzie value, czyli aktualna wartość danego pola. Taka wartość zawsze będzie stringiem (nawet przy polach typu number i podobnych).


const input = document.querySelector("#name");

if (!input.value.length) {
    console.log("Pole nie ma żadnej wartości")
} else {
    console.log("Twoje imię to: " + input[0].toUpperCase() + input.slice(1) );
}

I w zasadzie tyle powinno nam w większości sytuacji wystarczyć. Cała reszta to odpowiednia manipulacja takim elementem i w zależności od sytuacji użycie odpowiednich zdarzeń.

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


const input = document.querySelector("#userName");
input.addEventListener("input", e => {
    const val = input.value;
    const reg = /^[a-zA-Z ]{3,}$/g;

    if (!reg.test(val)) {
        input.classList.add("field-error");
    } else {
        input.classList.remove("field-error");
    }
});

Sprawdzając rzeczy w formularzach warto mieć gdzieś z tyłu głowy zasadę "im mniej tym lepiej". Spójrz na powyższy kod. Zastosowaliśmy wyrażenie regularne /^[a-zA-Z ]{3,}$/g, co oznacza, że użytkownik może wpisać tylko litery małe/duże oraz spację. Niestety wyrażenie to nie uwzględnia polskich liter, nie mówiąc o innych znakach. Chcąc dobrze sprawiliśmy, że spora liczba użytkowników nie będzie mogła poprawnie wypełnić takiego formularza.

Pola typu RADIO i CHECKBOX

Przyciski radiowe umożliwiają wybór tylko jednej opcji z pośród kilku.

Aby sprawdzić, czy dany radio jest zaznaczony, musimy sprawdzić jego właściwość checked:


const input = document.querySelector("#someRadio")
if (input.checked) {
    console.log("Wartość radio: ", input.value);
}

Najczęściej jednak radio występują w grupach. Aby je obsłużyć musimy je pobrać i wykonać po nich pętlę sprawdzając wartość checked każdego z nich. Po natrafieniu na pierwszy zaznaczony, pętlę możemy przerwać, ponieważ w grupie o wspólnej nazwie nie może być kilku zaznaczonych radio.


const form = document.querySelector("#myForm");
const genderRadio = form.querySelectorAll("input[name=gender]");
const resultElement = document.querySelector("#genderResult");

for (const radio of genderRadio) {
    radio.addEventListener("change", e => {
        for (const radio of genderRadio) {
            if (radio.checked) {
                resultElement.innerText = radio.nextSibling.data; //pobieram tekst leżący obok radio
                break;
            }
        }
    });
}
Wybierz płeć
Wybrałeś płeć:

Pola checkbox są bardzo podobne w obsłudze do pól radio. Różnica tutaj jest taka, że w jednej grupie może być kilka zaznaczonych przycisków.


const form = document.querySelector("#formFruit");
const checkboxes = form.querySelectorAll("input[name=fruit]");
const fruitsResult = document.querySelector("#fruitsResult");

for (const chk of checkboxes) {
    chk.addEventListener("change", e => {
        let checkedCount = 0;
        for (const chk of checkboxes) {
            if (chk.checked) {
                checkedCount++;
            }
        }
        fruitsResult.innerText = checkedCount;
    });
}
Wybierz owoce
Wybrałeś

Powyższy kod możemy też uprościć za pomocą funkcji filter:


chk.addEventListener("change", e => {
    fruitsResult.innerText = [...checkboxes].filter(el => el.checked).length;
});

Ciekawostką jest natomiast to, że dzięki Javascript dla checkboxów możemy ustawić trzecią, niedostępną z HTML opcję - czyli indeterminate, która oznacza pół zaznaczenie:


const form = document.querySelector("#intermediateForm");
const checkboxes = form.querySelectorAll("input[name=intermediate]");

checkboxes[0].checked = false;
checkboxes[1].checked = true;
checkboxes[2].indeterminate = true;

Stan taki dość często wykorzystuje się w rozwijanych drzewkach, gdzie za pomocą checkboxów zaznaczamy jakieś elementy. Więcej na ten temat możesz dowiedzieć się tutaj.

Przykładowe zastosowanie może mieć postać:


const form = document.querySelector("form");
const checkboxAll = document.querySelector("input[name=selectAll]");
const checkboxes = document.querySelectorAll('input[name=indeterminate]');
const lngCheckboxes = [...checkboxes].length; //ile checkboxów

function checkAll() {
    const lngChecked = [...checkboxes].filter(el => el.checked).length; //ile zaznaczonych
    if (lngChecked === 0) {
        checkboxAll.checked = false;
        checkboxAll.indeterminate = false;
        return;
    }

    const theSame = lngChecked === lngCheckboxes; //liczba checkboxów taka sama jak zaznaczonych
    checkboxAll.indeterminate = !theSame;
    checkboxAll.checked = theSame;
};

for (const el of checkboxes) {
    el.addEventListener("click", e => checkAll);
}

checkboxAll.addEventListener("click", e => {
    for (const el of checkboxes) {
        el.checked = checkboxAll.checked;
    }
})

Pola typu SELECT


<select name="eyeColor">
    <option value="-1">wybierz kolor</option>
    <option value="blue">niebieski</option>
    <option value="green">zielony</option>
    <option value="brown">brązowy</option>
    <option value="black">czarny</option>
</select>

Żeby pobrać wybraną wartość z selekta, tak samo jak w przypadku innych pól posłużymy się tutaj wartością value.

Każdy selekt dodatkowo udostępnia nam jeszcze kilka dość ciekawych właściwości:


select.value //wartość pola
select.options //kolekcja elementów option
select.selectedIndex //indeks wybranego optiona
select.options[selectedIndex] - zaznaczony option

Nowy option dla selekta

Nowe elementy <option> spokojnie możemy robić tak samo jak inne elementy - czyli korzystając z funkcji creteElement, ale możemy też skorzystać z konstruktora Option(text*, value*, defaultSelected*, selected*):


const select = document.querySelector("select");

const option = new Option("Tekst w option", 102);
select.options[select.options.length-1] = option;

//to samo możemy uzyskać bardziej klasycznie
const option = document.createElement("option");
newOption.innerText = "Tekst w option";
newOption.value = 102;
select.appendChild(newOption);

Spróbujmy to wykorzystać do stworzenia selekta, który będzie dynamicznie wypełniany opcjami z innego selekta.
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="firstSelect" multiple="multiple">
        <option value="1"> opcja 1 </option>
        <option value="2"> opcja 2 </option>
        <option value="3"> opcja 3 </option>
    </select>

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

    <button type="button" id="copySelect">Kopiuj zaznaczone elementy option</button>
</form>

function copyOptions() {
    for (const option of select1.options){
        if (option.selected) {
            select2[select2.length] = new Option(option.innerText, option.value);
        }
    }
}

const form = document.querySelector(".form-move");
const select1 = form.querySelector("select[name=firstSelect]");
const select2 = form.querySelector("select[name=secondSelect]");
const btn = form.querySelector(".button");

btn.addEventListener("click", copyOptions);

Jeżeli byśmy chcieli dodatkowo z lewego selekta usuwać przerzucone wartości, tuż po ich skopiowaniu elementy te powinniśmy usunąć - tak samo jak usuwamy każdy inny element na stronie.


if (select1.length > 0) {
    for (let i=select1.length-1; i>=0; i--){
        const option = select1.options[i];
        if (option.selected) {
            select2[select2.length] = new Option(option.innerText, option.value);
            option.remove();
        }
    }
}

Pętlę robimy od końca do początku, bo gdy usuniemy dany element, zmienią się indeksy w kolekcji.

Input type color

Input typu color służy do pobierania koloru. Jest to jeden z prostszych do obsługi inputów, bo wystarczy tutaj obsłużyć tylko zdarzenie change:


<input type="color" class="input-color">
<div class="color-div"> Wybierz kolor </div>

const form = document.querySelector(".form-color");
const input = form.querySelector(".input-color");
const colorDiv = form.querySelector(".color-div");

input.addEventListener("change", e => {
    colorDiv.style.background = input.value;
    colorDiv.innerText = input.value;
});
Wybierz kolor

Nawet dla takiego prostego inputa mogą pojawić się "smaczki", którymi warto się zająć.

Po wybraniu koloru wrzucamy go do diva leżącego obok. Jeżeli jednak wybierzemy dość ciemny kolor, napis w divie będzie niewidoczny.

Możemy tutaj wykorzystać przepis z niezastąpionego StackOverflow:


//https://stackoverflow.com/questions/12043187/how-to-check-if-hex-color-is-too-black
//funkcja zwraca jasność koloru w przedziale 0-255, gdzie 0 to czarny, a 255 biały
function colorBrightness(color) {
    const c = color.substring(1);
    const rgb = parseInt(c, 16);
    const r = (rgb >> 16) & 0xff;
    const g = (rgb >>  8) & 0xff;
    const b = (rgb >>  0) & 0xff;

    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

const form = document.querySelector(".form-color");
const input = form.querySelector(".input-color");
const colorDiv = form.querySelector(".color-div");

input.addEventListener("change", e => {
    colorDiv.style.background = input.value;
    colorDiv.innerText = input.value;

    if (colorBrightness(input.value) > 150) {
        colorDiv.style.color = "#000";
    } else {
        colorDiv.style.color = "#fff";
    }
});
Wybierz kolor

Najważniejszy jednak problem w przypadku tego typu inputów jest to, że elementy te nie zadziałają wszędzie. W przeglądarkach, które nie wspierają takiego pola w ich miejsce pojawi się zwyczajne pole tekstowe, do którego możemy wpisać dowolną wartość. W dzisiejszych czasach nie jest to dla wielu frontendowców problemem, bo tworzą swoje aplikacje tylko dla nojnowszych przeglądarek. Ale nawet jeżeli dana przeglądarka wspiera takie pole, wcale nie oznacza to, że będzie to robić identycznie jak inne przeglądarki.

Z tego też powodu, dość często dla inputów stosuje się zewnętrzne pluginy, które nakładają na takie inputy odpowiednią funkcjonalność. W moich zakładkach znalazłem takie cudo. Na ichniejszej stronie jest dokładnie jak tego użyć a i znajdują się odpowiednie dema.

Input typu RANGE

Ostatni na naszej liście input range to kolejne bardzo proste w obsłudze pole. Pole - jeżeli nie zależy nam na na ładnym wyglądzie i dodatkowych funkcjonalnościach, ponieważ autorzy zrobili to pole bardzo topornym i w sumie mało dopracowanym.

Dla pól range idealnym wydaje się reagować na zdarzenia typu change lub input. To pierwsze będzie odpalane gdy zmienimy wartość pola, a to drugie w czasie jej zmieniania:

change:

Wartość pola:
input:

Wartość pola:

const form = document.querySelector("#rangeTest");
const inputs = form.querySelectorAll("input");
const input1 = inputs[0];
const input2 = inputs[1];

input1.addEventListener("change", e => {
    input1.nextElementSibling.innerText = "Wartość pola: " + input1.value
});

input2.addEventListener("change", e => {
    input2.nextElementSibling.innerText = "Wartość pola: " + input2.value
});

Jeżeli chodzi o zmianę wyglądu to można próbować działać samym CSS, ale nie zawsze da to oczekiwane rezultaty. Można też posłużyć się odpowiednimi pluginami - np. tym, tym lub http://nitinhayaran.github.io/jRange/demo/.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu