JavaScript · DOM i zdarzenia

JavaScript DOM — interaktywna strona w jednej lekcji

Licznik, lista dodawana z formularza i oznaczanie punktów. querySelector, textContent i zdarzenia na trzech zadaniach.

45 minutPoziom podstawowyPliki i rozwiązania

Materiał redakcyjny TablicaPro · liceum i technikum

lista.append(punkt);
punkt.addEventListener("click", () => {
  punkt.classList.toggle("zrobione");
});
Punkty planu: 3, zrobione: 1
Strona reaguje bez przeładowania.

Na tej lekcji uczniowie ożywią stronę planu spotkania koła. Przycisk zliczy obecne osoby, formularz doda nowy punkt planu do listy, a kliknięcie punktu oznaczy go jako zrobiony. Wszystko bez przeładowania strony i bez bibliotek — wyłącznie JavaScript działający w przeglądarce na elementach dokumentu.

Materiał jest przeznaczony na 45 minut dla uczniów, którzy znają zmienne, console.log i wypisywanie wyniku na stronie — na przykład po lekcji JavaScript od podstaw. W zadaniu 2 pojawia się prosta instrukcja if; wystarczy ją pokazać na przykładzie.

Przygotowanie przed zajęciami

Rozpakuj pakiet z sekcji materiałów. Folder start zawiera index.html z gotowym układem strony, krótki style.css i prawie pusty app.js. Uczniowie piszą kod tylko w app.js. Folder rozwiazanie zawiera działający projekt.

Stronę otwiera się dwuklikiem w index.html — serwer i Node.js nie są potrzebne. Pokaż na początku konsolę przeglądarki (F12, zakładka Console): tam pojawią się komunikaty o błędach, np. literówka w nazwie elementu.

Cele i plan lekcji

Po zajęciach uczeń potrafi:

  • wyjaśnić, czym jest DOM, i pobrać element strony przez document.querySelector;
  • zmienić tekst elementu przez textContent;
  • obsłużyć zdarzenie click i submit za pomocą addEventListener;
  • utworzyć nowy element li, dodać go do listy i przełączyć mu klasę CSS.
CzasPraca na lekcji
0–5 minStrona startowa i konsola — co dziś ożywimy?
5–15 minDOM, querySelector, textContent i pierwsze zdarzenie click
15–22 minFormularz, submit i preventDefault, tworzenie elementu
22–37 minTrzy zadania samodzielne
37–42 minTesty w parach: pusty wpis, spacje, kilka punktów
42–45 minPytania kontrolne

DOM i pierwsze zdarzenie

Przeglądarka po wczytaniu HTML tworzy z niego drzewo obiektów — DOM (Document Object Model). JavaScript nie zmienia pliku index.html; zmienia to drzewo w pamięci, a przeglądarka od razu odrysowuje stronę. Po odświeżeniu wszystko wraca do stanu z pliku.

W index.html są m.in. takie elementy:

<p id="obecni">Obecnych: 0</p>
<button id="dodaj-osobe" type="button">+1 osoba</button>
<p id="powitanie">Kliknij przycisk powitania.</p>
<button id="przywitaj" type="button">Przywitaj</button>

Skrypt jest dołączony w head z atrybutem defer, dlatego wykona się dopiero po wczytaniu całego dokumentu — elementy będą już dostępne. Wspólny przykład wpisz do app.js:

const przycisk = document.querySelector("#przywitaj");
const powitanie = document.querySelector("#powitanie");

przycisk.addEventListener("click", () => {
  powitanie.textContent = "Witamy na spotkaniu koła!";
});

document.querySelector przyjmuje selektor jak w CSS: #przywitaj oznacza element o id="przywitaj". Zwraca pierwszy pasujący element albo null, gdy go nie ma. addEventListener("click", ...) rejestruje funkcję, która wykona się przy każdym kliknięciu — nie od razu po wczytaniu strony. Zapis () => { ... } to krótka funkcja (funkcja strzałkowa).

textContent ustawia tekst elementu. Wpisany tekst jest zawsze traktowany jak zwykły tekst, nawet jeśli zawiera znaki < i >. To ważne, gdy na stronie pokazujemy to, co wpisał użytkownik.

Formularz, submit i nowy element listy

Formularz w index.html:

<form id="formularz">
  <label for="punkt">Nowy punkt planu</label>
  <input id="punkt" type="text" maxlength="60">
  <button type="submit">Dodaj</button>
