Temat 18 · 2 × 45 minINF.03
Walidacja formularzy: HTML5 i JavaScript
Formularz bez walidacji przyjmie wszystko: puste imię, wiek „abc”, hasło różne od powtórzenia. Walidacja (validation) to sprawdzenie danych zanim zostaną użyte albo wysłane. Przeglądarka potrafi zrobić część pracy sama – wystarczą atrybuty HTML5 – a resztę piszesz w JavaScripcie: porównanie dwóch pól, własny komunikat w akapicie, zmiana koloru. W tej lekcji poznasz obie warstwy, dowiesz się, jak współpracują ze zdarzeniem submit, i jak zbudować komunikaty tak, by zrozumiał je także użytkownik czytnika ekranu.
Dwie warstwy walidacji
| Warstwa | Gdzie | Co sprawdza | Zalety | Ograniczenia |
|---|---|---|---|---|
| HTML5 (atrybuty) | w znacznikach input | puste pole, zakres, długość, format, typ | zero kodu, dymki przeglądarki, działa bez JS | komunikaty standardowe, tylko pojedyncze pole, wymaga <form> i przycisku submit |
| JavaScript | w skrypcie | wszystko, także zależności między polami (hasło = powtórzenie) | dowolny komunikat, miejsce, kolor | trzeba napisać i przetestować |
| Serwer (PHP itp.) | po wysłaniu | wszystko jeszcze raz | jedyna warstwa, której użytkownik nie obejdzie | poza tą lekcją |
Walidacja po stronie klienta (HTML5 + JS) daje natychmiastową reakcję, ale nie jest zabezpieczeniem – użytkownik może wyłączyć skrypty albo zmienić HTML w narzędziach deweloperskich. Dlatego serwer zawsze sprawdza dane ponownie. Na egzaminie INF.03 liczy się warstwa kliencka: w części pisemnej jako „zadanie wykonywane tylko po stronie klienta – sprawdzanie danych w polu w czasie rzeczywistym”, w części praktycznej jako komunikat w akapicie.
Atrybuty HTML5
Atrybuty wpisujesz w input, select lub textarea. Przeglądarka sprawdza je przy próbie wysłania formularza i blokuje wysłanie, pokazując dymek przy pierwszym niepoprawnym polu.
| Atrybut | Znaczenie | Przykład |
|---|---|---|
required | pole nie może być puste (checkbox: musi być zaznaczony; radio: jedna z grupy) | <input required> |
min, max | zakres liczby lub daty (type=number, range, date) | <input type="number" min="1" max="10"> |
step | dozwolony krok (step="0.01" dla groszy, step="any" – dowolny) | <input type="number" step="0.5"> |
minlength, maxlength | długość napisu w znakach | <input minlength="3" maxlength="20"> |
pattern | wyrażenie regularne, które musi pasować do całej wartości | <input pattern="[0-9]{2}-[0-9]{3}"> |
type="email" | wymaga postaci nazwa@domena | <input type="email"> |
type="url" | wymaga adresu ze schematem, np. https://… | <input type="url"> |
type="number" | tylko liczba (litery nie dają się wpisać, a jeśli się dadzą – pole jest niepoprawne) | <input type="number"> |
<form id="zapisy">
<p><label for="imie">Imię (min. 2 znaki):</label>
<input id="imie" name="imie" required minlength="2" maxlength="30"></p>
<p><label for="wiek">Wiek (13–19):</label>
<input id="wiek" name="wiek" type="number" required min="13" max="19"></p>
<p><label for="email">E-mail:</label>
<input id="email" name="email" type="email" required></p>
<p><label for="kod">Kod pocztowy (NN-NNN):</label>
<input id="kod" name="kod" pattern="[0-9]{2}-[0-9]{3}" placeholder="00-000" required></p>
<p><label><input type="checkbox" name="regulamin" required> Akceptuję regulamin</label></p>
<button type="submit">Zapisz</button>
</form>
<p id="status"></p>// Skrypt tylko informuje, że formularz przeszedł walidację HTML5.
// Zdarzenie submit NIE zachodzi, dopóki któreś pole jest niepoprawne.
document.getElementById('zapisy').addEventListener('submit', (zdarzenie) => {
zdarzenie.preventDefault(); // nie wysyłaj (w placu zabaw nie ma serwera)
document.getElementById('status').textContent = 'Dane poprawne – formularz zostałby wysłany.';
});Kliknij „Zapisz” z pustymi polami: dymek pojawia się przy pierwszym błędnym polu, a tekst „Dane poprawne…” nie wyświetla się, bo funkcja w ogóle nie została wywołana.
Które pole zablokuje wysłanie formularza, gdy użytkownik wpisze w nie 7?
Kilka szczegółów, które mylą: pattern nie wymaga ^ i $ – wzorzec i tak musi objąć całą wartość. type="email" uznaje za poprawne jan@szkola (bez kropki w domenie) – jeśli chcesz wymusić domenę z kropką, dodaj pattern. placeholder to podpowiedź, nie wartość: pole z samym placeholder jest puste i required je odrzuci.
Stylowanie: :invalid, :valid, :user-invalid
Przeglądarka na bieżąco oznacza pola jako poprawne lub nie, a CSS może to pokazać. Pseudoklasa :invalid działa jednak od razu po załadowaniu strony – puste pole required jest już niepoprawne, więc cały formularz świeci na czerwono, zanim użytkownik cokolwiek wpisze. Dlatego do stylów błędów używaj :user-invalid – pasuje dopiero po interakcji użytkownika z polem (od końca 2023 r. działa we wszystkich głównych przeglądarkach).
<form>
<p><label for="login">Login (3–12 znaków, litery i cyfry):</label><br>
<input id="login" pattern="[A-Za-z0-9]{3,12}" required>
<span class="znak"></span></p>
<p><label for="pin">PIN (4 cyfry):</label><br>
<input id="pin" inputmode="numeric" pattern="[0-9]{4}" required>
<span class="znak"></span></p>
<button type="submit">Sprawdź</button>
</form>input { padding: 6px; border: 2px solid #999; border-radius: 4px; }
input:user-invalid { border-color: Crimson; background: #fff0f0; }
input:user-invalid + .znak::after { content: " – popraw to pole"; color: Crimson; }
input:valid { border-color: SeaGreen; }
input:focus { outline: 3px solid #F2D338; }document.querySelector('form').addEventListener('submit', (e) => e.preventDefault());Przydadzą się też :required, :optional, :in-range, :out-of-range i :placeholder-shown. Pamiętaj: kolor to dodatek – komunikat tekstowy jest obowiązkowy (patrz sekcja o dostępności).
Sterowanie walidacją HTML5 z JavaScriptu
Każde pole i cały formularz mają Constraint Validation API – zestaw metod i właściwości, którymi skrypt może zapytać o wynik walidacji HTML5 i go uzupełnić.
| Element | Co daje |
|---|---|
pole.checkValidity() | true/false; przy false wywołuje na polu zdarzenie invalid, bez dymka |
pole.reportValidity() | jak wyżej, ale pokazuje dymek przeglądarki |
formularz.checkValidity() | true, gdy wszystkie pola są poprawne |
pole.validity | obiekt z flagami: valueMissing, typeMismatch, patternMismatch, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput, customError, valid |
pole.validationMessage | tekst, który pokazałby dymek (po polsku, jeśli przeglądarka jest po polsku) |
pole.setCustomValidity('tekst') | ustawia własny błąd – pole staje się niepoprawne, dymek pokaże twój tekst |
pole.setCustomValidity('') | kasuje własny błąd (bez tego pole zostanie niepoprawne na zawsze) |
<form novalidate> | wyłącza dymki i blokadę – walidację robisz sam, ale checkValidity() nadal działa |
Najczęstszy przypadek użycia setCustomValidity: porównanie dwóch pól, czego atrybuty nie potrafią.
<form id="rejestracja">
<p><label for="haslo">Hasło (min. 6 znaków):</label>
<input id="haslo" type="password" minlength="6" required></p>
<p><label for="haslo2">Powtórz hasło:</label>
<input id="haslo2" type="password" required></p>
<button type="submit">Załóż konto</button>
</form>
<p id="komunikat"></p>const haslo = document.getElementById('haslo');
const haslo2 = document.getElementById('haslo2');
function sprawdzZgodnosc() {
if (haslo.value !== haslo2.value) {
haslo2.setCustomValidity('Hasła muszą być identyczne.'); // pole staje się niepoprawne
} else {
haslo2.setCustomValidity(''); // pusty napis = brak błędu – KONIECZNE
}
}
// sprawdzaj na bieżąco, przy każdej zmianie któregokolwiek pola
haslo.addEventListener('input', sprawdzZgodnosc);
haslo2.addEventListener('input', sprawdzZgodnosc);
document.getElementById('rejestracja').addEventListener('submit', (e) => {
e.preventDefault();
document.getElementById('komunikat').textContent = 'Konto założone (walidacja HTML5 przeszła).';
});Co zwróci pole.checkValidity() dla <input type='number' min='1' max='5' value='9'>?
// podgląd, dlaczego pole jest niepoprawne – przydatne przy pisaniu własnych komunikatów
const wiek = document.getElementById('wiek'); // <input type="number" min="13" max="19" required>
wiek.addEventListener('input', () => {
const v = wiek.validity;
if (v.valueMissing) console.log('puste pole');
else if (v.rangeUnderflow) console.log('za mało – minimum ' + wiek.min);
else if (v.rangeOverflow) console.log('za dużo – maksimum ' + wiek.max);
else if (v.badInput) console.log('to nie jest liczba');
else console.log('OK');
});Walidacja w JavaScripcie
Gdy arkusz mówi „skrypt sprawdza, czy pola są wypełnione, i wyświetla komunikat w akapicie”, atrybuty HTML5 nie wystarczą – komunikat ma być twój, w twoim miejscu, często w określonym kolorze. Schemat jest zawsze ten sam:
- pobierz wartości kontrolek,
- sprawdź warunki po kolei; przy pierwszym błędzie wpisz komunikat do akapitu, ustaw kolor i zakończ (
return), - jeśli wszystko jest dobrze – policz lub wypisz wynik w innym kolorze.
Warunki, które powtarzają się w zadaniach:
| Sprawdzenie | Kod | Uwaga |
|---|---|---|
| pole puste | imie.trim() === '' | trim() usuwa spacje – same spacje to też „puste” |
| to nie liczba | isNaN(parseFloat(wartosc)) | nie isNaN(wartosc): isNaN('') daje false, bo pusty napis zamienia się na 0 |
| poza zakresem | wiek < 13 || wiek > 19 | pamiętaj o || w zakresie „poza” i && w zakresie „wewnątrz” |
| za krótkie | haslo.length < 6 | length to właściwość, bez nawiasów |
| hasła różne | haslo !== powtorz | porównuj napisy po trim() tylko wtedy, gdy to ma sens (haseł nie przycinaj) |
| checkbox niezaznaczony | !zgoda.checked | checked to true/false, nie napis |
| nic nie wybrano w select | miasto.value === '' | pierwsza opcja z value="" |
| zły format | /^[0-9]{2}-[0-9]{3}$/.test(kod) | wyrażenie regularne, tu z ^ i $ |
<form id="zgloszenie">
<p><label for="imie">Imię:</label> <input id="imie"></p>
<p><label for="wiek">Wiek:</label> <input id="wiek" type="number"></p>
<p><label for="haslo">Hasło:</label> <input id="haslo" type="password">
<label for="haslo2">Powtórz:</label> <input id="haslo2" type="password"></p>
<p><label><input id="zgoda" type="checkbox"> Zapoznałem się z regulaminem</label></p>
<button type="submit">Wyślij</button>
</form>
<p id="komunikat" aria-live="polite"></p>input[type=number] { width: 70px; }const komunikat = document.getElementById('komunikat');
function pokazBlad(tekst) {
komunikat.textContent = tekst;
komunikat.style.color = 'red';
}
function sprawdzFormularz(zdarzenie) {
zdarzenie.preventDefault(); // zawsze zatrzymaj wysłanie – decyzję podejmujemy niżej
const imie = document.getElementById('imie').value.trim();
const wiek = parseInt(document.getElementById('wiek').value);
const haslo = document.getElementById('haslo').value;
const haslo2 = document.getElementById('haslo2').value;
const zgoda = document.getElementById('zgoda').checked;
if (imie === '') {
pokazBlad('Wpisz imię.');
return; // pierwszy błąd kończy sprawdzanie
}
if (isNaN(wiek) || wiek < 13 || wiek > 19) {
pokazBlad('Wiek musi być liczbą od 13 do 19.');
return;
}
if (haslo.length < 6) {
pokazBlad('Hasło musi mieć co najmniej 6 znaków.');
return;
}
if (haslo !== haslo2) {
pokazBlad('Podane hasła różnią się.');
return;
}
if (!zgoda) {
pokazBlad('Musisz zapoznać się z regulaminem.');
return;
}
// wszystko poprawne
komunikat.textContent = 'Witaj ' + imie.toUpperCase() + ', zgłoszenie przyjęte.';
komunikat.style.color = 'Navy';
}
document.getElementById('zgloszenie').addEventListener('submit', sprawdzFormularz);Dlaczego preventDefault() jest na samym początku? Bo w placu zabaw (i na egzaminie) nie ma serwera – wysłanie formularza przeładowałoby stronę i wymazało komunikat. Gdybyś chciał wysłać dane po udanej walidacji, wywołaj preventDefault() tylko w gałęziach z błędem.
Użytkownik zostawia pole wieku puste. Który warunek wykryje to poprawnie, jeśli wiek = document.getElementById('wiek').value?
Walidacja na bieżąco
Zamiast czekać na „Wyślij”, możesz sprawdzać pole przy każdej zmianie (input) albo przy opuszczeniu (blur/change). Każde pole dostaje własny akapit komunikatu.
<p><label for="nick">Nick (3–10 znaków):</label>
<input id="nick" aria-describedby="nick-blad">
<span id="nick-blad" class="blad" aria-live="polite"></span></p>
<p><label for="punkty">Punkty (0–100):</label>
<input id="punkty" type="number" aria-describedby="punkty-blad">
<span id="punkty-blad" class="blad" aria-live="polite"></span></p>.blad { color: Crimson; font-size: 14px; margin-left: 8px; }
input[aria-invalid="true"] { border: 2px solid Crimson; }
input[type=number] { width: 70px; }function ustawBlad(pole, tekst) {
document.getElementById(pole.id + '-blad').textContent = tekst;
pole.setAttribute('aria-invalid', tekst !== ''); // true gdy jest błąd, false gdy pusto
}
document.getElementById('nick').addEventListener('blur', (e) => {
const nick = e.target.value.trim();
if (nick.length < 3 || nick.length > 10) ustawBlad(e.target, 'Nick musi mieć od 3 do 10 znaków.');
else ustawBlad(e.target, '');
});
document.getElementById('punkty').addEventListener('input', (e) => {
const punkty = parseFloat(e.target.value);
if (isNaN(punkty) || punkty < 0 || punkty > 100) ustawBlad(e.target, 'Podaj liczbę od 0 do 100.');
else ustawBlad(e.target, '');
});Dialekt egzaminacyjny: onsubmit i return false
W materiałach CKE i starszych podręcznikach zdarzenie wysłania obsługuje atrybut onsubmit, a wysłanie blokuje zwrócenie false z funkcji. Musisz ten zapis czytać – w części pisemnej pojawia się pytanie „które zdarzenie może zablokować wysłanie formularza” (odpowiedź: onsubmit).
<form onsubmit="return sprawdz()">
<input id="email" type="text">
<input type="submit" value="Wyślij">
</form>
<p id="info"></p>
<script>
function sprawdz() {
var email = document.getElementById('email').value;
if (email == '') {
document.getElementById('info').innerHTML = 'Podaj e-mail';
return false; // false = formularz NIE zostanie wysłany
}
return true; // true = wyślij
}
</script>Słowo return w atrybucie jest konieczne: onsubmit="sprawdz()" wywoła funkcję, ale zignoruje jej wynik i formularz wyśle się mimo błędu. W nowym kodzie pisz addEventListener('submit', …) z preventDefault() – robi dokładnie to samo, a nie da się zapomnieć return.
Formularz ma onsubmit='sprawdz()', a funkcja sprawdz zwraca false przy pustym polu. Co się stanie po kliknięciu „Wyślij” z pustym polem?
Dostępność komunikatów
Kryteria INF.03.3.6.6–8 wymagają stron „uwzględniających potrzeby użytkowników z różnymi niepełnosprawnościami”. Dla formularzy sprowadza się to do kilku nawyków, które poprawiają też zwykłą wygodę:
<label for="id">dla każdego pola – kliknięcie etykiety ustawia fokus, a czytnik ekranu odczytuje nazwę pola. Samplaceholdernie jest etykietą (znika po wpisaniu).aria-describedby="id-komunikatu"na polu – czytnik odczyta treść błędu razem z polem.aria-live="polite"na akapicie komunikatu – zmiana tekstu zostanie odczytana bez przenoszenia fokusu.role="alert"działa podobnie, ale przerywa czytanie (dla pilnych błędów).aria-invalid="true"na błędnym polu (i usunięcie po poprawie).- Nie tylko kolor: czerwony tekst to za mało dla osób z zaburzeniem widzenia barw – zawsze pełne zdanie, ewentualnie ikona lub prefiks „Błąd:”.
- Fokus na pierwsze błędne pole po nieudanym wysłaniu:
pole.focus()– użytkownik klawiatury od razu wie, co poprawić. - Atrybuty HTML5 są dostępne „za darmo”: dymek przeglądarki jest odczytywany przez czytniki ekranu.
// po nieudanej walidacji ustaw fokus na pierwszym niepoprawnym polu
const pierwszyBlad = formularz.querySelector(':invalid, [aria-invalid="true"]');
if (pierwszyBlad) pierwszyBlad.focus();Na egzaminie
Ściąga
| Składnia | Co robi | Przykład |
|---|---|---|
required | pole obowiązkowe | <input required> |
min="1" max="10" | zakres liczby/daty | type="number" min="1" max="10" |
minlength / maxlength | długość napisu | minlength="3" |
pattern="…" | wyrażenie regularne na całą wartość | pattern="[0-9]{2}-[0-9]{3}" |
type="email" / "url" / "number" | sprawdzenie formatu | type="email" required |
novalidate | wyłącza dymki HTML5 w formularzu | <form novalidate> |
:invalid, :valid | stan pola (od załadowania strony) | input:invalid { … } |
:user-invalid | stan pola po interakcji użytkownika | input:user-invalid { border-color: red } |
pole.checkValidity() | true/false bez dymka | if (!pole.checkValidity()) |
pole.reportValidity() | jak wyżej, z dymkiem | form.reportValidity() |
pole.validity.valueMissing | dlaczego niepoprawne | rangeOverflow, patternMismatch, badInput |
pole.setCustomValidity('tekst') | własny błąd; '' kasuje | zgodność haseł |
form.addEventListener('submit', f) | reakcja na wysłanie | f(e) { e.preventDefault(); … } |
onsubmit="return f()" | dialekt egzaminacyjny; false blokuje | return false; |
wartosc.trim() === '' | pole puste (także same spacje) | if (imie.trim() === '') |
isNaN(parseFloat(w)) | to nie liczba | isNaN('') to false – nie używaj samego isNaN |
pole.checked | checkbox/radio zaznaczony | if (!zgoda.checked) |
p.style.color = 'red' | kolor komunikatu | 'Navy', '#c00' |
aria-describedby, aria-live, aria-invalid | dostępność komunikatów | <p id="blad" aria-live="polite"> |
Zadania
Formularz zamówienia samymi atrybutami
Zbuduj formularz zamówienia pizzy bez ani jednej linii walidacji w JS. Wymagania: imię (obowiązkowe, 2–30 znaków), telefon (obowiązkowy, dokładnie 9 cyfr – użyj pattern), liczba sztuk (1–10), rozmiar jako select z pierwszą opcją „– wybierz –” o pustej wartości (obowiązkowy), checkbox zgody (obowiązkowy). Dodaj CSS: błędne pole po interakcji ma czerwoną ramkę. Skrypt ma tylko zatrzymać wysłanie i wypisać „Zamówienie przyjęte” po udanej walidacji.
Pokaż rozwiązanie
<form id="pizza">
<p><label for="imie">Imię:</label> <input id="imie" required minlength="2" maxlength="30"></p>
<p><label for="tel">Telefon (9 cyfr):</label> <input id="tel" pattern="[0-9]{9}" required inputmode="numeric"></p>
<p><label for="ile">Liczba sztuk:</label> <input id="ile" type="number" min="1" max="10" value="1" required></p>
<p><label for="rozmiar">Rozmiar:</label>
<select id="rozmiar" required>
<option value="">– wybierz –</option>
<option value="s">mała</option>
<option value="m">średnia</option>
<option value="l">duża</option>
</select></p>
<p><label><input type="checkbox" required> Zgadzam się na kontakt telefoniczny</label></p>
<button type="submit">Zamów</button>
</form>
<p id="wynik"></p>input:user-invalid, select:user-invalid { border: 2px solid Crimson; }
input[type=number] { width: 60px; }document.getElementById('pizza').addEventListener('submit', (e) => {
e.preventDefault(); // walidacja HTML5 już przeszła, bo submit w ogóle zaszedł
document.getElementById('wynik').textContent = 'Zamówienie przyjęte';
});Pusta pierwsza opcja z value="" sprawia, że required na select działa: wybór „– wybierz –” liczy się jako brak wartości.
Rejestracja z własnymi komunikatami
Formularz rejestracji: login, e-mail, hasło, powtórzenie hasła, wiek. Bez atrybutów HTML5 (albo z novalidate). Po kliknięciu „Zarejestruj” skrypt sprawdza po kolei i wypisuje w akapicie #komunikat pierwszy napotkany błąd na czerwono:
- „Login musi mieć od 4 do 12 znaków.”
- „E-mail musi zawierać znak @ i kropkę po nim.” (sprawdź przez
indexOflubincludes) - „Hasło musi mieć co najmniej 8 znaków.”
- „Hasła nie są identyczne.”
- „Wiek musi być liczbą co najmniej 16.”
Gdy wszystko jest poprawne: „Konto LOGIN utworzone.” w kolorze green, gdzie LOGIN to login wielkimi literami. Dodatkowo ustaw fokus na błędnym polu.
Pokaż rozwiązanie
<form id="rejestracja" novalidate>
<p><label for="login">Login:</label> <input id="login"></p>
<p><label for="email">E-mail:</label> <input id="email"></p>
<p><label for="haslo">Hasło:</label> <input id="haslo" type="password"></p>
<p><label for="haslo2">Powtórz hasło:</label> <input id="haslo2" type="password"></p>
<p><label for="wiek">Wiek:</label> <input id="wiek" type="number"></p>
<button type="submit">Zarejestruj</button>
</form>
<p id="komunikat" aria-live="polite"></p>input[type=number] { width: 60px; }const komunikat = document.getElementById('komunikat');
function blad(pole, tekst) {
komunikat.textContent = tekst;
komunikat.style.color = 'red';
pole.focus(); // użytkownik od razu trafia do błędnego pola
}
document.getElementById('rejestracja').addEventListener('submit', (e) => {
e.preventDefault();
const loginPole = document.getElementById('login');
const emailPole = document.getElementById('email');
const hasloPole = document.getElementById('haslo');
const haslo2Pole = document.getElementById('haslo2');
const wiekPole = document.getElementById('wiek');
const login = loginPole.value.trim();
const email = emailPole.value.trim();
const wiek = parseInt(wiekPole.value);
if (login.length < 4 || login.length > 12) return blad(loginPole, 'Login musi mieć od 4 do 12 znaków.');
const malpa = email.indexOf('@');
const kropkaPoMalpie = email.indexOf('.', malpa) > malpa; // kropka ZA znakiem @
if (malpa < 1 || !kropkaPoMalpie) return blad(emailPole, 'E-mail musi zawierać znak @ i kropkę po nim.');
if (hasloPole.value.length < 8) return blad(hasloPole, 'Hasło musi mieć co najmniej 8 znaków.');
if (hasloPole.value !== haslo2Pole.value) return blad(haslo2Pole, 'Hasła nie są identyczne.');
if (isNaN(wiek) || wiek < 16) return blad(wiekPole, 'Wiek musi być liczbą co najmniej 16.');
komunikat.textContent = 'Konto ' + login.toUpperCase() + ' utworzone.';
komunikat.style.color = 'green';
});return blad(...) to skrót: funkcja blad nic nie zwraca (undefined), ale return kończy obsługę zdarzenia po pierwszym błędzie.
Walidacja na żywo z podsumowaniem
Trzy pola: nazwa produktu (3–40 znaków), cena (liczba większa od 0, maks. 2 miejsca po przecinku), ilość (liczba całkowita 1–999). Każde pole ma własny span z komunikatem (aria-live="polite", powiązany przez aria-describedby) aktualizowany przy zdarzeniu input. Przycisk „Dodaj” jest wyłączony (disabled), dopóki wszystkie pola nie są poprawne. Po kliknięciu „Dodaj” pod formularzem pojawia się akapit „Dodano: NAZWA, ILOŚĆ szt. po CENA zł = WARTOŚĆ zł” (wartość z dwoma miejscami po przecinku). Podpowiedź: trzymaj stan poprawności każdego pola w obiekcie poprawne = { nazwa: false, cena: false, ilosc: false }.
Pokaż rozwiązanie
<form id="produkt" novalidate>
<p><label for="nazwa">Nazwa:</label> <input id="nazwa" aria-describedby="nazwa-info">
<span id="nazwa-info" class="info" aria-live="polite"></span></p>
<p><label for="cena">Cena:</label> <input id="cena" aria-describedby="cena-info">
<span id="cena-info" class="info" aria-live="polite"></span></p>
<p><label for="ilosc">Ilość:</label> <input id="ilosc" aria-describedby="ilosc-info">
<span id="ilosc-info" class="info" aria-live="polite"></span></p>
<button type="submit" id="dodaj" disabled>Dodaj</button>
</form>
<p id="wynik"></p>.info { font-size: 13px; margin-left: 8px; color: Crimson; }
input[aria-invalid="true"] { border: 2px solid Crimson; }
input[aria-invalid="false"] { border: 2px solid SeaGreen; }const poprawne = { nazwa: false, cena: false, ilosc: false }; // stan każdego pola
const dodaj = document.getElementById('dodaj');
// reguły: funkcja zwraca pusty napis (OK) albo tekst błędu
const reguly = {
nazwa: (w) => (w.trim().length >= 3 && w.trim().length <= 40 ? '' : 'Od 3 do 40 znaków.'),
cena: (w) => (/^[0-9]+([.,][0-9]{1,2})?$/.test(w.trim()) && parseFloat(w.replace(',', '.')) > 0
? '' : 'Liczba większa od 0, maks. 2 miejsca po przecinku.'),
ilosc: (w) => (/^[0-9]+$/.test(w.trim()) && parseInt(w) >= 1 && parseInt(w) <= 999
? '' : 'Liczba całkowita od 1 do 999.'),
};
function sprawdzPole(id) {
const pole = document.getElementById(id);
const blad = reguly[id](pole.value);
document.getElementById(id + '-info').textContent = blad;
pole.setAttribute('aria-invalid', blad !== '');
poprawne[id] = blad === '';
dodaj.disabled = !(poprawne.nazwa && poprawne.cena && poprawne.ilosc); // wszystkie trzy
}
for (const id of ['nazwa', 'cena', 'ilosc']) {
document.getElementById(id).addEventListener('input', () => sprawdzPole(id));
}
document.getElementById('produkt').addEventListener('submit', (e) => {
e.preventDefault();
const nazwa = document.getElementById('nazwa').value.trim();
const cena = parseFloat(document.getElementById('cena').value.replace(',', '.'));
const ilosc = parseInt(document.getElementById('ilosc').value);
const wartosc = (cena * ilosc).toFixed(2);
document.getElementById('wynik').textContent =
'Dodano: ' + nazwa + ', ' + ilosc + ' szt. po ' + cena.toFixed(2) + ' zł = ' + wartosc + ' zł';
});Formularz zgłoszenia serwisowego
Strona serwis.html zawiera tabelę z kontrolkami (HTML gotowy):
<table>
<tr><td>Imię:</td><td><input type="text" id="imie"></td></tr>
<tr><td>Nazwisko:</td><td><input type="text" id="nazwisko"></td></tr>
<tr><td>Opis usterki:</td><td><textarea id="opis"></textarea></td></tr>
<tr><td colspan="2"><input type="checkbox" id="regulamin"> Zapoznałem się z regulaminem serwisu</td></tr>
<tr><td colspan="2"><button type="button" onclick="wyslijZgloszenie()">Prześlij</button></td></tr>
</table>
<hr>
<p id="komunikat"></p>Skrypt, wykonywany po stronie klienta po kliknięciu „Prześlij”:
- Jeśli checkbox nie jest zaznaczony – w akapicie pod linią poziomą pojawia się tekst „Musisz zaakceptować regulamin serwisu” w kolorze czerwonym.
- Jeśli jest zaznaczony – w tym samym akapicie, w kolorze Navy: w pierwszej linii imię i nazwisko WIELKIMI LITERAMI, w drugiej linii „Opis usterki:”, w trzeciej treść opisu (do łamania linii użyj znacznika
<br>). - Stosuj znaczące nazwy zmiennych i funkcji.
Kryteria w stylu „Zasad oceniania” (0/1):
- 5.1 Skrypt w języku JavaScript, funkcja uruchamiana przyciskiem „Prześlij”.
- 5.2 Skrypt pobiera wartość z co najmniej jednej kontrolki.
- 5.3 Przy niezaznaczonym checkboxie wyświetla „Musisz zaakceptować regulamin serwisu”.
- 5.4 Przy zaznaczonym: imię i nazwisko w pierwszej linii, niżej „Opis usterki:” i treść.
- 5.5 Komunikat wyświetlany w akapicie pod linią poziomą (nie w oknie dialogowym).
- 5.6 Kolor: czerwony dla błędu, Navy dla danych.
- 5.7 Imię i nazwisko wielkimi literami.
- 5.8 Znaczące nazwy zmiennych i funkcji (PL lub EN).
Pokaż rozwiązanie
<table>
<tr><td>Imię:</td><td><input type="text" id="imie"></td></tr>
<tr><td>Nazwisko:</td><td><input type="text" id="nazwisko"></td></tr>
<tr><td>Opis usterki:</td><td><textarea id="opis" rows="2"></textarea></td></tr>
<tr><td colspan="2"><input type="checkbox" id="regulamin"> Zapoznałem się z regulaminem serwisu</td></tr>
<tr><td colspan="2"><button type="button" onclick="wyslijZgloszenie()">Prześlij</button></td></tr>
</table>
<hr>
<p id="komunikat"></p>function wyslijZgloszenie() {
const imie = document.getElementById('imie').value;
const nazwisko = document.getElementById('nazwisko').value;
const opis = document.getElementById('opis').value;
const regulaminZaznaczony = document.getElementById('regulamin').checked;
const komunikat = document.getElementById('komunikat');
if (!regulaminZaznaczony) {
komunikat.innerHTML = 'Musisz zaakceptować regulamin serwisu';
komunikat.style.color = 'red';
} else {
const imieNazwisko = (imie + ' ' + nazwisko).toUpperCase();
komunikat.innerHTML = imieNazwisko + '<br>Opis usterki:<br>' + opis; // innerHTML, bo <br>
komunikat.style.color = 'Navy';
}
}Tu przycisk ma type="button" i nie ma <form>, więc nic nie przeładuje strony – preventDefault jest zbędny. Gdyby HTML z arkusza zawierał <form> i type="submit", trzeba by go dodać. Test: raz bez zaznaczenia, raz z zaznaczeniem i innymi danymi.
Test powtórkowy
Test powtórkowy
Format jak w części pisemnej INF.03: 4 odpowiedzi, jedna poprawna.
Które zdarzenie formularza pozwala skryptowi zablokować jego wysłanie?
Który atrybut sprawi, że pole liczbowe przyjmie tylko wartości od 1 do 5?
Co zwraca metoda checkValidity() wywołana na polu formularza?
Formularz ma onsubmit='return sprawdz()'. Co musi zwrócić funkcja sprawdz, aby formularz NIE został wysłany?
Co wypisze ten kod, gdy pole wiek jest puste?
var wiek = document.getElementById('wiek').value;
if (isNaN(wiek)) {
document.write('blad');
} else {
document.write('ok');
}Które zadanie może być wykonane wyłącznie po stronie klienta (w przeglądarce)?
Pole <input type='text' id='kod' pattern='[0-9]{2}-[0-9]{3}' required> jest w formularzu z przyciskiem submit. Która wartość pozwoli wysłać formularz?
Skrypt ma wyświetlić komunikat błędu „w akapicie pod przyciskiem, w kolorze czerwonym”. Który fragment realizuje to poprawnie?
Po wpisaniu różnych haseł wywołano haslo2.setCustomValidity('Hasła różne'). Użytkownik poprawił drugie hasło, ale formularz nadal się nie wysyła. Dlaczego?
Która pseudoklasa CSS oznacza pole niepoprawne dopiero po tym, jak użytkownik z nim pracował (a nie od razu po załadowaniu strony)?