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
clickisubmitza pomocąaddEventListener; - utworzyć nowy element
li, dodać go do listy i przełączyć mu klasę CSS.
| Czas | Praca na lekcji |
|---|---|
| 0–5 min | Strona startowa i konsola — co dziś ożywimy? |
| 5–15 min | DOM, querySelector, textContent i pierwsze zdarzenie click |
| 15–22 min | Formularz, submit i preventDefault, tworzenie elementu |
| 22–37 min | Trzy zadania samodzielne |
| 37–42 min | Testy w parach: pusty wpis, spacje, kilka punktów |
| 42–45 min | Pytania 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();
});
| Test | Oczekiwany wynik |
|---|---|
| Trzy kliknięcia „+1 osoba” | Obecnych: 3 |
| Dodaj z pustym polem | komunikat Wpisz treść punktu., lista bez zmian |
| Dodaj z samymi spacjami | jak 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” ponownie | Punkty 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 = 0stoi poza funkcją. Gdyby był wewnątrz, przy każdym kliknięciu licznik zaczynałby od zera i zawsze pokazywał 1.returnw blokuifkoń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 przezinnerHTMLpozwoliłoby wstrzyknąć do strony dowolny kod HTML. - Element
liz 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
| Objaw | Co 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 znika | Brak preventDefault — formularz przeładował stronę. |
| Licznik obecności zawsze pokazuje 1 | Zmienna obecni jest deklarowana wewnątrz funkcji obsługi. |
| Kliknięcie nic nie robi, brak błędu w konsoli | Zmiany w app.js nie zostały zapisane albo strona nie została odświeżona. |
| Funkcja wykonuje się od razu po wczytaniu | Przekazano wynik wywołania, np. addEventListener("click", dodaj()), zamiast samej funkcji. |
| Przekreślenie nie działa | Nazwa klasy w toggle różni się od nazwy w style.css. |
Pytania kontrolne i punktacja
- Czym jest DOM i czy JavaScript zmienia w nim plik
index.htmlna dysku? - Po co w obsłudze zdarzenia
submitwywołujemypreventDefault()? - Dlaczego tekst wpisany przez użytkownika wstawiamy przez
textContent, a nieinnerHTML?
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
- Pakiet JavaScript: projekt startowy, rozwiązanie, instrukcja i karta pracy
- Karta pracy ucznia — otwórz i wydrukuj
- Działający projekt — rozwiązanie do wypróbowania
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 →