A Dokumentum Objektum Modellről korábban már volt szó érintőlegesen. Ebben a fejezetben kicsit részletesebben foglalkozunk vele, nagy vonalakban áttekintve, milyen lehetőségeket biztosít ez a szabvány a HTML oldalon lévő elemek manipulálására.
Korábbi fejezetekben láthattuk, hogy a Dokumentum Objektum Modell (DOM) nem más, mint az oldal szerkezetéért felelős HTML elemek JavaScript objektum reprezentánsai, amik ugyanolyan fastruktúrába szervezve a megjelenítés alapját képezik, és amin keresztül a dokumentum programozhatóvá válik. Magyarán szólva a DOM nem más, mint az oldal – általánosabb fogalmazva minden fastruktúra, pl. HTML és XML – szerkezetének programozási felülete.
Azon túl, hogy a DOM alapvető felépítését és funkcióját megnéztük, eddig nem sok műveletével ismerkedtünk meg. Láttuk, hogy segítségével hogyan választható ki egy elem az azonosítója alapján (document.getElementById), hogyan lehet egy elem belső HTML tartalmát kiolvasni vagy megváltoztatni (innerHTML), valamint hogyan lehet lekérdezni egy objektumról, hogy milyen HTML elem (tagName).
A DOM természetesen ennél sokkal többre képes. Rajta keresztül az egész dokumentum a legkisebb részletig tetszőlegesen bejárható és manipulálható. Ebben a fejezetben a DOM szolgáltatásai közé nyerhetünk bepillantást a teljesség igénye nélkül. Látni fogjuk, hogy hogyan lehet még elemeket kiválasztani, egy elem esetében milyen fontosabb tulajdonságokat érdemes ismernünk, és hogyan kezelhetők egy elem attribútumai, hogyan lehet a dokumentum fastruktúrájában közlekedni, milyen módszerek vannak új elemek létrehozására, törlésére és módosítására.
A DOM-mal kapcsolatos ismeretek demonstrálására az alábbi HTML dokumentumot fogjuk a következőkben használni:
<!doctype html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <p id="par1">Bekezdés.</p> <p class="aktiv">Még egy bekezdés.</p> <ul> <li>első</li> <li class="aktiv">második</li> </ul> <form name="form1"> <input type="radio" name="r1" value="elso"> <input type="radio" name="r1" value="masodik"> </form> </body> </html>
Ahogy azt már korábban említettük, a böngészőkben elérhető DOM működését több szabvány határozza meg. A DOM alapját az ún. DOM Core szabvány jelenti, amelyben egy csomópontokból álló általános fastruktúra ábrázolásához és működtetéséhez szükséges információk és műveletek vannak leírva. A HTML dokumentum egy speciális elemekből álló fastruktúra, speciális többletfunkcionalitását a HTML DOM szabvány határozza meg. A következőkben DOM alatt a HTML DOM-ot értjük.
A DOM nem más, mint csomópontok fája. Sokféle típusú csomópont létezik, és ez meghatározza lehetséges tulajdonságaikat és metódusaikat is. Némelyik típusú csomópontnak lehetnek további gyerekei, mások csak levélelemként szerepelhetnek. A teljesség igény nélkül tekintsük át a fontosabb csomóponttípusokat:
Az egyes csomóponttípusok viszonyát az alábbi ábra mutatja be. Jól látható, hogy az általános csomópont (Node) interfészt hogyan specializálják a belőle származó további interfészek. A DOM Core definiálja a Document interfészt, a HTML DOM ezt bővíti tovább a HTML-beli specialitásokkal a HTMLDocument interfészben. Ugyanígy az általános Element interfésznek a HTMLElement a HTML-beli kiterjesztője, és ebből származnak az egyes HTML elemeknek megfelelő speciális interfészek: HTMLBodyElement, HTMLParagraphElement, HTMLDivElement, stb. A szöveges csomópontot (Text) megelőzi egy általánosabb CharacterData interfész, ugyanis a HTML-beli megjegyzéseknek is külön interfésze van (Comment), amely ebből származik.
Ha egy dokumentum felépítését tekintjük, akkor egy dokumentum általában elemekből és szöveges csomópontokból áll. (Ennél jóval többféle csomópontot definiál a szabvány, de ez számunkra csak feleslegesen bonyolítaná a képet). Az elemek általában további elemeket és szöveges csomópontokat tartalmazhatnak, a szöveges csomópontok viszont kizárólag levélelemek lehetnek. A fentebb bemutatott példadokumentum esetén a DOM fa a következőképpen néz ki:
Látható, hogy minden egyes HTML tag külön DOM elemként jelenik meg, a szöveges információk (az újsor karakterek is) pedig szöveges csomópontként szerepelnek a fában.
A DOM programozása során elsődleges célunk az, hogy kiválasszuk azokat az elemeket, amelyekkel valamilyen módon dolgozni szeretnénk. Erre korábban már megismertünk egy metódus, a document.getElementById függvényt, amely azonosító alapján keresi meg és adja vissza az elemet. A gyakorlat azonban azt mutatja, hogy sokszor nem azonosító, hanem egyéb információk alapján szükséges kiválasztani az elemeket, így erre számos további lehetőségünk van. Az alábbi művelet egy része csak dokumentumszinten hívható meg, mások dokumentum- és elemszinten is használhatóak.
Dokumentumszintű kiválasztó metódusok:
A több elemet visszaadó metódusok ún. NodeList gyűjteményt adnak vissza, amely úgy viselkedik, mint egy tömb, de a tömbmetódusok nem érhetők el rajta, csupán length tulajdonságuk van. Feldolgozásuk csak a sima for ciklussal ajánlott.
//Dokumentumszintű kiválasztó metódusok document.getElementById('par1'); //<p id=?"par1">?Bekezdés.?</p>? document.getElementsByName('r1'); //NodeList[input, input] document.getElementsByTagName('p'); //NodeList[p#par1, p.aktiv] document.getElementsByClassName('aktiv'); //NodeList[p.aktiv, li.aktiv] document.querySelector('ul > li'); //<li>?első?</li>? document.querySelectorAll('ul > li'); //NodeList[li, li.aktiv] //Elemszintű kiválasztó metódusok var body = document.body; body.getElementsByTagName('p'); //NodeList[p#par1, p.aktiv] body.getElementsByClassName('aktiv'); //NodeList[p.aktiv, li.aktiv] body.querySelector('ul > li'); //<li>?első?</li>? body.querySelectorAll('ul > li'); //NodeList[li, li.aktiv] //Példa feldolgozás var aktivok = body.getElementsByClassName('aktiv'); for (var i = 0; i < aktivok.length; i++) { console.log(aktivok[i].tagName); } //=> P //=> LI
Elemek kiválasztásával közvetlenül érhetjük el a keresett elemeket. Mivel azonban a dokumentum egy fa, lehetőség van a dokumentum gyökerétől elindulva elérni az egyes csomópontokat. Ezt nevezzük a fa bejárásának. Fontos ezt is ismerni, hiszen előfordulhatnak olyan feladatok, amikor kiválasztással nem tudjuk a keresendő elemeket közvetlenül behatárolni, hanem csak egy adott elemtől az odáig vezető utat ismerjük. Faszerkezet lévén a bejárás során tudnunk kell a fában lefele haladni, azaz a gyerekelemeket lekérdezni, tudni kell fel felfele haladni, azaz a szülőelemre lépni, és tudni kell az adott szinten haladni, azaz a közös szülőhöz tartozó testvéreket elérni.
Gyerekelemek lekérdezésére a childNodes tulajdonság szolgál. Ez az adott elem alá tartozó összes csomópontot (elemeket és szöveges csomópontot egyaránt) visszaadja. Az első gyerekelemet a firstChild tulajdonsággal, az utolsót a lastChild tulajdonsággal kérhetjük le.
A szülőelemre egy adott csomópont parentNode tulajdonsága mutat.
A testvércsomópontok között a nextSibling és previousSibling tulajdonsággal tudunk előre, illetve visszafele haladni.
A fentiek null értéket adnak vissza, ha nincs megfelelő elem.
A bejárás során zavaró lehet, hogy a csomópontok között a szöveges csomópontok is megjelennek. Ezeket a csomópontok nodeType attribútuma alapján lehet kiszűrni. Az elemeknél ez a tulajdonság 1-es, míg szöveges csomópontoknál 3-as értékű.
Nézzünk néhány példát a fentiek alkalmazására:
//Út a <body> elemig document.childNodes; //NodeList[<!DOCTYPE html>, <html>?...html>?] document.childNodes[1]; //<html>?...?</html>? document.documentElement; //<html>?...?</html>? document.childNodes[1].firstChild; //<head>...</head> document.childNodes[1].firstChild.nextSibling; //#text document.childNodes[1].firstChild.nextSibling.nextSibling; //<body>...</body> //Vagy egyszerűbben document.body //Adott elem testvérelemeinek meghatározása function siblings(elem) { var testverek = []; var szulo = elem.parentNode; var gyerek = szulo.firstChild; while (gyerek !== null) { if (gyerek.nodeType ===1 && gyerek !== elem) { testverek.push(gyerek); } gyerek = gyerek.nextSibling; } return testverek; } //A par1 id-jú elem testvéreinek meghatározása var par1 = document.getElementById('par1'); console.log(siblings(par1)); //A legközelebbi adott nevű elem meghatározása function closest(elem, tag) { var szulo = elem.parentNode; tag = tag.toUpperCase(); while (szulo !== null && szulo.tagName !== tag) { szulo = szulo.parentNode; } return szulo; } //A rádiógomb formjának meghatározása var radio = document.getElementsByName('r1')[0]; console.log(closest(radio, 'form')); //Ezt éppenséggel lehet egyszerűbben is console.log(radio.form);
Ha kiválasztottunk vagy elértünk egy elemet, akkor a metódusain és tulajdonságain keresztül tudunk vele dolgozni. Ezeket a szabvány határozza meg, és függnek attól, hogy milyen típusú elemről van szó. Egy elem a tulajdonságait egyrészt mint csomópont, másrészt mint HTML elem kapja meg. Az előbbiről a DOM Core, az utóbbiról a HTML DOM szabvány gondoskodik. A csomóponti tulajdonságokról szóltunk részben fentebb, a HTML elem mivoltukból fakadó specialitásokkal pedig külön fejezetben foglalkozunk.
A kiválasztott elemnek lehetnek HTML attribútumai. Ezeknek a kezelésére a szabvány a következő műveleteket definiálja:
//A par1 id-jú paragrafus attribútumai var par1 = document.getElementById('par1'); par1.getAttribute('id'); //"par1" par1.setAttribute('data-forras', 'én'); par1.attributes; //[id, data-forras] par1.hasAttribute('data-forras'); //true par1.removeAttribute('data-forras'); par1.attributes; //[id]
A dokumentum módosításának következő lépése az új elemek létrehozása. Erre korábban már láttunk megoldást az innerHTML tulajdonság személyében, amelynél a szövegesen megadott HTML részletből a böngésző létrehozza a megfelelő DOM elemeket és azokat elhelyezi a dokumentumfa megfelelő részére. Az innerHTML a HTML5-tel kerül bele a szabványba.
A formálisabb utat elemek létrehozására a document.createElement() biztosítja. Ez egy dokumentumszintű metódus, paraméterként a létrehozandó elem nevét kell megadni. A metódus eredményeképpen a memóriában létrejön a megfelelő DOM elem. Ezt az elemet felruházhatjuk a szükséges attribútumokkal, beállíthatjuk bizonyos tulajdonságait, akár gyerekelemeket is csatolhatunk hozzá. Ha a memóriabeli elemet felkészítettük, akkor a dokumentum megfelelő pontjára kell beszúrni. Erre az alábbi elemszintű metódusok szolgálnak:
//Új bekezdés beszúrása a body végére var p = document.createElement('p'); p.innerHTML = 'Új bekezdés'; p.setAttribute('class', 'aktiv'); document.body.appendChild(p);
A dokumentum elemeit új elemek létrehozása nélkül is át tudjuk rendezni. Ha a létrehozásnál bemutatott három metódusnál (appendChild, insertBefore, replaceChild) az újelem egy fában már meglévő elemre mutat, akkor az először kivételre kerül a fából, majd az utasításnak megfelelően beszúrásra kerül. Így lehet elemeket mozgatni a fán belül.
//Lista első elemének a végére mozgatása var ul = document.getElementsByTagName('ul')[0]; var elsoLi = ul.firstChild.nextSibling; //A firstChild egy szöveges csomópont ul.appendChild(elsoLi);
Végül az utolsó módosítási lehetőség az elemek eltávolítása a fából. Erre a removeChild metódus szolgál, mely egy adott elem gyerekei közül távolítja el a paraméterként megadott csomópontot.
//A par1 id-jú bekezdés eltávolítása var par1 = document.getElementById('par1'); var szulo = par1.parentNode; szulo.removeChild(par1);
Természetesen speciális esetekben az innerHTML is használható egy vagy akár több elem törlésére is, ha üres szöveget adunk neki értékül.
//A body törlése document.body.innerHTML = '';
A tananyag az ELTE - PPKE informatika tananyagfejlesztési projekt (TÁMOP-4.1.2.A/1-11/1-2011-0052) keretében valósult meg.
A tananyag elkészítéséhez az ELTESCORM keretrendszert használtuk.