Zdarzenia myszy, klawiatury, formularza i strony w języku JavaScript: trzy sposoby podpięcia obsługi, addEventListener, obiekt zdarzenia i preventDefault oraz walidacja formularza po stronie klienta z komunikatem dla użytkownika. Lekcja 11.7 kursu INF.03.
Zdarzenie (event) to sygnał, który przeglądarka wysyła w chwili, gdy na stronie coś się dzieje: użytkownik klika przycisk, wciska klawisz, wybiera pozycję z listy albo wysyła formularz. Skrypt wczytany razem ze stroną przechodzi raz od góry do dołu i na tym kończy, więc każda reakcja na działanie użytkownika zaczyna się od podpięcia funkcji pod zdarzenie. W części pisemnej wracają pytania o nazwy zdarzeń („które zdarzenie zachodzi po najechaniu kursorem", „które pozwala zablokować wysłanie formularza", „w jakim języku pisze się weryfikację formularza po stronie przeglądarki"). W zadaniu praktycznym arkusz każe podpiąć skrypt pod przycisk albo pod załadowanie strony, pobrać wartość z pola formularza i sprawdzić ją przed wysłaniem.
Trzy sposoby podpięcia obsługi
Obsługa zdarzenia to funkcja, którą przeglądarka wywołuje w chwili, gdy zdarzenie zajdzie na wskazanym elemencie. Podpiąć ją można na trzy sposoby i wszystkie trzy stoją w bankach pytań.
Pierwszy to atrybut w znaczniku HTML. Nazwa atrybutu to nazwa zdarzenia z przedrostkiem on, a jego wartość to kod wykonywany po zajściu zdarzenia, zwykle wywołanie funkcji z nawiasami.
<button onclick="policz()">Policz</button>
Drugi to właściwość elementu. Element pobiera się metodą getElementById, opisaną w lekcji o modelu DOM, a potem przypisuje mu się funkcję. const przycisk = document.getElementById("licz");
przycisk.onclick = policz;
Po prawej stronie stoi sama nazwa funkcji, bez nawiasów. Nawiasy wywołałyby ją od razu przy wczytywaniu strony, a pod zdarzenie trafiłaby wtedy zwrócona przez nią wartość.
Trzeci to metoda addEventListener (DODAJ SŁUCHACZA ZDARZENIA). Bierze dwa argumenty: nazwę zdarzenia jako napis i funkcję do wywołania.
const przycisk = document.getElementById("licz");
przycisk.addEventListener("click", policz);
Sposób
Zapis
Ograniczenie
atrybut znacznika
<button onclick="policz()">
kod skryptu miesza się z treścią strony
właściwość elementu
przycisk.onclick = policz;
tylko jedna funkcja na zdarzenie
addEventListener
przycisk.addEventListener("click", policz);
wymaga wcześniejszego pobrania elementu
Trzeci sposób jest najlepszy z dwóch powodów. Pod jedno zdarzenie jednego elementu wolno podpiąć dowolnie wiele funkcji i wykonają się one po kolei, a kolejne przypisanie do onclick kasuje poprzednie i zostawia wyłącznie ostatnią funkcję. Do tego cały kod zostaje w pliku .js, więc znacznik w HTML nie wie nic o skrypcie. Metoda removeEventListener z tą samą nazwą zdarzenia i tą samą funkcją odpina obsługę z powrotem.
Pułapka: w addEventListener nazwa zdarzenia jest napisem BEZ przedrostka on, czyli "click", a nie "onclick". Przedrostek ma tylko atrybut w znaczniku i właściwość elementu.
Zdarzenia myszy i klawiatury
Zdarzenie myszy to zdarzenie wywołane ruchem kursora albo naciśnięciem przycisku myszy, a zdarzenie klawiatury naciśnięciem klawisza. To one wracają w pytaniach pisemnych najczęściej, bo wystarczy znać nazwę.
Zdarzenie
Atrybut
Kiedy zachodzi
click
onclick
pojedyncze kliknięcie elementu
dblclick
ondblclick
dwukrotne kliknięcie elementu
mousedown
onmousedown
wciśnięcie przycisku myszy
mouseup
onmouseup
zwolnienie przycisku myszy
mouseover
onmouseover
kursor wchodzi na element
mouseout
onmouseout
kursor opuszcza element
keydown
onkeydown
klawisz klawiatury został naciśnięty
keyup
onkeyup
klawisz klawiatury został zwolniony
keypress
onkeypress
naciśnięcie klawisza znakowego, zapis wycofany ze standardu
Para mouseover i mouseout obsługuje podświetlanie: pierwsze zachodzi w chwili wjechania kursora na element, drugie po jego zjechaniu.
const promocja = document.getElementById("promocja");
promocja.addEventListener("mouseover", function () {
promocja.style.backgroundColor = "Tomato";
});
promocja.addEventListener("mouseout", function () {
promocja.style.backgroundColor = "white";
});
Wynik: tło elementu o id="promocja" robi się czerwone pod kursorem i wraca do białego, gdy kursor go opuści.
Licznik wpisanych znaków podpina się pod keyup, a nie pod keydown. Zdarzenie keydown zachodzi PRZED wpisaniem znaku do pola, więc odczytana w nim wartość jest jeszcze o jeden znak krótsza.
const uwagi = document.getElementById("uwagi");
const licznik = document.getElementById("licznik");
uwagi.addEventListener("keyup", function () {
licznik.innerHTML = uwagi.value.length;
});
Wynik: po każdym naciśnięciu klawisza w elemencie o id="licznik" pojawia się aktualna liczba znaków wpisanych w polu.
Zdarzenia formularza i strony
Zdarzenie formularza to zdarzenie zachodzące na kontrolce albo na całym formularzu podczas jego wypełniania i wysyłania, a zdarzenie strony dotyczy całego dokumentu i jego wczytywania.
Zdarzenie
Atrybut
Kiedy zachodzi
Na czym
submit
onsubmit
próba wysłania formularza
<form>
reset
onreset
naciśnięcie przycisku kasującego
<form>
change
onchange
zmiana wartości zatwierdzona wyjściem z pola
kontrolka
input
oninput
każda zmiana wartości, znak po znaku
kontrolka
focus
onfocus
kursor wchodzi do pola
kontrolka
blur
onblur
kursor opuszcza pole
kontrolka
load
onload
strona razem z obrazami jest wczytana
<body>
DOMContentLoaded
brak atrybutu
kod HTML jest przetworzony, obrazy jeszcze nie
document
Zdarzenie submit zachodzi na znaczniku <form>, nie na przycisku: przycisk typu submit tylko je wyzwala. Obsługę wysyłki podpina się więc do formularza, a nie do przycisku. Przy liście rozwijanej change zachodzi zaraz po wybraniu innej pozycji, ale w polu tekstowym dopiero po wyjściu z niego, więc do sprawdzania w trakcie pisania nadaje się input albo keyup.
Skrypt umieszczony w znaczniku <head> wykonuje się, zanim przeglądarka zbuduje resztę strony, więc getElementById zwraca w nim null i podpięcie obsługi się nie udaje. Wyjścia są dwa: dołączenie pliku zapisem <script src="skrypt.js"></script> przed zamknięciem </body>, tak jak robią to arkusze CKE, albo owinięcie kodu w obsługę zdarzenia DOMContentLoaded.
document.addEventListener("DOMContentLoaded", function () {
const przycisk = document.getElementById("licz");
przycisk.addEventListener("click", policz);
});
Wynik: obsługa kliknięcia zostaje podpięta dopiero wtedy, gdy przycisk o id="licz" istnieje już w dokumencie. Zdarzenie load czeka dodatkowo na obrazy i arkusze stylów, więc uruchamia kod później niż DOMContentLoaded.
Obiekt zdarzenia i domyślne działanie
Obiekt zdarzenia to zestaw danych o tym, co się właśnie stało, podawany funkcji obsługującej jako pierwszy argument. Zwyczajowo nazywa się go event albo krócej e.
event.type
nazwa zdarzenia, na przykład click
event.target
element, na którym zdarzenie zaszło
event.key
znak naciśniętego klawisza, w zdarzeniach klawiatury
event.preventDefault()
blokuje domyślne działanie przeglądarki
Domyślne działanie to reakcja, którą przeglądarka wykonuje sama, bez skryptu: kliknięty odnośnik przenosi na inną stronę, a przycisk typu submit wysyła formularz i przeładowuje stronę. Metoda preventDefault (ZAPOBIEGNIJ DOMYŚLNEMU) tę reakcję zatrzymuje, a reszta funkcji wykonuje się normalnie.
const formularz = document.getElementById("zamowienie");
formularz.addEventListener("submit", function (event) {
event.preventDefault();
document.getElementById("komunikat").innerHTML = "Sprawdź ilość";
});
Wynik: naciśnięcie przycisku wysyłającego nie przeładowuje już strony, a w elemencie o id="komunikat" pojawia się napis.
W zapisie atrybutem tę samą rolę pełni słowo return (ZWRÓĆ). Zapis onsubmit="return sprawdz()" wysyła formularz tylko wtedy, gdy funkcja sprawdz zwróci true, a zwrócone false zatrzymuje wysyłkę dokładnie tak samo jak preventDefault. Samo onsubmit="sprawdz()" bez słowa return nie zatrzyma niczego, choćby funkcja zwracała false.
Walidacja formularza po stronie klienta
Walidacja to sprawdzenie, czy wprowadzone dane spełniają określone reguły. Po stronie klienta robi ją przeglądarka, zanim cokolwiek pojedzie na serwer, więc użytkownik dostaje komunikat od razu, bez przeładowania strony.
Pierwszą warstwę daje sam HTML5, bez jednej linii skryptu: required nie wypuszcza pustego pola, type="number" z atrybutami min i max pilnuje zakresu, pattern sprawdza wzorzec, a novalidate w znaczniku <form> wyłącza całe sprawdzanie. Atrybuty te opisuje lekcja o formularzach HTML. Reguły HTML5 sprawdzane są PRZED zdarzeniem submit, więc gdy pole z required zostanie puste, funkcja podpięta pod submit w ogóle się nie uruchomi. JavaScript dokłada to, czego atrybuty nie potrafią: porównanie dwóch pól ze sobą, własną treść komunikatu i własne miejsce, w którym on się pokazuje. Schemat jest zawsze ten sam: pobierz wartość, sprawdź warunek, przy błędzie pokaż komunikat i zwróć false.
<form id="zamowienie" action="zamow.php" method="post" onsubmit="return sprawdz()">
<input type="text" id="imie" name="imie">
<input type="text" id="ilosc" name="ilosc">
<input type="submit" value="Zamów">
</form>
function sprawdz() {
const imie = document.getElementById("imie").value.trim();
const ilosc = Number(document.getElementById("ilosc").value);
if (imie === "") {
alert("Podaj imię i nazwisko");
return false;
}
if (isNaN(ilosc) || ilosc < 1 || ilosc > 10) {
alert("Ilość musi być liczbą od 1 do 10");
return false;
}
return true;
}
Wynik: puste imię albo ilość spoza zakresu od 1 do 10 zatrzymuje formularz i pokazuje okno z komunikatem, a poprawne dane jadą metodą POST do pliku zamow.php.
Trzy kawałki tego kodu warto rozebrać. Właściwość value (wartość) oddaje zawsze NAPIS, nawet w polu type="number", więc porównanie liczbowe wymaga konwersji funkcją Number albo parseInt. Metoda trim (PRZYTNIJ) usuwa spacje z obu końców napisu, bo bez niej pole wypełnione samymi spacjami przechodzi jako niepuste. Funkcja isNaN (NIE JEST LICZBĄ) łapie wpisany tekst, bo Number("dwa") daje NaN, a każde porównanie z NaN jest fałszywe, więc sam warunek zakresu takiej wartości by nie odrzucił.
Komunikat nie musi być oknem alert. Wstawiony do pustego akapitu obok pola zostaje na ekranie i nie blokuje strony.
const komunikat = document.getElementById("komunikat");
komunikat.innerHTML = "Ilość musi być liczbą od 1 do 10";
komunikat.style.color = "Maroon";
Wynik: bordowy napis w elemencie o id="komunikat". Właściwość innerHTML wstawia kod HTML, więc do tekstu pochodzącego od użytkownika bezpieczniejsza jest właściwość textContent, która traktuje wszystko jako zwykły napis.
Pułapka: walidacja u klienta to wygoda dla użytkownika, a nie zabezpieczenie. JavaScript da się wyłączyć, a żądanie wysłać z pominięciem strony, więc te same reguły musi powtórzyć skrypt na serwerze, opisany w lekcji o formularzach w PHP. Na egzaminie
Pytania pisemne o zdarzenia sprowadzają się najczęściej do jednej nazwy. Reakcją na pojedyncze kliknięcie dowolnego elementu strony jest onclick, a nie onload ani onkeydown. Zdarzeniem wyzwalanym, gdy kursor myszy znajdzie się nad elementem, jest onmouseover, natomiast onmouseout zachodzi po jego opuszczeniu, a onmousedown i onmouseup dotyczą przycisku myszy, nie ruchu kursora. Zdarzenie onkeydown zachodzi w chwili naciśnięcia klawisza KLAWIATURY, a onkeyup po jego zwolnieniu, więc funkcja, która ma działać po każdym wpisanym do pola znaku, podpina się pod jedno z nich. Kodem wykonywanym w chwili wysyłania formularza, z możliwością zablokowania albo przepuszczenia wysyłki, steruje onsubmit, nie onclick ani onchange.
Osobna grupa pytań dotyczy podziału pracy między przeglądarkę i serwer. Weryfikację kompletności formularza działającą po stronie przeglądarki pisze się w języku JavaScript, a nie w PHP, CSS czy Ruby. Gdy zadanie mówi o sprawdzaniu danych w czasie rzeczywistym i zapisaniu ich potem do bazy, najprostszy podział to walidacja w języku JavaScript i przesłanie danych skryptem PHP. Zdarza się też ramka z kodem i pytanie o efekt kliknięcia przycisku: trzeba wtedy prześledzić, który element pobiera getElementById i co skrypt mu zmienia, innerHTML czy style.
W zadaniu praktycznym polecenie brzmi zwykle „przycisk uruchamiający skrypt" albo „skrypt wykonywany po załadowaniu strony". Pierwsze to <button onclick="nazwa()"> lub addEventListener po pobraniu przycisku, drugie to <body onload="nazwa()"> albo plik dołączony przed </body>. Identyfikatory muszą zgadzać się co do znaku: id="ilosc" w znaczniku i getElementById("ilosc") w kodzie, przy czym name służy wysyłce na serwer, a id wyszukiwaniu w skrypcie. Arkusze piszą skrypty w starszym stylu, z var zamiast let, obsługą w atrybucie onclick i wypisywaniem przez document.write. Taki zapis działa i odtworzenie go nie jest błędem, choć w kodzie pisanym od zera lepsze są let, const i addEventListener, a document.write uruchomiony po wczytaniu strony kasuje jej dotychczasową treść. Oceniane rezultaty to „skrypt" i „działanie witryny", więc egzaminator klika po gotowej stronie: skrypt, który nie reaguje na kliknięcie, punktu nie dostanie, choćby w pliku wyglądał poprawnie.
Ściąga
<button onclick="licz()">
obsługa w atrybucie znacznika, wywołanie z nawiasami
przycisk.onclick = licz;
obsługa we właściwości, bez nawiasów, tylko jedna funkcja
el.addEventListener("click", licz);
obsługa dopisana, nazwa zdarzenia bez on, wiele funkcji
el.removeEventListener("click", licz);
odpięcie wcześniej dodanej obsługi
onclick, ondblclick
pojedyncze i dwukrotne kliknięcie elementu
onmouseover, onmouseout
kursor wchodzi na element i go opuszcza
onmousedown, onmouseup
wciśnięcie i zwolnienie przycisku myszy
onkeydown, onkeyup
klawisz klawiatury naciśnięty i zwolniony
onsubmit
próba wysłania formularza, zachodzi na znaczniku <form>
onchange, oninput
zmiana wartości po wyjściu z pola i znak po znaku
onfocus, onblur
wejście kursora do pola i wyjście z niego
onload
strona z obrazami wczytana, atrybut znacznika <body>
DOMContentLoaded
kod HTML przetworzony, obrazy jeszcze nie
event.target
element, na którym zdarzenie zaszło
event.preventDefault()
blokuje domyślne działanie, na przykład wysyłkę formularza
onsubmit="return sprawdz()"
wysyła formularz tylko przy zwróconym true
pole.value
wartość kontrolki, zawsze napis
Number(pole.value)
konwersja wartości pola na liczbę przed porównaniem
isNaN(x)
prawda, gdy x nie jest liczbą
Pobierz ściągę PDF