Hierarchia dokumentu

Każda strona HTML składa się z elementów - to już wiemy.

Na samej górze jest kontynent - czyli okno przeglądarki - window, które zawiera w sobie wszystkie obiekty, funkcje i właściwości. W tym oknie znajduje się nasze małe państewko - document (czyli nasza strona).
W tym państewku żyje od groma i tyć tyć różnych obiektów i ...elementów (zupełnie jak w życiu!).
W naszych skryptach będziemy operować na tych obiektach, nakazując im wykonywanie różnych rzeczy.

Do odzwierciedlenia ułożenia elementów JS korzysta z DOM. DOM czyli Document Object Model. Cóż to za dziwo? Jeżeli czytałeś ten kurs od deski do deski (czego wcale nie polecam), pamiętasz, że w wymaganiach pisałem o debugerze. Naciskamy F12 i zależnie od przeglądarki w zakładce HTML/Elements widzimy drzewo dokumentu składające się ze wspomnianych elementów.


Najlepiej uczyć się na przykładach, dlatego zacznijmy od bardzo prostej strony:


<!doctype html>
<html><head>
    <title>To jest tytuł strony</title>
</head>
<body>
    <p>
        Ten napis zawiera <strong>pogrubiony tekst</strong>
    </p>
</body>
</html>

Nasz dokument możemy rozrysować jako hierarchiczne drzewo. Na samej górze jest document HTML, a tuż pod nim znajdują się jego "dzieci" - nody. Bardzo proste i w zasadzie mocno teoretyczne drzewo może mieć postać:

Hierarchiczne ułożenie elementów na stronie

W praktyce takich nodów jest o wiele więcej.

Odwoływanie się do obiektów

Gdy DOM jeszcze nie istniał, odwoływanie się do obiektów wymagało korzystania ze specjalnych kolekcji. Większość obiektów na stronie pogrupowana jest w kolekcje które są swego rodzaju tablicami. I tak dla przykładu mamy kolekcję forms, która zawiera wszystkie formularze na naszej stronie, kolekcję images, która zawiera wszystkie znaczniki IMG na naszej stronie, kolekcję links zawierającą linki itp.
Korzystanie z takich kolekcji było całkiem miłe (np. żeby pobrać 1 formę na stronie korzystaliśmy z instrukcji forms[0]), jednak ograniczało się tylko to elementów, dla których stworzono kolekcje.
W dzisiejszych czasach takich ograniczeń nie mamy i możemy działać na każdym elemencie na stronie.

Do odwoływania się do jakiegoś elementu skorzystamy z jednej z trzech metod: getElementById, getElementsByTagName lub z querySelector

Zanim zaczniemy...

O zdarzeniach (eventach) jeszcze się nie uczyliśmy i ich nie używaliśmy, ale w tym wypadku zrobimy wyjątek.
Na samym początku kursu wspomniałem, że najbezpieczniej jest wstawiać skrypty na końcu strony. Dzięki temu serwujemy jako pierwszy content strony, ale przede wszystkim mamy pewność, że elementy na stronie wczytają się przed skryptem, który się do nich odnosi.
Jest to bardzo proste zabezpieczenie się przed sytuacją, gdy skrypt wczyta się przed resztą strony, co spowoduje, że nie będzie jeszcze widział wciąż wczytującego się dokumentu i jego elementów. Zabezpieczenie to nie jest jednak pewne, ponieważ nie mamy pewności, czy inna osoba nie wrzuci naszego skryptu w np. head strony. Jako profesjonaliści powinniśmy korzystać z bardziej pewnych metod.
Tutaj przychodzi nam z pomocą event DOMContentLoaded, który gwarantuje nam, że skrypt zacznie swoje działanie wtedy, gdy całe drzewo DOM zostanie już wczytane. W praktyce wszystkie skrypty operujące na elementach DOM powinny korzystać z tego eventu. W przykładach pomijam ten zapis.

