Manipularea DOM cu JavaScript


Daca acum ceva vreme JavaScript nu era privit ca un limbaj de programare foarte puternic, si cei mai multi programatori JavaScript foloseau limbajul in cadrul interfetelor web doar pentru a transmite alerte utilizatorilor, pentru a schimba imaginile atunci cand mouse-ul intra peste o imagine (nu exista in CSS pseudo-clasa :hover) sau a deschide ferestre pop-up, popularitatea sa si modul de folosire a evoluat in momentul in care motoarele de redare din spatele browserelor au implementat DOM. Aplicatii web 2.0 ca Gmail, flickr, delicious au aratat lumii intregi cat de multe se poate face cu JavaScript, folosind tehnici ca DHTML si AJAX.

Sa precizam mai intai faptul ca DOM nu este specific JavaScript; este de fapt un model de reprezentare a HTML si CSS, independent de o anumita platforma sau de un limbaj de programare.
Exista 3 nivele de DOM, numerotate de la 1 la 3. DOM 1 este implementat de cele mai folosite browsere din ziua de azi (probabil ca mai exista persoane care mai folosesc IE5 sau o versiune de Netscape mai mica de 7, dar procentul acestora este foarte, foarte mic). O implementare completa de DOM 2 nu exista in acest moment in niciunul din motoarele de redare, dar toate mai putin Trident (care se afla in spatele Internet Explorer) au mare parte din specificatii implementate. Cat despre DOM 3, cu toate ca este o recomandare a W3C din 2004, doar o mica parte din specificatii sunt implementate in browsere, si numai in cele mai recente dintre versiunile lor (iar Gecko, motorul de redare din spatele Firefox, pare sa aiba cea mai mare parte dintre acestea comparativ cu celelalte).
Manipularea DOM-ului poate fi impartita in 3 tipuri:

  • preluarea elementelor din DOM in vederea modificarii acestora
  • crearea de noi elemente si adaugarea acestora in ierarhia DOM
  • stergerea de elemente

Sa incepem cu primul tip de manipulare: preluarea si modificarea elementelor. Desigur, in mod normal va trebui ca mai intai sa preluam unul sau mai multe elemente, si dupa ce am facut acest lucru, sa modificam elementul sau colectia de elemente.
Elementul din ierarhia DOM cel mai folosit pentru manipularea DOM este document, dar urmatoarele 3 metode, cele mai folosite pentru a prelua unul sau mai multe elemente din DOM, sunt disponobile si oricarui alt element din DOM, descendent al lui document. Ca o nota, elementul/elementele returnate de aceste metode fac parte din descendentii elementului a carui metoda se apeleaza. De exemplu:

//div_element este un element DOM
// preluat inainte
// de exemplu, prin document.getElementById
div_element.getElementsByTagName('a');

va returna toate ancorele (elementele cu tag-ul a), care sunt descendenti ai lui div_element.
Asadar, cele 3 metode de care vorbeam, sunt:

  • getElementById(id) preia elementul din ierarhia DOM, care are id-ul id; id este de tip string
  • getElementsByName(name) preia toate elementele din DOM care au atributul DOM name egal cu atributul de tip string transmis ca parametru, si returneaza o colectie de elemente prin care putem itera ca si cand ar fi vorba de un vector
  • getElementsByTagName(tag_name) preia toate elementele din DOM care au tag-ul tag_name, si returneaza o colectie, la fel ca metoda de mai sus

Din nefericire, nu exista o metoda de a prelua elementele care au o anumita clasa (cel putin nu exista o solutie nativa, care sa functioneze in toate browserele – cu toate ca Firefox are metoda getElementsByClassName), desi ar fi foarte utila (ganditi-va ca putem adauga o anumita clasa unor elemente care se comporta asemanator, si atunci am putea itera prin toate elementele dintr-o colectie intoarsa de getElementsByClassName, adaugand functionalitatea dorita). Putem insa implementa noi insine o functie care sa faca ce ne dorim. Urmatorul exemplu este din cartea Accelerated DOM Scripting with Ajax, APIs, and Libraries, scrisa de Jonathan Snook, o carte excelenta pe care o recomand oricui doreste sa invete JavaScript pentru manipularea DOM (daca cititi aceasta carte, o sa intelegeti si cat de mult usureaza framework-urile JavaScript munca unui programator):

