Spacer po hierarchii

Wiemy już, że elementy na stronie tworzą hierarchiczne drzewo. Aby operować na takich obiektach, musimy dobrze opanować sztukę "spacerowania" po nich ^^.

Każdy element na stronie tworzy tak zwany zwaną node czyli pojedynczy korzeń drzewa. Takimi nodami są nie tylko elementy, ale także tekst w nich zawarty, ich atrybuty itp.

Przykładowo w poniższym kodzie nodem jest zarówno akapit jak i nodem jest tekst, który się w nim znajduje.


<p>Jakiś tekst</p>

Niektóre typy nodów mogą zawierać w sobie nody - dzieci (child), a niektóre dzieci mieć nie mogą (na przykład takim nodem będzie znak łamania linii <br /> lub <input />) ;). Jeżeli dany element posiada atrybuty (np src, alt), to są one jego nodami.

Relacje między nodami

Rozpiszmy przykładowy akapit, który posiada w sobie pogrubiony tekst:

To jest bardzo ważny tekst


<p id="paragraf">To jest <strong>bardzo</strong> ważny tekst</p>

Nasz paragraf możemy w łatwy sposób rozpisać za pomocą wykresu:

rozpisany akapit

  • #paragraf jest rodzicem (parentNode) tekstu i strong
  • tekst i strong są dziećmi (childNodes) #paragraf
  • To jest jest pierwszym dzieckiem (firstChild) #paragraf
  • ważny tekst jest ostatnim dzieckiem (lastChild) #paragraf
  • bardzo jest pierwszym dzieckiem (firstChild) drugiego dziecka (childNodes[1]) #paragraf
  • To jest, bardzo, ważny tekst są typu tekstowego

Znając hierarchiczne położenie obiektów, możemy w łatwy sposób się po nich przemieszczać:


var p = document.querySelector('#paragraf');
console.log(p.parentNode) //wskazuje na rodzica czyli na body - jeżeli dany paragraf jest w body
console.log(p.childNodes[1]) //wskazuje na drugie dziecko noda czyli <strong>
console.log(p.childNodes[1].firstChild) //wskazuje tekst "bardzo"
console.log(p.lastChild) //wskazuje tekst "tekst"

Każdy nod posiada pewne właściwości i swój typ. I tak np. właściwość nodeType dla tekstu ma wartość 3, a dla przycisku na stronie ma wartość 1. typy nodów i ich właściwości są tylko do odczytu.

Istnieje kilka właściwości przypisanych do każdego node:
WłaściwośćOpis
nodeNamenazwa node
nodeValue, textContentwartość node (tylko dla nodów tekstowych)
nodeTypetyp node - zobacz niżej
parentNode, parentElement rodzic (parent), jeżeli istnieje
children lista dzieci - elementów html
childNodes lista dzieci danego obiektu (child nodes)
firstChild pierwsze dziecko (first child)
lastChild ostatnie dziecko (last child)
previousSibling zwraca poprzedni nod na tym samym poziomie
nextSibling zwraca następny nod na tym samym poziomie
attributes lista atrybutów elementu
ownerDocument dokument zawierający ten element

Powyższe właściwości wystarczą ci w większości skryptów. Dla dociekliwych - całą listę znajdziecie zobaczyć np tutaj.

Powyższa właściwość nodeType zwraca tym danego noda w postaci numeru:

Typy nodów:
NumberOpis
1element HTML
2atrybut elementu
3tekst
8zawartość HTML
9dokument
10dokument definicji

Zastosowanie w praktyce

Poniższy przykład pokazuje zastosowanie powyższych informacji w praktyce:


<table id="tabelka" class="tab">
    <tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td></tr>
    <tr><td>7</td><td>8</td><td>9</td><td>10</td><td>11</td><td>12</td></tr>
    <tr><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td></tr>
    <tr><td>19</td><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td></tr>
</table>

function markRow(id) {
    var tab = document.getElementById(id);
    var td = tab.getElementsByTagName('td');

    for (var x=0; x<td.length; x++) {
        td[x].addEventListener('mouseover', function() {
            this.parentNode.classList.add('mark'); //ustawiamy klase hover dla TR - dzięki niej zmienimy wygląd dla td
        });
        td[x].addEventListener('mouseout', function() {
            this.parentNode.classList.remove('mark');
        });
    }
}

