Model DOM w JavaScript: jak przeglądarka buduje drzewo dokumentu, jak pobrać z niego element metodami getElementById, getElementsByClassName i querySelector oraz jak zmienić jego treść, atrybuty, styl i klasy, a także dodać i usunąć węzeł. Lekcja 11.6 kursu INF.03.
DOM (Document Object Model, obiektowy model dokumentu) to drzewo obiektów, które przeglądarka buduje z kodu HTML strony i oddaje do dyspozycji skryptowi. Każdy znacznik jest w nim osobnym obiektem z własnościami, a zmiana własności widoczna jest na ekranie od razu, bez przeładowania strony. W części pisemnej wracają pytania „do czego służy document.getElementById", „którą instrukcją zmienić kolor tekstu elementu", „jak odwołać się do pierwszego akapitu". W zadaniu praktycznym to DOM wstawia policzony wynik do wskazanego elementu i zmienia kolory strony.
Drzewo dokumentu
Drzewo dokumentu to hierarchia węzłów, w której każdy element HTML ma jednego rodzica i dowolnie wiele dzieci, a elementy o wspólnym rodzicu są rodzeństwem. Korzeniem jest html, a całe drzewo reprezentuje obiekt document (dokument): od niego zaczyna się każde odwołanie do strony. Znaczniki i zagnieżdżanie opisuje dział o HTML. Drzewo dokumentu: korzeń html, pod nim head i body, a w body header, main z nagłówkiem i akapitem oraz footer; skrypt sięga do każdego z tych węzłów
Przeglądarka buduje to drzewo w trakcie czytania pliku, od góry do dołu. Skrypt uruchomiony w <head> wykonuje się, zanim powstanie cokolwiek z <body>, więc szuka elementów, których jeszcze nie ma, i dostaje null (nic). Kolejny wiersz, który próbuje na tym null czegoś zmienić, kończy się błędem w konsoli i skrypt staje.
<head>
<script src="skrypt.js"></script>
</head>
<body>
<p id="cena1">120 zł</p>
</body>
W takim układzie document.getElementById("cena1") zwróci null, choć akapit istnieje w pliku. Lekarstwo jest jedno: znacznik <script> idzie przed zamykającym </body>, i dokładnie tak dołącza się plik skryptu w zadaniu praktycznym.
Skrypt nie zmienia pliku HTML na dysku, tylko drzewo w pamięci przeglądarki. Po odświeżeniu strony wszystko wraca do stanu z pliku.
Pobieranie elementów
Pobranie elementu to odnalezienie w drzewie węzła, na którym skrypt ma pracować. Metody należą do obiektu document i dzielą się na dwie grupy: jedne oddają POJEDYNCZY element, drugie KOLEKCJĘ, czyli ponumerowany zbiór wszystkich pasujących elementów. Kolejne przykłady pracują na takim fragmencie strony sklepu:
<h1 id="tytul">Sklep komputerowy</h1>
<p class="cena">650 zł</p>
<p class="cena">120 zł</p>
getElementById (POBIERZ ELEMENT PO IDENTYFIKATORZE) oddaje jeden element o podanym id, bez znaku # w nawiasie. getElementsByTagName (POBIERZ ELEMENTY PO NAZWIE ZNACZNIKA) oddaje kolekcję wszystkich elementów danego znacznika, a getElementsByClassName (POBIERZ ELEMENTY PO NAZWIE KLASY) kolekcję wszystkich elementów należących do klasy.
const tytul = document.getElementById("tytul");
const akapity = document.getElementsByTagName("p");
const ceny = document.getElementsByClassName("cena");
console.log(tytul.textContent);
console.log(akapity.length);
console.log(ceny[0].textContent);
Kolekcja sama nie jest elementem: ma własność length (długość) i numerację od zera, więc do pojedynczej pozycji sięga się nawiasem kwadratowym, a do wszystkich pętlą.
const ceny = document.getElementsByClassName("cena");
for (let i = 0; i < ceny.length; i++) {
ceny[i].textContent = "wyprzedane";
}
Wynik: oba akapity klasy cena mają teraz treść wyprzedane. Ta sama instrukcja bez [i], czyli ceny.textContent = "wyprzedane";, nie zmieniłaby niczego, bo kolekcja takiej własności nie ma.
querySelector (WYBIERZ PO SELEKTORZE) i querySelectorAll (WYBIERZ WSZYSTKIE PO SELEKTORZE) przyjmują selektor CSS, ten sam co w arkuszu stylów: #tytul dla identyfikatora, .cena dla klasy, p dla znacznika. Pierwsza oddaje pierwszy pasujący element, druga kolekcję wszystkich. Selektory omawia lekcja o selektorach CSS. const pierwszaCena = document.querySelector(".cena");
const wszystkieCeny = document.querySelectorAll(".cena");
console.log(pierwszaCena.textContent);
console.log(wszystkieCeny.length);
console.log(document.querySelector("#tytul").textContent);
Pułapka: getElementById ma ELEMENT w liczbie pojedynczej, a getElementsByTagName i getElementsByClassName ELEMENTS w mnogiej. Zapisy getElementByID i getElementByTagName nie istnieją, bo JavaScript rozróżnia wielkość liter.
Zmiana treści elementu
Treść elementu zmienia się przez przypisanie do jednej z trzech własności. innerHTML (wewnętrzny HTML) podmienia zawartość razem ze znacznikami: to, co przypiszesz, przeglądarka przeczyta jak kod strony. textContent (treść tekstowa) wstawia czysty tekst, więc znaczniki pokażą się dosłownie, ze znakami mniejszości. innerText (tekst wewnętrzny) działa jak textContent, ale pomija tekst ukryty stylem.
const koszyk = document.getElementById("koszyk");
koszyk.innerHTML = "Masz <b>3</b> produkty";
const stopka = document.getElementById("stopka");
stopka.textContent = "Masz <b>3</b> produkty";
Wynik: w koszyku widać Masz 3 produkty z pogrubioną trójką, a w stopce dosłownie Masz <b>3</b> produkty, razem ze znacznikami.
To samo przypisanie ze znakiem plus dokłada treść zamiast ją kasować: koszyk.innerHTML = koszyk.innerHTML + " i mysz";. Do prawdziwej strony innerHTML nadaje się tylko dla treści, którą sam tworzysz: wstawiony w ten sposób tekst od użytkownika wprowadza na stronę cudzy kod, więc dane z formularza idą przez textContent. Arkusz CKE żąda jednak innerHTML wprost i tego trzeba użyć.
Poza DOM stoi document.write (PISZ), znane z pytań o wypisywanie tekstu. Pisze ono wprost do dokumentu w trakcie jego wczytywania, a wywołane później czyści całą stronę i zastępuje ją tym, co dostało, więc w gotowej witrynie się go nie używa.
Atrybuty i wartość pola formularza
Atrybut to para nazwa i wartość zapisana w znaczniku, na przykład src w obrazie albo href w odnośniku. setAttribute (USTAW ATRYBUT) przyjmuje nazwę atrybutu i nową wartość, getAttribute (POBIERZ ATRYBUT) samą nazwę i oddaje wartość. Najczęstsze atrybuty są dostępne także jako zwykłe własności obiektu, więc te same dwa zapisy robią to samo.
const foto = document.getElementById("foto");
foto.setAttribute("src", "monitor2.png");
foto.src = "monitor2.png";
console.log(foto.getAttribute("alt"));
Wynik dla obrazu <img id="foto" src="monitor1.png" alt="Monitor 24">: na stronie pojawia się monitor2.png w miejscu poprzedniego zdjęcia, a konsola wypisuje Monitor 24. Podmiana obrazka po kliknięciu przycisku to jedno ze stałych pytań w banku.
Pola formularza trzymają wpisany tekst w osobnej własności value (wartość). Zawsze jest to napis, nawet gdy pole ma typ number, więc do rachunków przepuszcza się go przez Number. Same pola opisuje lekcja o formularzach HTML. const pole = document.getElementById("ilosc");
const ile = Number(pole.value);
const suma = ile * 650;
document.getElementById("koszyk").innerHTML = "Do zapłaty: " + suma + " zł";
Wynik: gdy w polu stoi 2, element o identyfikatorze koszyk pokazuje Do zapłaty: 1300 zł. Znacznik input nie ma treści między nawiasami ostrymi, więc jego innerHTML jest pusty: wpisany tekst leży wyłącznie w value.
Styl i klasy
Wygląd elementu zmienia się na dwa sposoby: przez własność style (styl), która ustawia regułę wprost w znaczniku, albo przez podmianę klasy, po której gotową regułę znajdzie arkusz stylów. Nazwy właściwości CSS z minusem tracą go w JavaScript i dostają wielką literę: background-color staje się backgroundColor, font-size staje się fontSize.
const cena = document.getElementById("cena1");
cena.style.color = "blue";
cena.style.backgroundColor = "yellow";
document.body.style.backgroundColor = "black";
Wynik: akapit cena1 ma niebieski tekst na żółtym tle, a tło całej strony robi się czarne. Ostatni wiersz to dokładnie to, o co prosi arkusz w zadaniu „skrypt zmienia kolory tła i czcionki całej strony".
Własność display (wyświetlanie) o wartości none usuwa element z układu strony: element znika, a miejsce po nim się zamyka. Wartość block przywraca go z powrotem. Klasę całą naraz podmienia className (nazwa klasy), a pojedyncze klasy dokłada i zabiera classList (lista klas) metodami add, remove i toggle.
const promocja = document.getElementById("promocja");
promocja.style.display = "none";
promocja.className = "wyrozniona";
promocja.classList.add("nowosc");
promocja.classList.remove("wyprzedaz");
console.log(promocja.className);
Wynik: wyrozniona nowosc. Element jest ukryty, className nadpisał wszystkie wcześniejsze klasy, add dołożył jedną, a remove nieobecnej klasy wyprzedaz nie zrobił nic.
Pułapka: style.background-color to błąd składni, bo minus czyta się jako odejmowanie, a style.font-color nie istnieje w ogóle: kolorem tekstu jest color.
Tworzenie i usuwanie elementów
Nowy element powstaje w dwóch krokach. createElement (UTWÓRZ ELEMENT) buduje węzeł z podanej nazwy znacznika, ale trzyma go poza drzewem, więc na stronie jeszcze go nie widać. appendChild (DOŁĄCZ DZIECKO) wywołany na elemencie rodzica dopina węzeł jako jego ostatnie dziecko i dopiero wtedy element pojawia się na ekranie.
const lista = document.getElementById("produkty");
const nazwy = ["Monitor 24", "Klawiatura", "Mysz"];
for (let i = 0; i < nazwy.length; i++) {
const pozycja = document.createElement("li");
pozycja.textContent = nazwy[i];
lista.appendChild(pozycja);
}
Wynik: lista <ul id="produkty"> dostaje trzy elementy <li> z nazwami w kolejności z tablicy. Tablice omawia lekcja o tablicach i obiektach. removeChild (USUŃ DZIECKO) kasuje węzeł, ale wywołuje się go na RODZICU i podaje mu dziecko do usunięcia, nie odwrotnie.
const lista = document.getElementById("produkty");
const pierwsza = lista.getElementsByTagName("li")[0];
lista.removeChild(pierwsza);
console.log(lista.getElementsByTagName("li").length);
Wynik: 2, bo pierwsza pozycja zniknęła. Całą zawartość elementu czyści jeden wiersz lista.innerHTML = "";, i to jest zwykle krótsza droga niż pętla z removeChild.
Na egzaminie
Pytania pisemne o DOM mają kilka stałych kształtów. „Metoda document.getElementById(id) ma za zadanie": zwrócić odniesienie do elementu o podanym identyfikatorze, nie sprawdzić formularza ani nic wypisać. „DOM dostarcza metod i własności pozwalających na": pobieranie i modyfikowanie elementów strony wyświetlonej przez przeglądarkę. Odwołanie do pierwszego akapitu we fragmencie bez identyfikatorów to getElementsByTagName("p")[0], a operacje na znacznikach z klasą nomargin to getElementsByClassName("nomargin"). Zmiana koloru tekstu elementu o identyfikatorze test to document.getElementById("test").style.color = "blue";, a nie style.font-color, nie css.color i nie style.background-color. Zawartość elementu zmienia innerHTML, a wartość atrybutu setAttribute, nie getAttribute.
Kilka odpowiedzi myli się regularnie. Pytanie z komunikatem błędu w konsoli i kodem getElementByTagName ma jedną poprawną odpowiedź: błędnie napisana nazwa metody, powinno być getElementsByTagName. Kolekcja nie jest elementem, więc bez indeksu albo pętli przypisanie do niej nic nie da. Skrypt umieszczony w <head> bez opóźnienia nie znajdzie elementu z <body>. W kodzie z arkuszy i banku pytań zmienne deklaruje var, starsze od let i const, ale działające tu tak samo.
W zadaniu praktycznym polecenie brzmi zwykle: „skrypt zmienia kolor tła i czcionki całej strony", „wynik wyświetlony w elemencie o podanym identyfikatorze", „lista wygenerowana na podstawie tablicy". Pierwsze to document.body.style.backgroundColor i document.body.style.color, drugie document.getElementById("wynik").innerHTML = ..., trzecie pętla z createElement i appendChild. Plik dołącza się znacznikiem <script src="skrypt.js"></script> przed zamykającym </body>, a wywołuje kliknięciem przycisku: zdarzenia opisuje lekcja o zdarzeniach i formularzach. Ściąga
document.getElementById("x")
jeden element o identyfikatorze x, bez znaku #
document.getElementsByTagName("p")
kolekcja wszystkich akapitów
document.getElementsByClassName("c")
kolekcja wszystkich elementów klasy c
document.querySelector("#x")
pierwszy element pasujący do selektora CSS
document.querySelectorAll(".c")
kolekcja wszystkich pasujących do selektora
kolekcja[0]
pierwszy element kolekcji, numeracja od zera
kolekcja.length
liczba elementów w kolekcji
el.innerHTML = "<b>3</b>"
treść razem ze znacznikami, znaczniki działają
el.textContent = "<b>3</b>"
czysty tekst, znaczniki widoczne dosłownie
el.setAttribute("src", "a.png")
ustawienie wartości atrybutu
el.getAttribute("alt")
odczyt wartości atrybutu
pole.value
tekst wpisany w pole formularza
el.style.color = "blue"
kolor tekstu elementu
el.style.backgroundColor = "red"
kolor tła, nazwa CSS bez minusa, wielką literą
el.style.display = "none"
ukrycie elementu razem z jego miejscem
el.className = "a b"
podmiana całego atrybutu class
el.classList.add("a")
dołożenie jednej klasy, bez ruszania pozostałych
document.createElement("li")
nowy węzeł, jeszcze poza drzewem
rodzic.appendChild(el)
dopięcie węzła jako ostatniego dziecka
rodzic.removeChild(el)
usunięcie dziecka, wywołane na rodzicu
Pobierz ściągę PDF