Przejdź do treści
INF.03 · Dział II

Programowanie po stronie klienta (JavaScript)

Zmienne, typy danych, pętle, instrukcje warunkowe, manipulacja modelem DOM, obsługa zdarzeń (events), walidacja formularzy po stronie klienta, ciasteczka i pamięć lokalna (Local Storage).

Wstęp: JavaScript to dział, w którym pisze się kod, a nie opisuje

W części praktycznej INF.03 JavaScript pojawia się prawie zawsze w tej samej roli: masz gotowy formularz w HTML i masz go ożywić — pobrać elementy przez getElementById, podpiąć zdarzenie, sprawdzić dane, wyświetlić komunikat. Część pisemna pyta o rzeczy, które trzeba znać z pamięci: różnicę między == a ===, zasięg let kontra var, wynik konkretnej pętli albo metodę tablicy. Ten artykuł idzie dokładnie tą ścieżką: od wstawienia skryptu do strony aż do zapisu danych w Local Storage.

1

Osadzanie skryptu w stronie

JavaScript wykonuje się w przeglądarce użytkownika — to właśnie znaczy „po stronie klienta". Serwer wysyła plik, przeglądarka go uruchamia. Skrypt trafia do dokumentu na jeden z dwóch sposobów: wpisany wprost w znacznik <script> albo dołączony z zewnętrznego pliku .js.

<!-- 1. Skrypt wewnetrzny (inline) -->
<script>
document.getElementById("tytul").textContent = "Witaj!";
</script>

<!-- 2. Skrypt zewnetrzny - atrybut src, znacznik MUSI byc zamkniety -->
<script src="js/skrypt.js"></script>

<!-- 3. Odroczenie wykonania -->
<script src="js/skrypt.js" defer></script>
<script src="js/analityka.js" async></script>
Bez atrybutu

Przeglądarka wstrzymuje budowanie strony, pobiera skrypt i od razu go wykonuje. Jeśli taki skrypt stoi w sekcji <head> i szuka elementu z <body>, dostanie null — bo tego elementu jeszcze nie ma.

defer

Skrypt pobiera się równolegle z budową strony, ale wykonuje dopiero po zbudowaniu całego DOM. Kolejność wielu skryptów z defer jest zachowana. To domyślny wybór do skryptów operujących na stronie.

async

Skrypt pobiera się równolegle i wykonuje natychmiast po pobraniu, przerywając budowę strony. Kolejność nie jest gwarantowana. Nadaje się do niezależnych skryptów (statystyki, reklamy), nie do kodu operującego na DOM.

Zasada praktyczna na egzamin:

Skrypt operujący na elementach strony umieszczasz albo na końcu sekcji body, tuż przed </body>, albo w <head> z atrybutem defer. Oba warianty gwarantują, że drzewo DOM jest gotowe. Atrybuty defer i async działają wyłącznie dla skryptów zewnętrznych — na skrypcie wpisanym wprost w stronę są ignorowane.

Zanim napiszesz pierwszą linię
  • Instrukcje kończy się średnikiem. Nie jest to bezwzględnie wymagane, ale na egzaminie stawiaj je zawsze — czytelność jest oceniana.
  • Komentarz jednoliniowy: dwa ukośniki. Wieloliniowy: ukośnik-gwiazdka ... gwiazdka-ukośnik.
  • JavaScript rozróżnia wielkość liter. getElementById zapisane jako getElementByID to błąd.
  • Do szybkiego podglądu wartości służy console.log() — wynik widać w konsoli przeglądarki (klawisz F12).
  • alert(), prompt() i confirm() to okienka systemowe. prompt zawsze zwraca tekst, nawet gdy wpiszesz liczbę.

2

Zmienne, typy danych i operatory

Zmienną deklaruje się słowem var, let albo const. Różnice między nimi to jedno z najpewniejszych pytań testowych z tego działu.