</form>
<p id="komunikat" aria-live="polite"></p>
<p id="licznik">Punkty planu: 0, zrobione: 0</p>
<ul id="lista"></ul>

Przycisk typu submit wysyła formularz, a przeglądarka domyślnie przeładowuje wtedy stronę — dodany punkt zniknąłby po ułamku sekundy. W obsłudze zdarzenia submit wywołujemy więc zdarzenie.preventDefault(), co blokuje to domyślne działanie. Obsługa submit zamiast click na przycisku ma tę zaletę, że działa również po naciśnięciu Enter w polu tekstowym.

Nowy element tworzy document.createElement("li"). Na początku istnieje tylko w pamięci; na stronie pojawi się dopiero po dołączeniu do listy przez lista.append(punkt). Wartość wpisaną w pole odczytuje właściwość value, a metoda trim() usuwa spacje z początku i końca.

Trzy zadania z DOM

Zadanie 1. Licznik obecności. Po każdym kliknięciu przycisku #dodaj-osobe zwiększ zmienną obecni o 1 i pokaż w akapicie #obecni tekst w formacie Obecnych: 3. Zmienna musi być zadeklarowana przez let poza funkcją obsługi zdarzenia.

Zadanie 2. Dodawanie punktu planu. Obsłuż zdarzenie submit formularza. Zablokuj przeładowanie strony. Jeśli po usunięciu spacji pole jest puste, wpisz w #komunikat tekst Wpisz treść punktu. i nie dodawaj niczego. W przeciwnym razie utwórz li z wpisanym tekstem, dodaj go do #lista, wyczyść pole i komunikat, a w #licznik pokaż liczbę punktów.

Zadanie 3. Punkt zrobiony. Kliknięcie punktu listy ma przełączać na nim klasę zrobione (styl przekreślenia jest już w style.css). Licznik ma pokazywać tekst Punkty planu: 3, zrobione: 1. Powtórne kliknięcie punktu cofa oznaczenie.

Dla chętnych: dodaj przycisk „Usuń zrobione”, który usuwa z listy wszystkie punkty z klasą zrobione i odświeża licznik. Przyda się lista.querySelectorAll("li.zrobione") i metoda remove().

Rozwiązanie zadań

Pełny plik rozwiazanie/app.js:

const przycisk = document.querySelector("#przywitaj");
const powitanie = document.querySelector("#powitanie");

przycisk.addEventListener("click", () => {
  powitanie.textContent = "Witamy na spotkaniu koła!";
});

// Zadanie 1
const dodajOsobe = document.querySelector("#dodaj-osobe");
const obecniTekst = document.querySelector("#obecni");
let obecni = 0;

dodajOsobe.addEventListener("click", () => {
  obecni = obecni + 1;
  obecniTekst.textContent = "Obecnych: " + obecni;
});

// Zadania 2 i 3
const formularz = document.querySelector("#formularz");
const pole = document.querySelector("#punkt");
const komunikat = document.querySelector("#komunikat");
const licznik = document.querySelector("#licznik");
const lista = document.querySelector("#lista");

function odswiezLicznik() {
  const wszystkie = lista.querySelectorAll("li").length;
  const zrobione = lista.querySelectorAll("li.zrobione").length;
  licznik.textContent = "Punkty planu: " + wszystkie + ", zrobione: " + zrobione;
}

formularz.addEventListener("submit", (zdarzenie) => {
  zdarzenie.preventDefault();
  const tekst = pole.value.trim();
  if (tekst === "") {
    komunikat.textContent = "Wpisz treść punktu.";
    return;
  }
  const punkt = document.createElement("li");
  punkt.textContent = tekst;
  punkt.addEventListener("click", () => {
    punkt.classList.toggle("zrobione");
    odswiezLicznik();
  });
  lista.append(punkt);
  pole.value = "";
  komunikat.textContent = "";
  odswiezLicznik();
});
TestOczekiwany wynik
Trzy kliknięcia „+1 osoba”Obecnych: 3
Dodaj z pustym polemkomunikat Wpisz treść punktu., lista bez zmian
Dodaj z samymi spacjamijak wyżej — trim() usuwa spacje
Dodaj „Powitanie”, „Quiz”, „Projekt”trzy punkty, Punkty planu: 3, zrobione: 0
Kliknij „Quiz”przekreślenie, Punkty planu: 3, zrobione: 1
Kliknij „Quiz” ponowniePunkty planu: 3, zrobione: 0
Wpis <b>Test</b>na liście widać dosłownie znaczniki, tekst nie jest pogrubiony