function getElementsByClassName(node,classname) {
var a = [];
var re = new RegExp('(^| )'+classname+'( |$)');
var els = node.getElementsByTagName("*");
for (var i=0,j=els.length; i>j; i++)
if (re.test(els[i].className))
a.push(els[i]);
return a;
}

Probabil ca aceasta functie este mai lenta decat metoda nativa a elementelor (in browserele pe care o au), deci atunci cand vrem sa o folosim, putem verifica inainte existenta metodei native:

var e = document.getElementById('my_element');
var elements = null;
if (Element.prototype.getElementsByClassName)
elements = e.getElementsByClassName('my_class');
else
elements = getElementsByClassName(e, 'my_class');

Poate va intrebati de ce nu putem adauga functia de mai sus, ca metoda a prototipului elementelor, cum invatasem in articolul Obiecte in JavaScript. Raspunsul e simplu: Internet Explorer nu intoarce un Element, ci un Object, si niciodata nu ar trebui sa adaugam metode obiectului Object (ganditi-va ca toate celelalte obiecte sunt derivate din Object, si atunci toate obiectele ar avea aceasta metoda, ceea ce nu ar trebui sa se intample vreodata). O metoda de a adauga functia ca metoda, care sa functioneze si in IE, este prezentata in articolul Emulating Prototyping of DOM Objects in Internet Explorer. In plus, toate framework-urile JS din ziua de azi au adaugata o metoda asemanatoare.
Acum ca am aflat cum putem prelua elementele din DOM, sa vedem cum le putem modifica proprietatile. Pentru a modifica continutul unui element, se folosesc doua proprietati pe care le are orice element:

  • innerHTML, daca vrem sa adaugam si elemente HTML in interiorul elementului
  • nodeValue daca vrem sa adaugam text (entitatile HTML sunt automat codate, asa ca de exemplu daca folosim e.nodeValue="ceva", in e.innerHTML vom avea de fapt "<strong>ceva</strong>")

Pentru a modifica atributele unui element (id, style, class, etc.), putem folosi element.nume_atribut sau putem itera prin toate atributele elementului folosind vectorul element.attributes[] (Atentie, cuvantul class este cuvant rezervat in JavaScript, deci pentru a modifica numele clasei/claselor unui element trebuie sa folosim element["class"] in toate browserele mai putin IE; ca de obicei, IE se comporta diferit, si pentru a modifica clasele in acest browser trebuie sa folosim proprietatea className: element["className"]). Mai mult, putem modifica oricare proprietate din CSS, folosind numele proprietatilor din CSS, transformate din “hyphenated-form” (border-width) in camelCase form (borderWidth), ca proprietati ale lui element.style. De exemplu, daca am vrea sa setam pentru toate link-urile dintr-o pagina, culoare textului rosie si textul italic, si sa le adaugam clasa special am folosi urmatoarea bucata de cod:

var anchors = document.getElementsByTagName(&quot;a&quot;);
for (var i=0, total=anchors.length; i<total; i++) {
anchors[i].style.color = "#FF0000";
anchors[i].style.textStyle = "italic";
anchors[i]["class"] = "red";
anchors[i]["className"] = "red"; //IE
}