var kontra let kontra const
Cechavarletconst
Zasięgfunkcyjny (cała funkcja)blokowy (para nawiasów klamrowych)blokowy
Ponowna deklaracja w tym samym zasięgutak, bez błędunie — SyntaxErrornie — SyntaxError
Zmiana wartościtaktaknie — TypeError
Hoisting (wyniesienie deklaracji)tak, wartość przed przypisaniem to undefineddeklaracja wyniesiona, ale użycie przed nią to ReferenceErrortak samo jak let
Wymagana wartość początkowanienietak, przy deklaracji

Praktyka: domyślnie piszesz const, a gdy wartość ma się zmieniać — let. var zostawiasz starym kodom, ale musisz je rozpoznawać na egzaminie.

// Zasieg blokowy kontra funkcyjny
if (true) {
var a = 10;
let b = 20;
}
console.log(a);   // 10  - var "wycieka" poza blok
console.log(b);   // ReferenceError: b is not defined

// const chroni PRZYPISANIE, nie zawartosc obiektu
const lista = [1, 2, 3];
lista.push(4);          // OK - tablica sie zmienia
console.log(lista);     // [1, 2, 3, 4]
lista = [9];            // TypeError: Assignment to constant variable
Typy proste
  • number — jeden typ na liczby całkowite i zmiennoprzecinkowe. Separator dziesiętny to kropka.
  • string — tekst w apostrofach lub cudzysłowach.
  • booleantrue albo false.
  • undefined — zmienna zadeklarowana, ale bez wartości.
  • null — celowy brak wartości, ustawiany przez programistę.
  • NaNNot a Number, wynik nieudanej operacji liczbowej. Typem jest… number.
Konwersje
  • Number("12") → 12, Number("abc") → NaN
  • parseInt("12.9") → 12, ucina część ułamkową
  • parseFloat("12.9zł") → 12.9, czyta do pierwszego nieliczbowego znaku
  • String(12) lub (12).toString() → "12"
  • isNaN(x) — sprawdza, czy wartość nie da się przeliczyć na liczbę
  • typeof x — zwraca nazwę typu jako tekst
Pułapka numer jeden: plus na tekstach

Wartość pobrana z pola formularza przez .value jest zawsze tekstem, nawet dla pola typu number. Jeśli jej nie przekonwertujesz, plus skleja zamiast dodawać:

"2" + 3     // "23"   - plus sklada teksty (konkatenacja)
"2" - 3     // -1     - minus wymusza liczby
"6" * "2"   // 12     - mnozenie tez wymusza liczby
Number("2") + 3   // 5   - poprawnie

const cena = Number(document.getElementById("cena").value);
const ile  = parseInt(document.getElementById("ilosc").value, 10);
console.log(cena * ile);
Operator == kontra ===

== porównuje po wcześniejszej konwersji typów (porównanie luźne). === porównuje wartość i typ, bez żadnej konwersji (porównanie ścisłe). Negacje to odpowiednio != i !==.

PorównanieWynik ==Wynik ===Dlaczego
5 i "5"truefalsetekst zamieniony na liczbę
0 i falsetruefalsefalse przeliczane na 0
"" i 0truefalsepusty tekst to liczba 0
null i undefinedtruefalsewyjątek zapisany w standardzie
"10" i 10truefalsekonwersja tekstu na liczbę
NaN i NaNfalsefalseNaN nie równa się niczemu, nawet sobie

Na egzaminie i w praktyce używaj ===. Jedyne sensowne zastosowanie == to sprawdzenie „null albo undefined" jednym warunkiem.

Pozostałe operatory
  • Arytmetyczne: plus, minus, gwiazdka, ukośnik, procent (reszta z dzielenia), dwie gwiazdki (potęgowanie). 17 % 5 daje 2 — to standardowy sposób sprawdzenia parzystości.
  • Inkrementacja: i++ zwraca starą wartość i zwiększa, ++i najpierw zwiększa, potem zwraca.
  • Przypisania złożone: x += 5 to skrót od x = x + 5.
  • Porównania: mniejszość, większość oraz warianty z równością — zapisywane jako <, >, <= i >=.
  • Logiczne: podwójny ampersand (i — wszystkie warunki prawdziwe), podwójna kreska pionowa (lub — wystarczy jeden), wykrzyknik (negacja).
  • Warunkowy (trójargumentowy): wynik = wiek >= 18 ? "dorosly" : "nieletni";

