Lekcja 9: Wprowadzenie do DOM

DOM (Document Object Model) to kluczowa koncepcja w web developmencie, pozwalająca JavaScriptowi na interakcję ze strukturą, zawartością i stylami dokumentu HTML lub XML. DOM reprezentuje dokument jako drzewo obiektów, gdzie każdy element HTML, atrybut i fragment tekstu jest węzłem (node).

Co to jest DOM?

Kiedy przeglądarka ładuje stronę HTML, tworzy w pamięci reprezentację DOM tego dokumentu. JavaScript może następnie używać tego modelu do manipulowania stroną.

Struktura Drzewa DOM

Każdy element w dokumencie HTML jest reprezentowany jako węzeł w drzewie DOM. Główne typy węzłów to:

Przykład prostego drzewa DOM dla poniższego HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Moja Strona</title>
</head>
<body>
    <h1>Nagłówek</h1>
    <p>To jest <strong>ważny</strong> tekst.</p>
</body>
</html>

Drzewo DOM (uproszczone):

Document
└── html
    ├── head
    │   └── title
    │       └── Text: "Moja Strona"
    └── body
        ├── h1
        │   └── Text: "Nagłówek"
        └── p
            ├── Text: "To jest "
            ├── strong
            │   └── Text: "ważny"
            └── Text: " tekst."

Dostęp do elementów DOM

JavaScript udostępnia obiekt document, który jest punktem wejścia do DOM. Istnieje wiele metod pozwalających na znajdowanie i wybieranie elementów HTML.

Starsze metody (nadal używane)

<div id="main-container">
    <p class="intro">Pierwszy paragraf.</p>
    <p>Drugi paragraf.</p>
    <p class="intro">Trzeci paragraf.</p>
</div>
// Plik HTML musi zawierać powyższy div
// Znajdź element po ID
let container = document.getElementById("main-container");
console.log(container);

// Znajdź wszystkie paragrafy
let allParagraphs = document.getElementsByTagName("p");
console.log(allParagraphs); // HTMLCollection [p.intro, p, p.intro]
console.log(allParagraphs.length); // 3
console.log(allParagraphs[0]); // Pierwszy paragraf

// Znajdź elementy po klasie
let introParagraphs = document.getElementsByClassName("intro");
console.log(introParagraphs); // HTMLCollection [p.intro, p.intro]
console.log(introParagraphs.length); // 2

Uwaga: HTMLCollection jest "żywa" - automatycznie aktualizuje się, gdy struktura DOM ulega zmianie.

Nowoczesne metody (zalecane)

Te metody wykorzystują selektory CSS do wybierania elementów, co jest bardziej elastyczne.

// Plik HTML musi zawierać div#main-container z paragrafami
// Znajdź pierwszy element pasujący do selektora
let firstIntro = document.querySelector(".intro"); // Znajdzie pierwszy paragraf z klasą "intro"
console.log(firstIntro);

let mainDiv = document.querySelector("#main-container"); // Znajdzie div o id "main-container"
console.log(mainDiv);

let firstParagraphInMain = document.querySelector("#main-container p"); // Znajdzie pierwszy paragraf wewnątrz diva
console.log(firstParagraphInMain);

// Znajdź wszystkie elementy pasujące do selektora
let allIntroParagraphs = document.querySelectorAll(".intro");
console.log(allIntroParagraphs); // NodeList [p.intro, p.intro]
console.log(allIntroParagraphs.length); // 2

// NodeList można łatwo iterować np. za pomocą forEach
allIntroParagraphs.forEach(function(paragraph, index) {
    console.log(`Paragraf intro ${index}:`, paragraph);
});

Uwaga: NodeList zwrócona przez querySelectorAll jest statyczna - nie aktualizuje się automatycznie przy zmianach w DOM.

Nawigacja po drzewie DOM

Po wybraniu elementu (węzła) można nawigować po drzewie DOM, aby uzyskać dostęp do jego rodziców, dzieci lub rodzeństwa.

// Plik HTML musi zawierać div#main-container z paragrafami
let mainContainer = document.getElementById("main-container");
let firstP = mainContainer.querySelector("p"); // Pierwszy paragraf

