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 Tagi, ale także tekst w nich zawarty, ich atrybuty itp.
Przykładowo w kodzie:
<p> Jakiś tekst </p>
nodem jest zarówno akapit jak i nodem jest tekst, który się w nim znajduje.
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.
Aby pobrać noda, możemy skorzystać z dwóch podstawowych metod oferowanych przez JS - getElementById i getElementsByTagName. Pierwsza z nich zwraca odwołanie do elementu, który posiada dany ID, druga zaś pobiera nam wszystkie elementy danego typu w postaci tablicy.
Zastosowanie tych metod jest bardzo proste:
<input type="button" id="guzik" value="ok" />
<script type="text/javascript">
var g = document.getElementById('guzik');
alert(g.value); //korzystamy z okienka dialogowego by wypisać value pobranego guzika
</script>
Zauważyłeś, że pobrany element podstawiam po zmienną?
Gdy pobieram element z dokumentu, zmuszam przeglądarkę do przejścia po wszystkich elementach strony i wyszukania szukanego elementu.
Dzięki temu, że pobrany element podstawiam pod zmienną, przy kolejnej operacji (w naszym przykładzie odczycie wartości value) znacząco zyskuję
na szybkości, gdyż przeglądarka nie musi kolejny raz szukać.
W przypadku powyższego skryptu różnicy nie zauważymy. Ale w przypadku większych skryptów różnica może już być znacząca.
Kolejna omawiana metoda to getElementsByTagName:
<table id="tabelka">
<tr><td>1</td>2<td>3</td></tr>
<tr><td>4</td>5<td>6</td></tr>
</table>
<script type="text/javascript">
var tab = document.getElementById('tabelka');
var td = tab.getElementsByTagName('td'); //pobieramy wszystkie td z tabeli
for (x=0; x<td.length; x++) { //pętla po wszystkich td
td[x] = ... //tutaj czarujemy z każdym td
}
</script>
Do pobrania elementu o danej klasie css, skorzystamy z metody getElementsByTagName. Wystarczy pobrać elementy, po czym wykonać po nich pętlę, zwracając te z wymaganą klasą:
function getElementsByClass(element, tagName, className) {
var pasujace = [];
var dzieci = element.getElementsByTagName(tagName);
for (var i = 0; max = dzieci.length; i < max; i++) {
if ((new RegExp(className)).test(dzieci[i].className)) {
pasujace.push(dzieci[i]);
}
}
return pasujace;
}
Czemu podczas definiowania pętli for podstawiliśmy długość kolekcji pod dodatkową zmienną max? 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ć :)
var body = document.getElementsByTagName('body')[0];
var superTabelki = getElementsByClass(body, 'table', 'super'); //pobieramy tabele z body, które mają klasę "super"
var czerwone = getElementsByClass(body, '*', 'czerwone'); //pobieramy wszystkie tagi z body, które mają klasę "czerwone"
W nowoczesnych przegladarkach istnieją też o wiele bardziej pomocne metody, które pobierają elementy z danymi ID lub klasami CSS - zupełnie jak w jQuery.
Pierwsza z omawianych metod to querySelector(), która 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 klasą .wazne
var element = document.querySelector('.wazne, .inne');
Druga metoda to querySelectorAll(), która ma bardzo podobne działanie, z tą różnicą, że pobiera wszystkie pasujące elementy:
//pobieramy wszystkie elementy, które mają klasy .wazne, .inne oraz element z id=main
var elementy = document.querySelectorAll('.wazne, .inne, #main');
if(elementy) {
var element = elementy[0];
for (i=0; max = elementy.length; i<elementy.length; i++) {
console.log(elementy[i]);
}
}
Wszyscy ci, którzy przyzwyczaili się do używania jQuery, mogą pokusić się do napisania małego skryptu, który ułatwi im pracę z powyższymi metodami:
function $(selector, el) {
if (!el) {el = document;}
return el.querySelectorAll(selector);
}
alert( $('.z_kolorami')[0].id );
Dodatkowe informacje na ten temat znajdziecie tutaj
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:
Znając hierarchiczne położenie obiektów, możemy w łatwy sposób się po nich przemieszczać:
var p = document.getElementById('paragraf');
p.parentNode //wskazuje na rodzica czyli na body - jeżeli dany paragraf jest w body
p.childNodes[1] //wskazuje na drugie dziecko noda czyli <strong>
//jeżeli nasz akapit będzie pierwszy na stronie to...
document.firstChild.childNodes[1].firstChild.nodeValue //pobranie tekstu To jest
document.firstChild.childNodes[1].parentNode.childNodes[1].firstChild.nodeValue //To samo co wyżej - czemu?
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.
| Właściwość | Opis |
|---|---|
| nodeName | nazwa node |
| nodeValue | wartosć node (tylko dla nodów tekstowych) |
| nodeType | typ node - zobacz niżej |
| parentNode | rodzic (parent), jeżeli istnieje |
| childNodes | lista (array) 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 |
| Number | Opis |
|---|---|
| 1 | element HTML |
| 2 | atrybut elementu |
| 3 | tekst |
| 8 | zawartość HTML |
| 9 | kodument |
| 10 | dokument definicji |
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 podswietl_wiersze(id) {
var tab = document.getElementById(id);
var td = tab.getElementsByTagName('td');
for (x=0; x<td.length; x++) {
td[x].onmouseover = function() {
this.parentNode.className = 'podswietlone'; //ustawiamy klase hover dla TR
}
td[x].onmouseout = function() {
this.parentNode.className = '';
}
}
}
podswietl_wiersze('tabelka')
| 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 |
Jak powiedziałem na początku, 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' ) {
alert( '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:
alert( '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.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ą).
Spróbujmy teraz odczytać wartość noda tekstowego:
alert( 'Wartość tekstu: ' + p[3].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.
Spacerując po dworzu czasami możesz trafić na pewne problemy. A to się człowiek przewróci, a to dostanie niechcący w "twarz". W JS niestety też się takie rzeczy zdarzają.
Rozpatrzmy pewien kod:
<p> <span>Ala ma kota, a kot ma Alę.</span> <strong>Ala go kocha</strong> <span>a kot ją wcale...</span> </p>
Jak pobrać powyższe znaczniki? Bardzo prosto:
var p = document.getElementsByTagName('p')[0]; //p - nasz akapit
p.firstChild //wskazuje na span
p.childNodes[1] //wskazuje na strong
p.childNodes[2] //wskazuje na drugi span
Okazuje się jednak, że powyższy kod zadziała tylko w przeglądarce IE. Dzieje się tak dlatego, ponieważ między kolejnymi znacznikami występują białe znaki (tabulacja, spacje, czy łamanie linii), które przecież też są rodzajem tekstu, a więc i pełnoprawnymi nodami. Niestety IE spacerując po drzewie dokumentu pomija nody zawierające tylko białe znaki, co owocuje kolejną niekompatybilnością z inymi przeglądarkami.
"No to jesteśmy załatwieni".
Nie do końca. Wyjść jest kilka. Przede wszystkim zawsze możemy skorzystać z metod getElementById lub getElementsByTagName - to pewniaki. Drugim wyjściem jest usunąć z drzewa dokumentu wszystkie nody z białymi znakami:
function usunBialeZnaki(element) {
for (x=0; x<element.childNodes; x++) {
if (element[x].nodeType==1 && element[x].test(/^\s\n\r/)) {
element[x].parentNode.removeChild(element[x])
} else if (element[x].nodeType==3 && element[x].chasChildNodes()) {
usunBialeZnaki(element[x])
}
}
}
usunBialeZnaki(document);
Więcej na ten temat możesz dowiedzieć się tutaj: http://developer.mozilla.org/en/docs/Whitespace_in_the_DOM.