3

Warunki, switch i pętle

Typowe zadanie testowe pokazuje kilka linii kodu i pyta, co zostanie wypisane. Trzeba umieć „wykonać kod w głowie": policzyć obroty pętli i sprawdzić, czy warunek jest spełniony.

// Instrukcja warunkowa
const ocena = 4;

if (ocena >= 5) {
console.log("bardzo dobry");
} else if (ocena >= 4) {
console.log("dobry");
} else if (ocena >= 3) {
console.log("dostateczny");
} else {
console.log("niedostateczny");
}

// switch - porownuje SCISLE (jak ===)
const dzien = 3;
switch (dzien) {
case 1:
case 2:
case 3:
case 4:
case 5:
  console.log("dzien roboczy");
  break;
case 6:
case 7:
  console.log("weekend");
  break;
default:
  console.log("bledny numer dnia");
}

Brak break = przelot (fall-through).

Jeśli w bloku case zapomnisz instrukcji break, wykonanie „przeleci" do kolejnych przypadków, aż napotka break albo koniec bloku. Czasem wykorzystuje się to celowo, jak w przykładzie wyżej, gdzie pięć przypadków dzieli jedną instrukcję. Częściej jest to jednak błąd i typowy haczyk w pytaniu „co wypisze ten kod".

// for - gdy znasz liczbe powtorzen
let suma = 0;
for (let i = 1; i <= 10; i++) {
suma += i;
}
console.log(suma);        // 55

// while - warunek sprawdzany PRZED wykonaniem
let n = 5;
while (n > 0) {
console.log(n);
n--;
}

// do...while - blok wykona sie CO NAJMNIEJ RAZ
let x = 100;
do {
console.log("wykonalo sie mimo falszywego warunku");
} while (x < 10);

// for...of - po WARTOSCIACH tablicy
const oceny = [3, 4, 5];
for (const o of oceny) {
console.log(o);         // 3, 4, 5
}

// for...in - po KLUCZACH / INDEKSACH
const uczen = { imie: "Ala", klasa: "3A" };
for (const klucz in uczen) {
console.log(klucz + " = " + uczen[klucz]);   // imie = Ala, klasa = 3A
}

// break przerywa cala petle, continue pomija jeden obieg
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) continue;   // pomin parzyste
if (i > 7) break;            // zakoncz przy 9
console.log(i);              // 1, 3, 5, 7
}
Którą pętlę wybrać
PętlaKiedyCo daje w każdym obiegu
forliczba powtórzeń znana z górylicznik, którym sam sterujesz
whilepowtarzasz, dopóki warunek prawdziwynic — sam pilnujesz zmiennej
do...whileblok ma się wykonać przynajmniej raznic; warunek sprawdzany na końcu
for...ofprzechodzisz tablicę lub tekstwartość elementu
for...inprzechodzisz właściwości obiektunazwę klucza (dla tablicy — indeks jako tekst)

4

Funkcje: deklaracja, wyrażenie, strzałkowa

Funkcja to nazwany fragment kodu, który przyjmuje parametry i może zwrócić wartość instrukcją return. Funkcja bez return zwraca undefined.

// 1. Deklaracja funkcji - podlega hoistingowi,
//    mozna ja wywolac PRZED miejscem definicji
console.log(pole(3, 4));    // 12 - dziala

function pole(a, b) {
return a * b;
}

// 2. Wyrazenie funkcyjne - przypisane do zmiennej,
//    wywolanie przed definicja konczy sie bledem
const obwod = function (a, b) {
return 2 * (a + b);
};

// 3. Funkcja strzalkowa (arrow function)
const podwoj = (x) => x * 2;          // krotki zapis: return domyslny
const suma = (a, b) => {
return a + b;
};

// Parametr domyslny - uzywany, gdy argument nie zostal podany
function powitaj(imie = "gosciu") {
return "Witaj, " + imie + "!";
}
console.log(powitaj());        // Witaj, gosciu!
console.log(powitaj("Ala"));   // Witaj, Ala!