document.addEventListener("DOMContentLoaded", function() {
    markRow('tabelka');
});
123456
789101112
131415161718
192021222324

Przykład jest mocno ułomny, bo przecież coś podobnego bez problemu wykonamy za pomocą 1 linijki CSS. Autor przeprasza.

Atrybuty jako nody

Atrybuty każdego elementu też są jego nodami, tak więc dla każdego atrybutu możemy korzystać z właściwości nodów:


for (var x=0; x<p.attributes.length; x++) {
    if (p.attributes[x].nodeName.toLowerCase()=='title') {
        console.log('Wartość atrybutu "title" wynosi: ' +p.attributes[x].nodeValue);
    }
}

Aby pobrać atrybut elementu możemy skorzystać z metody getAttribute(atrybut). Atrybut może istnieć i być pusty, jednak nie przeszkadza to by sprawdzić jego istnienie:


console.log('Wartość atrybutu "title" wynosi: ' +p.getAttribute('title'));

//sprawdzamy czy atrybut istnieje
if (!p.getAttribute('title')) {
    ...atrybut nie istnieje
}

Aby ustawić wartość atrybutu możemy skorzystać z setAttribute(atrybut). Nazwy atrybutów tworzymy tak samo jak w przypadku nazw css dla javascript, tak więc aby zmienić atrybut bgcolor, musimy go podać jako bgColor.

td[0].setAttribute('bgColor','red');

Do usuwania atrybutu służy metoda removeAttribute(atrybut).


td.removeAttribute('bgColor');
td.className = 'czerwona';

W IE7 (i kilku innych przeglądarkach) za pomocą metody setAttribute() nie możemy ustawiać atrybutów style, class oraz zdarzeń inilne. W IE8 zostało to poprawione, jednak zdarzeń wciąż nie możemy ustawiać (co w zasadzie problemem nie jest, gdyż i tak tego typu deklaracji super bohaterowie nie lubią).

Nody tekstowe

Spróbujmy teraz odczytać wartość noda tekstowego:


console.log( 'Wartość tekstu: ' + p.childNodes[1].firstChild.nodeValue );

W powyższym kodzie odczytaliśmy wartość tekstu w 4 akapicie. Zwróć uwagę, że czytaliśmy wartość pierwszego noda dla tego akapitu. Tekst sam w sobie jest nodem.

Teoretycznie pobrany tekst powinien być zwrócony w całości, niezależnie od jego długości. Teoria jednak nie zawsze idzie w parze z praktyką. W Operze 7-9, wielkość pobranego tekstu wynosi 32kb, w starszych przeglądarkach z serii Mozilla wynosi tylko 4kb. Jeżeli wielkość tekstu przekroczy te magiczne wartości, zostanie on podzielony na części - nody tekstowe. Dlatego czasami przy pobieraniu długich tekstów, musimy mieć na uwadze, że będziemy musieli zrobić pętlę po nodach.


var p = document.getElementsByTagName('p')[0]; //akapit z tekstem Pana Tadeusza
var tekst = '';
if (p.childNodes && p.childNodes.length>1) {
    for (i=0; i<p.childNodes.length; i++) {
        var tekst += p.childNodes[i].nodeValue;
    }
}

W nowszych przeglądarkach na szczęście ten problem nie istnieje.

innerHTML, textContent

Powyższe właściwości są bardzo precyzyjne, i bardzo często używanie ich może sprawiać niemałe problemy. Javascript udostępnia też dwie bardziej ogólne właściwości. innerHTML służy do pobierania i wstawiania do danego elementu kodu html.

Właściwość textContent służy do pobierania i wstawania do elementu tekstu:

    p.innerHTML = 'To nie jest <strong>mało</strong> ważny tekst';
    console.log(p.childNodes[1]) //wskazuje na drugie dziecko noda czyli <strong>
    console.log(p.childNodes[1].firstChild.textContent) //wskazuje tekst "mało"
    console.log(p.lastChild) //wskazuje słowo "tekst"