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.
1
2
3
4
5
6
<head>
<scriptsrc="skrypt.js"></script>
</head>
<body>
<pid="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:
1
2
3
<h1id="tytul">Sklep komputerowy</h1>
<pclass="cena">650 zł</p>
<pclass="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.
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ą.
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.
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.
1
2
3
4
constkoszyk=document.getElementById("koszyk");
koszyk.innerHTML="Masz <b>3</b> produkty";
conststopka=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.
1
2
3
4
constfoto=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.
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.
1
2
3
4
constcena=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.
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.
1
2
3
4
5
6
7
constlista=document.getElementById("produkty");
constnazwy=["Monitor 24","Klawiatura","Mysz"];
for(leti=0;i<nazwy.length;i++){
constpozycja=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.
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
Zapis
Znaczy
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