// return NATYCHMIAST konczy funkcje
function sprawdzWiek(wiek) {
if (wiek < 0) {
  return "bledna wartosc";
}
return wiek >= 18 ? "pelnoletni" : "nieletni";
}
Co trzeba wiedzieć
  • Parametr to nazwa w nawiasach przy definicji, argument to konkretna wartość podana przy wywołaniu.
  • Brakujący argument to undefined, chyba że ustawiono wartość domyślną. Nadmiarowe argumenty są ignorowane.
  • Zmienne zadeklarowane wewnątrz funkcji są lokalne — z zewnątrz niewidoczne.
  • Wywołanie to nazwa z nawiasami. pole bez nawiasów to sama funkcja, nie jej wynik.
Funkcja strzałkowa — skróty
  • Jeden parametr — nawiasy okrągłe można pominąć.
  • Ciało to jedno wyrażenie — można pominąć klamry i słowo return.
  • Zero parametrów — puste nawiasy są obowiązkowe.
  • Funkcje strzałkowe nie mają własnego this i nie podlegają hoistingowi. Najczęściej używa się ich jako krótkich funkcji przekazywanych do map, filter i obsługi zdarzeń.

5

Tablice, obiekty i praca z tekstem

Tablica przechowuje uporządkowaną listę wartości, indeksowaną od zera. Obiekt przechowuje pary klucz–wartość. Metody tablic to wdzięczny materiał na pytania „co wypisze ten kod", bo część z nich zmienia oryginał, a część zwraca nową tablicę.

const owoce = ["jablko", "gruszka", "sliwka"];

console.log(owoce.length);   // 3
console.log(owoce[0]);       // jablko
console.log(owoce[2]);       // sliwka
console.log(owoce[3]);       // undefined - nie ma bledu!

// METODY ZMIENIAJACE ORYGINAL
owoce.push("banan");         // dodaje na KONIEC, zwraca nowa dlugosc
owoce.pop();                 // usuwa OSTATNI, zwraca usuniety element
owoce.unshift("wisnia");     // dodaje na POCZATEK
owoce.shift();               // usuwa PIERWSZY, zwraca usuniety element
owoce.splice(1, 1);          // usuwa 1 element od indeksu 1
owoce.splice(1, 0, "malina");// wstawia bez usuwania

// METODY ZWRACAJACE NOWA TABLICE (oryginal bez zmian)
const liczby = [5, 12, 8, 130, 44];
console.log(liczby.slice(1, 3));   // [12, 8] - od 1 do 3 BEZ 3
console.log(liczby.indexOf(8));    // 2
console.log(liczby.indexOf(99));   // -1 - nie znaleziono
console.log(liczby.includes(44));  // true
console.log(liczby.join(" - "));   // "5 - 12 - 8 - 130 - 44"

// PRZEBIEG PO ELEMENTACH
liczby.forEach(function (el, i) {
console.log(i + ": " + el);      // nic nie zwraca (undefined)
});

const podwojone = liczby.map((el) => el * 2);       // [10, 24, 16, 260, 88]
const duze = liczby.filter((el) => el > 10);        // [12, 130, 44]
const razem = liczby.reduce((a, b) => a + b, 0);    // 199

Zapamiętaj trzy różnice, o które pyta test:

slice nie zmienia tablicy i nie obejmuje elementu o indeksie końcowym. splice zmienia tablicę w miejscu, a jej drugi argument to liczba elementów do usunięcia, nie indeks końcowy. forEach tylko przechodzi po elementach i zwraca undefined, natomiast map zwraca nową tablicę tej samej długości, a filter — nową tablicę zawierającą wyłącznie elementy spełniające warunek.

// OBIEKT - pary klucz: wartosc
const uczen = {
imie: "Jan",
nazwisko: "Kowalski",
klasa: "3A",
oceny: [4, 5, 3],
srednia: function () {
  let s = 0;
  for (const o of this.oceny) s += o;
  return s / this.oceny.length;
}
};

console.log(uczen.imie);        // Jan   - notacja z kropka
console.log(uczen["klasa"]);    // 3A    - notacja z nawiasem kwadratowym
uczen.wiek = 17;                // dodanie nowej wlasciwosci
delete uczen.klasa;             // usuniecie wlasciwosci
console.log(uczen.srednia().toFixed(2));   // 4.00