Ogólna konstrukcja użycia tego eventu ma postać:


document.addEventListener("DOMContentLoaded", function() {

    ...tutaj trafia nasz skrypt operujący na elementach ze strony

});

Jeżeli zagłębisz się w temat, prędzej czy później odnajdziesz w śmietniku internetu skrypty, które zamiast z powyższego eventu korzystają z window.onload (lub jego odpowiednika window.addEventListener('load'...)). Jest to błąd. Event ten jest odpalany po całkowitym wczytaniu strony, czyli także grafiki. Zazwyczaj wczytanie samego dokumentu DOM trwa kilkadziesiąc milisekund, natomiast czekanie na wgranie grafiki czasami może trwać baaardzo długo.

Pobieranie elementu za pomocą getElementById

Metoda getElementById(id) pobiera element o danym ID.

<input type="button" id="guzik" value="ok" />

document.addEventListener("DOMContentLoaded", function() {

    var g = document.getElementById('guzik');
    console.log(g.value); //korzystamy z okienka dialogowego by wypisać value pobranego guzika

});

Pobieranie elementu za pomocą getElementsByTagName

Metoda getElementsByTagName(id) pobiera zbiór (tablicę) elementów o danym tagu:


<table id="tabelka">
    <tr><td>1</td>2<td class="czerwone">3</td></tr>
    <tr><td>4</td>5<td class="czerwone">6</td></tr>
</table>

document.addEventListener("DOMContentLoaded", function() {
    var tab = document.getElementById('tabelka');

    var td = tab.getElementsByTagName('td'); //pobieramy wszystkie td z tabeli

    for (var x=0; x<td.length; x++) { //pętla po wszystkich td
        td[x] = ... //tutaj czarujemy z każdym td
    }
});

querySelector() i querySelectorAll()

W dzisiejszych czasach przeglądarki udostępniają nam dwie nowe metody, czyli querySelector() i querySelectorAll().

Pierwsza z nich zwraca pierwszy element z pasujących. Jako jej argumenty podajemy wyrażenie CSS, które określa szukane elementy:


//pobieramy pierwszy element na stronie z klasami .wazne i .inne
var element = document.querySelector('.wazne, .inne');

Druga metoda querySelectorAll() ma bardzo podobne działanie, z tą różnicą, że pobiera wszystkie pasujące elementy:


document.addEventListener("DOMContentLoaded", function() {
    //pobieramy wszystkie elementy, które mają klasy .wazne, .inne oraz element z id=main
    var elementy = document.querySelectorAll('.wazne, .inne, #main');

    if (elementy) {
        for (var i=0; i<elementy.length; i++) {
            console.log(elementy[i]);
        }
    }
});

Dodatkowe informacje na ten temat znajdziecie tutaj

Pętle po kolekcjach

Pobraliśmy powyżej kolekcję elementów. Kolekcje są bardzo podobne do tablic, ale nie są tablicami, dlatego niektórych metod nie możemy na nich wywoływać (np. splice, push, reverse).
Aby wykonać pętlę po elementach kolekcji możemy skorzystać z tradycyjnych pętli takich jak for i while:


var divs = document.querySelectorAll('div.module');

for (var i=0; i<divs.length; i++) {
    divs[i].style.color = "red";
}
ale już bardzo wygodnej pętli forEach nie będziesz mógł użyć.

/* taka pętla nie zadziała! */
document.querySelectorAll('div.module').forEach(function() {

});

var divs = document.querySelectorAll('div.module');

[].forEach.call(divs, function(div) {
    //działamy na elementach
    div.style.color = "red";
});

Warto zmienną length w pętli for podstawić wcześniej pod zmienną np:


var max = td.length;
for (var x=0; x<max; x++) {...

Dzięki temu nasz skrypt w większości przeglądarek wykona się o wiele lepiej (w starszych IE nawet 170x szybciej!).
W praktyce nie jest to zbyt często stosowana metoda - co nie znaczy, że nie warto jej znać :)