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)
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(); //wysłanie formularza bez zdarzenia submit i z pominięciem htmlowej walidacji
form.requestSubmit(); //wysłanie formularza i wywołanie dla niego zdarzenia submit
form.reset(); //zresetowanie formularza
//zdarzenia
submit - odpalane w momencie wysyłki formularza przez użytkownika
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 | zdarzenia 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 | zdarzenia 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.
Wartość pola
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 w formie tekstowej (nawet przy polach typu number, range i podobnych).
const input = document.querySelector("#name");
const name = input.value;
if (!name.length) {
console.log("Pole nie ma żadnej wartości")
} else {
console.log("Twoje imię to: " + name.toUpperCase() + name.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("is-invalid");
} else {
input.classList.remove("is-invalid");
}
});
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.
Pierwsza z nich -
valueAsNumber
- dla pól typu number zwraca wartość w postaci liczby, natomiast dla pól typu date zwraca czas Unixowy, czyli w milisekundach od 1 stycznia 1970. Ta druga właściwość dla pól date zwraca wynik w postaci daty.Druga to
valueAsDate
i zwraca wartość pola typu date w postaci daty, a nie tekstu. Do przetestowania tutaj.
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 radioGroup = form.querySelectorAll("input[name=gender]");
const resultElement = document.querySelector("#genderResult");
for (const radio of radioGroup) {
radio.addEventListener("change", e => {
for (const radio of radioGroup) {
if (radio.checked) {
resultElement.innerText = radio.nextSibling.data; //pobieram tekst leżący obok radio
break;
}
}
});
}
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 checkboxGroup = form.querySelectorAll("input[name=fruit]");
const fruitsResult = document.querySelector("#fruitsResult");
for (const chk of checkboxGroup) {
chk.addEventListener("change", e => {
let checkedCount = 0;
for (const chk of checkboxGroup) {
if (chk.checked) {
checkedCount++;
}
}
fruitsResult.innerText = checkedCount;
});
}
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 = form.querySelector("input[name=selectAll]");
const checkboxes = form.querySelectorAll('input[name=indeterminate]');
function checkAll() {
const lngCheckboxes = [...checkboxes].length; //ile checkboxów
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.selectedOpitons | kolekcja wybranych opcji. Będzie zawierać 1 element, chyba, że selekt ma atrybut multiple |
select.selectedIndex | indeks wybranego 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;
});
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";
}
});
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 najnowszych 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:
const form = document.querySelector("#rangeTest");
const inputs = form.querySelectorAll("input");
const input1 = inputs[0];
const input2 = inputs[1];
input1.addEventListener("change", () => {
input1.nextElementSibling.innerText = "Wartość pola: " + input1.value
});
input2.addEventListener("input", () => {
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 tym.