// TABLICA OBIEKTOW - typowa struktura w zadaniach
const uczniowie = [
{ imie: "Ala", punkty: 45 },
{ imie: "Bartek", punkty: 78 },
{ imie: "Celina", punkty: 61 }
];
const zdali = uczniowie.filter((u) => u.punkty >= 50);
console.log(zdali.length);      // 2
const tekst = "  Programowanie w JavaScript  ";

console.log(tekst.length);              // 30 - razem ze spacjami
console.log(tekst.trim());              // "Programowanie w JavaScript"
console.log(tekst.trim().length);       // 26
console.log(tekst.toUpperCase());       // wszystko wielkimi literami
console.log(tekst.toLowerCase());       // wszystko malymi literami

const s = "JavaScript";
console.log(s.substring(0, 4));         // "Java"  - od 0 do 4 BEZ 4
console.log(s.charAt(0));               // "J"
console.log(s[0]);                      // "J"
console.log(s.indexOf("Script"));       // 4
console.log(s.replace("Java", "Type")); // "TypeScript" - zwraca NOWY tekst

const dane = "Kowalski;Jan;3A";
const czesci = dane.split(";");         // ["Kowalski", "Jan", "3A"]
console.log(czesci[1]);                 // Jan

// Sprawdzenie poprawnosci adresu e-mail bez wyrazen regularnych
const mail = "ala@example.com";
const ok = mail.includes("@") && mail.indexOf(".") > mail.indexOf("@");
console.log(ok);                        // true

6

Model DOM i modyfikacja strony

DOM (Document Object Model) to drzewiasta reprezentacja dokumentu HTML w pamięci przeglądarki. Każdy znacznik jest węzłem, do którego skrypt ma dostęp przez obiekt document. Zmiana węzła jest natychmiast widoczna na ekranie — bez przeładowania strony.

Metody wyszukiwania elementów
MetodaSzuka poZwraca
getElementById("x")identyfikatorze (bez znaku hash)jeden element albo null
getElementsByClassName("k")nazwie klasy (bez kropki)kolekcję żywą (HTMLCollection)
getElementsByTagName("li")nazwie znacznikakolekcję żywą
querySelector(".k")selektorze CSSPIERWSZY pasujący element albo null
querySelectorAll(".k")selektorze CSSNodeList wszystkich pasujących (statyczna)

Kluczowa różnica zapisu: w getElementById podajesz samą nazwę identyfikatora, a w querySelector pełny selektor CSS, czyli z krzyżykiem dla id i kropką dla klasy.

// WYSZUKIWANIE
const naglowek = document.getElementById("tytul");
const pierwszy = document.querySelector("#tytul");      // to samo, selektor CSS
const menu     = document.querySelector("nav .menu");
const wszystkie = document.querySelectorAll("li.produkt");
console.log(wszystkie.length);

// ZMIANA TRESCI
naglowek.textContent = "Nowy tytul";                // wstawia CZYSTY TEKST
naglowek.innerHTML   = "Nowy <strong>tytul</strong>"; // interpretuje HTML

// ZMIANA ATRYBUTOW
const obrazek = document.querySelector("img");
obrazek.src = "grafika/logo.png";
obrazek.setAttribute("alt", "Logo firmy");
console.log(obrazek.getAttribute("alt"));

// POLA FORMULARZA
const pole = document.getElementById("login");
console.log(pole.value);          // tresc wpisana przez uzytkownika
pole.value = "";                  // czyszczenie pola
const zgoda = document.getElementById("regulamin");
console.log(zgoda.checked);       // true / false dla checkboxa

// STYLE - wlasciwosci CSS pisane camelCase
naglowek.style.color = "red";
naglowek.style.backgroundColor = "#eee";   // nie background-color!
naglowek.style.fontSize = "24px";

// KLASY CSS - czystszy sposob niz recznie ustawiane style
naglowek.classList.add("aktywny");
naglowek.classList.remove("ukryty");
naglowek.classList.toggle("zaznaczony");
console.log(naglowek.classList.contains("aktywny"));   // true
// TWORZENIE NOWEGO ELEMENTU - trzy kroki
const lista = document.getElementById("lista");