Komentarz do rozwiązania:

  • let obecni = 0 stoi poza funkcją. Gdyby był wewnątrz, przy każdym kliknięciu licznik zaczynałby od zera i zawsze pokazywał 1.
  • return w bloku if kończy obsługę zdarzenia — niczego nie dodajemy, gdy pole jest puste.
  • Licznik liczy elementy w DOM zamiast prowadzić osobne zmienne. Dzięki temu nie rozjedzie się z tym, co widać na stronie, nawet po dodaniu zadania „Usuń zrobione”.
  • Ostatni test pokazuje, dlaczego używamy textContent. Wstawienie tekstu użytkownika przez innerHTML pozwoliłoby wstrzyknąć do strony dowolny kod HTML.
  • Element li z obsługą kliknięcia nie jest dostępny z klawiatury. W prawdziwej aplikacji w punkcie listy umieszcza się przycisk albo pole wyboru — to dobry temat na rozszerzenie.

Typowe błędy

ObjawCo sprawdzić
Cannot read properties of null (reading 'addEventListener')querySelector nie znalazł elementu: brak # przed id, literówka albo skrypt bez defer.
Punkt pojawia się i natychmiast znikaBrak preventDefault — formularz przeładował stronę.
Licznik obecności zawsze pokazuje 1Zmienna obecni jest deklarowana wewnątrz funkcji obsługi.
Kliknięcie nic nie robi, brak błędu w konsoliZmiany w app.js nie zostały zapisane albo strona nie została odświeżona.
Funkcja wykonuje się od razu po wczytaniuPrzekazano wynik wywołania, np. addEventListener("click", dodaj()), zamiast samej funkcji.
Przekreślenie nie działaNazwa klasy w toggle różni się od nazwy w style.css.

Pytania kontrolne i punktacja

  1. Czym jest DOM i czy JavaScript zmienia w nim plik index.html na dysku?
  2. Po co w obsłudze zdarzenia submit wywołujemy preventDefault()?
  3. Dlaczego tekst wpisany przez użytkownika wstawiamy przez textContent, a nie innerHTML?

Odpowiedzi: DOM to utworzone przez przeglądarkę drzewo obiektów odpowiadających elementom strony; skrypt zmienia je w pamięci, a plik na dysku pozostaje bez zmian. preventDefault() blokuje domyślne wysłanie formularza i przeładowanie strony, które usunęłoby dodany punkt. textContent traktuje dane jako zwykły tekst, więc wpisane znaczniki nie staną się częścią strony; innerHTML interpretowałby je jako kod.

Proponowana punktacja (razem 10 punktów): zadanie 1 — zdarzenie i zmienna poza funkcją 2 pkt; zadanie 2 — submit z preventDefault 1 pkt, obsługa pustego wpisu 2 pkt, tworzenie i dodanie li z czyszczeniem pola 2 pkt; zadanie 3 — przełączanie klasy 1 pkt, licznik zrobionych 1 pkt; wykonane testy z tabeli — 1 pkt. Zadanie dla chętnych nie jest wymagane do kompletu punktów.

Materiały do pobrania

Materiały są dostępne bez logowania. Projekt startowy ma gotowy HTML, CSS i wspólny przykład w komentarzu; rozwiązanie zawiera pełny app.js. Klucz jest publiczny i służy nauce oraz samodzielnemu sprawdzeniu pracy, nie poufnemu sprawdzianowi.

Co dalej po DOM?

Następne zajęcia mogą przenieść listę do tablicy w JavaScripcie i zapisywać ją w pamięci przeglądarki albo walidować pola formularza przed wysłaniem. Tę ścieżkę — od zmiennych przez DOM po zdarzenia i pracę z danymi — rozwija kurs JavaScript w TablicaPro z prezentacjami, ćwiczeniami i testami. Jeśli dane z formularza mają trafić na serwer, zobacz lekcję o formularzach PHP.

Źródła techniczne: wprowadzenie do DOM w MDN, addEventListener w MDN oraz właściwość textContent w MDN.

Poprowadź kolejne lekcje JavaScript

Pełny kurs: prezentacje, ćwiczenia i testy do pracy z klasą.

Kup dostęp do kursu JavaScript →

Zobacz program kursu