Desigur, uneori vom avea nevoie sa folosim descendentii unui anumit element; pentru aceasta exista un vector childNodes pentru fiecare element din DOM, care returneaza descendentii directi ai elementului. Daca ne intereseaza numai primul sau ultimul element, este mai simplu sa folosim proprietatile firstChild/lastChild. Daca ne intereseaza parintele unui element, folosim parentNode, iar daca ne intereseaza vecinii unui element (copiii parintelui elementului, care se afla imediat inainte sau imediat dupa copil), folosim previousSibling/nextSibling.
Desigur acestea nu sunt singurele metode disponibile (despre o parte din proprietati vom vorbi imediat, intrucat au legatura cu inserarea/eliminarea elementelor din DOM, iar de o alta parte data viitoarem cand vom vorbi de evenimente), iar cateva liste destul de complete cu metodele si proprietatilor elementelor din DOM si ale lui document, gasiti aici.
Sa mergem mai departe si sa vedem cum cream si adaugam noi elemente in DOM. Crearea de elemente este de fapt foarte simpla: se apeleaza document.createElement(tagName), apoi se seteaza atributele la fel ca mai sus. Putem desigur crea nu doar elemente, ci si comentarii (cu ajutorul document.createComment(commenttext), dar in general nu se foloseste, din moment ce comentariile HTMl nu sunt afisate utilizatorului), sau noduri text: document.createTextNode(text). De notat ca intr-un document HTML, si spatiile albe consituie noduri in DOM, de tip text. Mai corect spus ar fi ca ar trebui sa fie, insa IE nu introduce aceste noduri in DOM, si este posibil sa obtinem comportament diferit in functie de fiecare browser, de exemplu la traversarea copiilor unui element:

<div id="element">
<p>Paragraf</p>
</div>
// in JavaScript
var div = document.getElementById(&quot;element&quot;);
var descendants = div.childNodes;
// pentru Firefox, descendants = ["\n", HTMLElement p, "\n"]
// pentru IE, descendants = [HTMLElement p]
alert(descendants.length); // IE -> 1, orice browser in afara de IE -> 3

Dupa ce am creat elementul, il putem insera in DOM, cu ajutorul metodei

element.insertBefore(newElement, targetElement);

Daca targetElement este null, newElement va fi inserat ca ultim copil al lui element. Nu va lasati inselati de numele metodei, pentru ca de fapt este posibil sa inseram un nou element intr-un alt element, oriunde printre descendentii sai directi. Cateva exemple:

// parent este un element preluat inainte,
// newElement este un nou Element creat inainte
// inserare ca prim copil:
parent.insertBefore(newElement, parent.firstChild);
// inserare ca al treilea copil
parent.insertBefore(newElement, parent.childNodes[3]);
// inserare ca ultim copil
parent.insertBefore(newElement); // al doilea parametru e null
// inserare dupa elementul tinta
parent.insertBefore(newElement, targetElement.nextSibling);
// de fapt, am inserat inaintea urmatorului &quot;frate&quot; al lui targetElement

Si pentru a acoperi toate cazurile de inserare in DOM, singurul scenariu care a mai ramas sa fie vazut, este inlocuirea unui element cu un altul:

element.replace(newElement, oldElement);

Daca inseram in DOM elemente pe care le-am preluat tot din DOM si nu le-am creat noi, la inserarea acestora, vor fi mutate in noul loc din DOM; daca ceea ce voiam sa facem era sa copiem elementul, exista urmatoarea metoda:

element.cloneNode(deepBoolean);

care creaza o clona a lui element (daca deepBoolean e setat la true, vor fi clonati si copiii lui element). Atentie insa daca element avea setat atributul id: clonarea va copia si acest atribut, deci inainte sa il inseram in DOM, ar fi o idee buna sa-i schimbam ID-ul, altfel document.getElementById e posibil sa nu se comporte cum ne-am fi dorit (daca exista mai multe noduri cu acelasi id, va fi returnat primul dintre acestea).
A mai ramas sa vorbim despre stergetea unui element din DOM. Este destul de simplu:

parent.removeChild(childreference);

va elimina referinta la copil dintre descendentii direct ai lui parent; referinta copilului nu va fi distrusa, ci poate fi refolosita (putem modifica copilul si il putem insera altundeva in DOM). Daca avem elementul si nu parintele, putem folosi urmatorul cod:

element.parentNode.removeChild(element);

Data viitoare, vom vorbi despre evenimente in JavaScript.

, , , , , , , , ,

blog comments powered by Disqus