const nowy = document.createElement("li");   // 1. utworz
nowy.textContent = "Nowy produkt";           // 2. wypelnij
nowy.classList.add("produkt");
lista.appendChild(nowy);                     // 3. dolacz do DOM

// Wstawienie PRZED istniejacym elementem
const pierwszyEl = lista.firstElementChild;
lista.insertBefore(nowy, pierwszyEl);

// USUWANIE
nowy.remove();                    // element usuwa sam siebie
lista.removeChild(lista.lastElementChild);   // rodzic usuwa dziecko
lista.innerHTML = "";             // szybkie wyczyszczenie calej zawartosci

// Wypelnienie listy z tablicy - czesty motyw w zadaniach
const produkty = ["Mysz", "Klawiatura", "Monitor"];
produkty.forEach(function (nazwa) {
const li = document.createElement("li");
li.textContent = nazwa;
lista.appendChild(li);
});

textContent czy innerHTML?

textContent wstawia tekst dosłownie — znaczniki wpisane przez użytkownika zobaczy on jako zwykłe znaki. innerHTML parsuje zawartość jako kod HTML, więc pozwala dodać formatowanie, ale wstawianie tam danych od użytkownika otwiera drogę do ataku XSS. Reguła: jeśli wstawiasz sam tekst — zawsze textContent.

7

Zdarzenia i walidacja formularza

Zdarzenie to sygnał, że coś się stało: użytkownik kliknął, wpisał znak, wysłał formularz, strona się wczytała. Skrypt reaguje na zdarzenie, podpinając do elementu funkcję nasłuchującą metodą addEventListener.

Najważniejsze zdarzenia
ZdarzenieKiedy zachodziTypowy element
clickkliknięcie mysząprzycisk, odnośnik, dowolny element
submitpróba wysłania formularzaznacznik form (nie przycisk!)
inputprzy każdej zmianie treści polapole tekstowe
changepo zatwierdzeniu zmiany i utracie fokusulista rozwijana, checkbox, plik
loadpo wczytaniu strony wraz z grafikąobiekt window
mouseover / keyup / focus / blurnajechanie, puszczenie klawisza, wejście i wyjście z poladowolny element, pole formularza
// Skladnia: element.addEventListener("nazwa", funkcja);
// UWAGA: nazwa zdarzenia BEZ przedrostka "on"

const przycisk = document.getElementById("start");

przycisk.addEventListener("click", function (event) {
console.log("Kliknieto element:", event.target);
console.log("Typ zdarzenia:", event.type);
});

// To samo funkcja strzalkowa
przycisk.addEventListener("click", (e) => alert("Klik!"));

// Odnosnik, ktory ma NIE przechodzic pod adres
document.querySelector("a.pomoc").addEventListener("click", function (e) {
e.preventDefault();      // anuluje domyslne dzialanie przegladarki
console.log("Zablokowano przejscie");
});

// Kod uruchamiany po zbudowaniu drzewa DOM
document.addEventListener("DOMContentLoaded", function () {
console.log("Strona gotowa");
});
Kompletna walidacja formularza — wzorzec zadania praktycznego

To najczęstsze zadanie z tego działu. Schemat jest zawsze taki sam: pobierz formularz i pola, podepnij zdarzenie submit, zablokuj domyślne wysłanie metodą preventDefault, sprawdź kolejne pola i wypisz komunikaty. Poniższy kod jest kompletny — HTML i skrypt.

<!-- HTML -->
<form id="rejestracja" novalidate>
<label for="login">Login:</label>
<input type="text" id="login">
<span class="blad" id="bladLogin"></span>

<label for="email">E-mail:</label>
<input type="text" id="email">
<span class="blad" id="bladEmail"></span>

<label for="wiek">Wiek:</label>
<input type="text" id="wiek">
<span class="blad" id="bladWiek"></span>

<button type="submit">Zarejestruj</button>
</form>
<p id="komunikat"></p>
// JavaScript - plik dolaczony z atrybutem defer
const formularz = document.getElementById("rejestracja");
const poleLogin = document.getElementById("login");
const poleEmail = document.getElementById("email");
const poleWiek  = document.getElementById("wiek");
const komunikat = document.getElementById("komunikat");