if (firstP) { // Sprawdzenie czy element został znaleziony
    console.log("Rodzic pierwszego paragrafu:", firstP.parentNode); // div#main-container
    console.log("Następny element po pierwszym paragrafie:", firstP.nextElementSibling); // Drugi paragraf 

} if (mainContainer) { console.log("Dzieci kontenera (elementy):", mainContainer.children); // HTMLCollection [p.intro, p, p.intro] }

Zadanie praktyczne

Stwórz prosty plik HTML z kilkoma elementami (np. nagłówek h1, kilka paragrafów p, lista ul z elementami li). Nadaj niektórym elementom ID i klasy. Następnie napisz skrypt JavaScript, który:

  1. Znajdzie element h1 i wyświetli go w konsoli.
  2. Znajdzie wszystkie elementy li i wyświetli ich liczbę.
  3. Znajdzie element o określonym ID.
  4. Znajdzie wszystkie elementy o określonej klasie.
Pokaż rozwiązanie

Przykładowy HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Zadanie DOM</title>
</head>
<body>
    <h1 id="main-heading">Witaj w DOM</h1>
    <p class="content">To jest pierwszy paragraf.</p>
    <p>To jest drugi paragraf.</p>
    <ul class="item-list">
        <li>Element 1</li>
        <li class="special">Element 2 (specjalny)</li>
        <li>Element 3</li>
    </ul>
    <script src="script.js"></script>
</body>
</html>

Przykładowy JavaScript (script.js):

// Upewnij się, że skrypt jest uruchamiany po załadowaniu DOM
document.addEventListener('DOMContentLoaded', (event) => {
    // 1. Znajdź h1
    let heading = document.querySelector("h1"); // lub document.getElementsByTagName("h1")[0];
    console.log("Znaleziony nagłówek:", heading);

    // 2. Znajdź wszystkie li i ich liczbę
    let listItems = document.querySelectorAll("li"); // lub document.getElementsByTagName("li");
    console.log("Liczba elementów li:", listItems.length);

    // 3. Znajdź element po ID
    let mainHeading = document.getElementById("main-heading");
    console.log("Element o ID main-heading:", mainHeading);

    // 4. Znajdź elementy po klasie
    let contentParagraphs = document.getElementsByClassName("content");
    console.log("Elementy o klasie content:", contentParagraphs);

    let specialItems = document.querySelectorAll(".special"); // Użycie querySelectorAll dla klasy
    console.log("Elementy o klasie special (querySelectorAll):", specialItems);
});

Zadanie do samodzielnego wykonania

Używając HTML z poprzedniego zadania, napisz skrypt, który znajdzie listę ul o klasie item-list, a następnie używając nawigacji po drzewie DOM (np. children lub querySelectorAll w kontekście znalezionego elementu), wyświetli w konsoli tekst każdego elementu li znajdującego się wewnątrz tej listy.

FAQ - Wprowadzenie do DOM

Czy DOM to to samo co HTML?

Nie. HTML to język znaczników używany do strukturyzowania treści strony. DOM to obiektowa reprezentacja tego dokumentu HTML w pamięci przeglądarki, stworzona na podstawie kodu HTML. JavaScript interaguje z DOM, a nie bezpośrednio z plikiem HTML.

Które metody wybierania elementów są najlepsze?

Obecnie zaleca się używanie querySelector i querySelectorAll ze względu na ich elastyczność (korzystają z selektorów CSS) i spójność. getElementById jest nadal bardzo szybki i użyteczny, gdy mamy unikalne ID. Metody getElementsByTagName i getElementsByClassName są starsze, ale wciąż działają.

Co to jest HTMLCollection i NodeList?

Są to kolekcje podobne do tablic, zwracane przez metody DOM. Główna różnica: HTMLCollection jest zazwyczaj "żywa" (automatycznie odzwierciedla zmiany w DOM), podczas gdy NodeList (zwracana przez querySelectorAll) jest statyczna. Obie można iterować, ale NodeList ma wbudowaną metodę forEach.

Dlaczego `childNodes` zawiera węzły tekstowe (np. białe znaki)?

childNodes zwraca wszystkie typy węzłów-dzieci, w tym węzły tekstowe reprezentujące spacje, tabulacje czy znaki nowej linii między elementami HTML w kodzie źródłowym. Jeśli potrzebujesz tylko elementów HTML, użyj właściwości children.

Czy mogę wybrać element, który jeszcze nie istnieje?

Nie. Metody wybierania elementów działają na drzewie DOM, które jest tworzone na podstawie załadowanego HTML. Jeśli spróbujesz wybrać element przed jego załadowaniem (np. skrypt w <head> próbujący znaleźć element w <body>), metoda zwróci null lub pustą kolekcję.

Co oznacza, że NodeList jest "statyczna"?

Oznacza to, że kolekcja zwrócona przez querySelectorAll jest "migawką" stanu DOM w momencie jej utworzenia. Jeśli po jej utworzeniu dodasz lub usuniesz elementy pasujące do selektora, statyczna NodeList nie zostanie automatycznie zaktualizowana. W przeciwieństwie do "żywych" HTMLCollection.