formularz.addEventListener("submit", function (e) {
e.preventDefault();          // BEZ TEGO strona sie przeladuje

// 1. czyszczenie poprzednich komunikatow
document.querySelectorAll(".blad").forEach(function (el) {
  el.textContent = "";
});
komunikat.textContent = "";

let poprawny = true;

// 2. login - niepusty, co najmniej 3 znaki
const login = poleLogin.value.trim();
if (login === "") {
  document.getElementById("bladLogin").textContent = "Pole jest wymagane";
  poprawny = false;
} else if (login.length < 3) {
  document.getElementById("bladLogin").textContent = "Minimum 3 znaki";
  poprawny = false;
}

// 3. e-mail - musi zawierac @ oraz kropke PO malpie
const email = poleEmail.value.trim();
const malpa = email.indexOf("@");
const kropka = email.lastIndexOf(".");
if (email === "") {
  document.getElementById("bladEmail").textContent = "Podaj adres e-mail";
  poprawny = false;
} else if (malpa < 1 || kropka < malpa + 2 || kropka === email.length - 1) {
  document.getElementById("bladEmail").textContent = "Nieprawidlowy adres";
  poprawny = false;
}

// 4. wiek - liczba z zakresu 1-120
const wiek = Number(poleWiek.value);
if (poleWiek.value.trim() === "" || isNaN(wiek)) {
  document.getElementById("bladWiek").textContent = "Wpisz liczbe";
  poprawny = false;
} else if (wiek < 1 || wiek > 120) {
  document.getElementById("bladWiek").textContent = "Zakres od 1 do 120";
  poprawny = false;
}

// 5. wynik
if (poprawny) {
  komunikat.style.color = "green";
  komunikat.textContent = "Formularz wyslany poprawnie";
  formularz.submit();        // dopiero teraz realne wyslanie
} else {
  komunikat.style.color = "red";
  komunikat.textContent = "Popraw zaznaczone pola";
}
});

// Kasowanie komunikatu w trakcie pisania
poleLogin.addEventListener("input", function () {
document.getElementById("bladLogin").textContent = "";
});

Walidacja po stronie klienta nie zastępuje walidacji na serwerze.

Użytkownik może wyłączyć JavaScript albo wysłać dane z pominięciem formularza. Walidacja w przeglądarce to wyłącznie wygoda i szybka informacja zwrotna — dane zawsze trzeba sprawdzić jeszcze raz po stronie serwera, np. w PHP. To zdanie regularnie pada w pytaniach testowych.

8

Local Storage, Session Storage, ciasteczka

Przeglądarka daje trzy mechanizmy przechowywania danych po stronie klienta. Egzamin pyta głównie o różnice: czas życia, pojemność i to, czy dane wędrują do serwera przy każdym żądaniu.

Porównanie mechanizmów
CechaLocal StorageSession StorageCookie
Pojemność (typowo)ok. 5–10 MBok. 5–10 MBok. 4 KB na ciasteczko
Czas życiabezterminowo, aż skasuje je skrypt lub użytkownikdo zamknięcia karty przeglądarkido daty podanej w atrybucie expires; bez niej — do zamknięcia przeglądarki
Wysyłane na serwernienietak, przy każdym żądaniu HTTP
Zasięgwszystkie karty tej samej domenytylko bieżąca kartadomena i ścieżka podana w atrybutach
Dostęp z PHPbrakbraktak, tablica superglobalna COOKIE
Typowe zastosowaniemotyw kolorystyczny, koszyk, ustawieniadane jednorazowego kreatora, formularz w trakcie wypełnianiaidentyfikator sesji, zgoda na regulamin
// LOCAL STORAGE - cztery metody, wszystko na TEKSTACH
localStorage.setItem("motyw", "ciemny");
const motyw = localStorage.getItem("motyw");    // "ciemny"
localStorage.removeItem("motyw");               // usuwa jeden klucz
localStorage.clear();                           // czysci wszystko

// Klucza, ktorego nie ma, to null - stad typowy warunek startowy
if (localStorage.getItem("licznik") === null) {
localStorage.setItem("licznik", "0");
}

// Liczby trzeba konwertowac RECZNIE - w pamieci sa jako tekst
let licznik = Number(localStorage.getItem("licznik"));
licznik++;
localStorage.setItem("licznik", licznik);

// Obiekty i tablice zapisujesz przez JSON
const koszyk = [
{ nazwa: "Mysz", cena: 79 },
{ nazwa: "Monitor", cena: 899 }
];
localStorage.setItem("koszyk", JSON.stringify(koszyk));   // obiekt -> tekst
const wczytany = JSON.parse(localStorage.getItem("koszyk"));  // tekst -> obiekt
console.log(wczytany[1].nazwa);      // Monitor

// SESSION STORAGE - identyczne metody, inny czas zycia
sessionStorage.setItem("krok", "2");
console.log(sessionStorage.getItem("krok"));
// CIASTECZKA - jedna wlasciwosc tekstowa document.cookie
// Zapis: nazwa=wartosc, po srednikach atrybuty
document.cookie = "jezyk=pl; max-age=2592000; path=/";   // 30 dni w sekundach

// Odczyt zwraca WSZYSTKIE ciasteczka w jednym tekscie
console.log(document.cookie);    // "jezyk=pl; zgoda=tak"

// Dlatego odczyt pojedynczego wymaga rozbicia tekstu
function pobierzCookie(nazwa) {
const pary = document.cookie.split("; ");
for (const para of pary) {
  const czesci = para.split("=");
  if (czesci[0] === nazwa) return czesci[1];
}
return null;
}
console.log(pobierzCookie("jezyk"));    // pl

// Usuniecie = ustawienie daty waznosci w przeszlosci
document.cookie = "jezyk=; max-age=0; path=/";

Najkrótsza odpowiedź na pytanie „czym różni się cookie od Local Storage":

Ciasteczko jest małe (około 4 KB), ma datę wygaśnięcia i jest automatycznie dołączane do każdego żądania HTTP, więc serwer może je odczytać — na tym opierają się sesje. Local Storage jest znacznie pojemniejszy, nie wygasa i nigdy nie opuszcza przeglądarki — serwer o nim nie wie, dopóki skrypt sam mu tych danych nie wyśle. Żadnego z tych mechanizmów nie wolno używać do haseł ani danych wrażliwych, bo użytkownik i skrypty na stronie mają do nich pełny dostęp.

Najczęstsze pułapki egzaminacyjne
  • Brak preventDefault w obsłudze submit. Strona przeładowuje się, komunikat błyska na ułamek sekundy i znika. To najczęstszy powód utraty punktów w zadaniu z walidacją.
  • Zapominanie, że .value zwraca tekst. Bez Number() lub parseInt() plus skleja zamiast dodawać, a porównanie liczbowe daje zaskakujące wyniki.
  • Mylenie == z ===. Zapis "0" == false daje true, a "0" === false — false. W kodzie egzaminacyjnym stosuj wyłącznie porównanie ścisłe.
  • Skrypt w head bez defer. getElementById zwraca wtedy null i pojawia się błąd „cannot read properties of null".
  • Selektor podany w złej formie. getElementById("#login") nie zadziała — tu podaje się samo id, krzyżyk należy do querySelector.
  • Mylenie slice ze splice. slice zwraca fragment i nie rusza oryginału, splice wycina z oryginału. Drugi argument splice to liczba elementów, nie indeks.
  • Traktowanie forEach jak map. forEach zwraca undefined, więc przypisanie jego wyniku do zmiennej to gwarantowany błąd logiczny.
  • Uznanie walidacji w JavaScripcie za zabezpieczenie. To tylko wygoda dla użytkownika. Dane zawsze weryfikuje jeszcze raz serwer.
Sprawdź, ile z tego zostało w głowie

JavaScript wchodzi dopiero wtedy, gdy sam napiszesz walidację formularza bez zaglądania do notatek i bezbłędnie przewidzisz wynik kilku porównań. Rozwiąż test z tego działu i zobacz, gdzie jeszcze masz luki.