Framework-uri JavaScript: Manipularea DOM cu MooTools


Asa cum am promis, sa vedem cum putem manipula DOM-ul cu MooTools. Prima functie, $ (da, este vorba doar de simbolul dolar – cum spuneam data trecuta, acesta este un caracter valid, in JavaScript, pentru numele de variabile) este oarecum echivalenta cu document.getElementById. Insa, daca va hotarati sa manipulati DOM-ul cu MooTools, este absolut necesar sa folositi $ si nu document.getElementById, si iata de ce: toate browserele non-IE permit modificarea prototipului obiectului Element, insa IE nu permite asta. Asa ca, folosind $, este returnata o instanta modificata de Element – ii sunt adaugate toate metodele si toti membrii care pentru celelalte browsere ar fi fost adaugate prin modificarea prototipului. In plus, $ accepta ca parametru nu numai id-ul unui element DOM, ci chiar si o instanta de Element care a fost sau nu obtinuta de MooTools. Daca instanta respectiva a fost obtinuta tot prin MooTools, este returnata elementul fara nicio modificare, altfel ii sunt adaugate proprietatile si metodele necesare. Ca sa intelegeti ce vreau sa spun:

<!-- HTML -->
<div id="gaseste-ma"></div>
var e = document.getElementById('gaseste-ma');
// Element.grab ar trebui sa existe numai
// daca elementul a fost modificat de MooTools
alert($type(e.grab)); // non-IE -> function, IE -> false
e2=$(e);
// efectueaza modificari numai daca browser-ul este IE
alert($type(e2.grab)); // function, in TOATE browserele
alert(e2 == e); // true, deci este aceeasi instanta
// de acum inainte, orice $ aplicat lui e nu mai are niciun efect
e3=$(e2);
alert($type(e3.grab)); // function
alert(e3 == e);
alert(e3 == e2); // in continuare, aceeasi instanta

Pentru a selecta colectii de elemente, exista functia $$ care primeste oricati parametri ce pot fi: string, si atunci sunt selectate toate elementele care au tag-ul egal cu acel string; un element deja obtinut din DOM, sau o colectie/un vector de elemente din DOM (obtinute sau nu cu MooTools). Aceasta functie intoarce un vector cu toate elementele gasite/transmise ca parametri.

Spuneam ca jQuery are un motor de selectie a elementelor foarte puternic, care permite selectia de elemente cu sintaxa CSS2/3. (De altfel, si noi ne-am chinuit sa scriem o functie getElementsByClassName.) Si pentru MooTools exista un modul oficial, Selectors, care permite ca parametrii de tip string transmisi lui $$ sa fie nu doar tag-uri, ci selectori CSS2/3. De exemplu:

// selecteaza toate ancorele din elementele strong,
// care elemente strong sunt descendenti directi ai unui paragraf
// precum si elementele cu id-urile e1 si e2
$$('p > strong a', '#e1', $('e2'));
// selecteaza toate ancorele care fac legaturi catre pagini externe
$$('a[href^=http://]','a[href^=https://]');

Pentru exemplele urmatoare o sa consider ca a fost inclus si modulul Selectors.

$ si $$ considera ca radacina document. Daca vrem ca selectia sa considere ca radacina un alt element, MooTools adauga lui Element cateva metode (toate aceste metode au ca radacina elementul a caror metoda sunt, si returneaza null daca nu gasesc nicio potrivire):

  • Element.getElement primeste ca parametru un string care reprezinta un selector CSS si intoarce primul element care se potriveste
  • Element.getElements primeste ca parametru un selector CSS si intoarce toate elementele care se potrivesc
  • Element.getElementById primeste ca parametru un id de element si intoarce elementul cu id-ul respectiv.

Pentru a itera printre descendentii unui element, exista urmatoarele metode (Atentie, MooTools ignora elementele de tip text – mai ales din cauza ca IE6 nu ia in considerare elementele de tip text care contin doar spatii albe, in vreme ce IE7+ si browserele non-IE le iau in considerare):

  • Element.getPrevious, Element.getNext selecteaza primul frate aflat la stanga/dreapta elementului curent; poate primi optional un singur argument – un selector – si atunci intoarce primul frate din stanga/dreapta care se potriveste
  • Element.getAllPrevious, Element.getAllNext se comporta la fel ca Element.getPrevious/Element.getNext, numai ca intoarce toti fratii din stanga/dreapta care se potrivesc
  • Element.getFirst, Element.getLast la fel ca Element,getPrevious/Element.getNext, numai ca intoarce primul/ultimul descendent direct
  • Element.getParent – la fel, numai ca intoarce primul stramos
  • Element.getParents – la fel, numai ca intoarce un vector cu toti stramosii
  • Element.getChildren – la fel, numai ca intoarce elemente dintre descendentii directi ai elementului

In plus, mai exista o metoda Element.hasChild, care primeste un id de element sau o referinta de element si intoarce true sau false dupa cum elementul cu id-ul dat ca parametru, sau referinta la un element data ca parametru, este sau nu descendent direct al elementului, si o metoda Element.match care intoarce true sau false dupa cum elementul este acelasi cu argumentul transmis (daca s-a transmis o referinta de element) sau se potriveste cu selectorul CSS transmis ca parametru (atentie, pentru a functiona trebuie un singur selector, de exemplu: e.match('.myClass') intoarce true daca e are clasa myClass, insa e.match('p > .myClass') intoarce false, chiar daca e are clasa myClass si este descendent direct al unui paragraf), si false altfel.

Cat despre modificarea elementelor din DOM, avem la dispozitie alte cateva metode:

  • Element.get si Element.set sunt oarecum mai complicate, intrucat lucreaza cu proprietati si atribute generice ale elementelor (vezi documentatia), insa de retinut sunt Element.get('text'), Element.set('text', textNou) care intoarce textul continut in element/seteaza continutul elementului la textul transmis ca parametru, si Element.get('html'), Element.set('html', htmlNou) care lucreaza de fapt cu innerHTML (sau putem fi folosit direct innerHTML). Element.get('tag') intoarce numele tagului elementului (insa nu se poate modifica numele tagului unui element)
  • Element.getProperty, Element.setProperty, Element.removeProperty intoarce/seteaza/sterge o proprietate a unui element (de obicei, atributele elementelor). De exemplu: $$('img')[0].getProperty(’src’) intoarce sursa primei imagini din document, $('link').set('href','/') seteaza referinta ancorei cu ID-ul link, iar $('link').removeProperty('rel') sterge atributul rel al ancorei cu id-ul link
  • Element.getProperties primeste ca parametri un numar arbitrar de stringuri si intoarce un obiect in care cheile au valorile proprietatilor cautate, iar valorile sunt chiar valorile lor. De exemplu, pentru <a id="link" href="/" title="Home" rel="me"></a>, $('link').getProperties('id','href','title','rel') ar intoarce urmatorul obiect: {id: "link", href: "/", title: "Home", rel: "me"}
  • Element.setProperties primeste ca parametru un obiect de genul celui de mai sus, seteaza toate proprietatile la valorile respective, si intoarce chiar elementul
  • Element.removeProperties primeste ca argumente stringuri, ca si Element.getProperties, numai ca are ca efect stergerea proprietatilor si intoarce ca rezultat elementul

Daca vrem sa lucram cu clasele elementelor (clasele CSS, nu clasele din OOP), exista 3 metode:

  • Element.hasClass, care intoarce true daca elementul are printre clasele sale, clasa transmisa ca parametru
  • Element.addClass, care adauga clasa transmisa ca parametru celorlalte clase deja existente
  • Element.removeClass, care sterge clasa transmisa ca parametru dintre clasele deja existente
  • Element.toggleClass, care adauga clasa transmisa ca parametru daca elementul nu avea clasa respectiva, respectiv sterge clasa transmisa ca parametru daca elementul avea deja clasa respectiva, dintre clasele deja existente. Efectul este echivalent cu o instructiune de genul: e.hasClass('myClass')?e.removeClass('myClass'):e.addClass('myClass'); (vezi operatorul ternar ?:)

Mai exista si doua metode speciale, Element.store si Element.retrieve, care permit stocarea/obtinerea de date arbitrare intr-un element din DOM (si vom vedea intr-un articol viitor cum este folosita aceasta caracteristica pentru a ne usura munca). Element.store primeste ca parametri o cheie si valoarea de stocat, iar Element.retrieve intoarce valoarea stocata pentru cheia transmisa ca parametru.

Adaugarea de noi elemente in DOM incepe cu crearea unui element, care este simpla: se creaza o noua instanta de Element, constructorul primind doi parametri: primul, tag-ul elementului nou creat, iar al doilea, optional, un obiect care sa contina ca si chei, atributele elementului sau una dintre valorile: text, html, styles, events (pentru a seta continutul elementului/innerHTML la o anumita valoare, sau a seta proprietatile CSS la anumite valori – daca exista cheia styles, valoarea sa nu trebuie sa fie string, ci un obiect cu chei dintre atributele css – sau pentru a atasa ascultatori la evenimente – la fel si pentru events, daca aceasta cheie este prezenta, valoarea sa trebuie sa fie un obiect ale carui chei sa fie tipurile de ascultatori atasati, iar valorile lor sa fie functiile care trateaza evenimentele), iar ca valori, valorile acestora. Astfel, pentru a crea o noua ancora, putem proceda astfel:

var ancora = new Element('a',{
    href: '/',
    title: 'Prima pagina',
    'class': 'myClass',
    text: 'Prima pagina',
    'styles': {
        color: '#F00',
        'text-decoration': 'none
    },
    'events': {
        click: function() {
            alert('La revedere');
        }
    }
});

Cat despre adaugarea efectiva a elementelor in ierarhia DOM, exista o multime de metode, care acopera cea mai mare parte a nevoilor pentru cazurile de utilizare comune:

  • Element.inject primeste ca prim parametru un element (sau un id de element) si are ca efect adaugarea elementului curent, acelui element. Daca al doilea parametru lipseste, elementul este adaugat ca descendent direct, la sfarsitul tuturor descendentilor celui de-al doilea element. Acelasi lucru se intampla si daca al doilea parametru este ‘bottom’. Celelalte trei valori posibile pentru al doilea parametru sunt: ‘top’ – elementul este adaugat ca prim descendent direct, ‘before’ – elementul este adaugat ca frate in stanga celui de-al doilea element, si ‘after’ – elementul este adaugat ca frate in dreapta celui de-al doilea element.
  • Element.grab functioneaza asemanator lui Element.inject, dar rolurile sunt inversate: elementul curent are rolul de parinte, si elementul transmis ca parametru este adaugat lui ca descendent. De aceasta data, al doilea parametru nu poate fi decat ‘top’ sau ‘bottom’ – elementul de inserat va fi primul sau ultimul descendent direct.
  • Element.adopt functioneaza ca Element.grab, insa este posibil sa se adauge mai multe elemente deodata. Primeste un numar arbitrar de parametri care pot fi id-uri de elemente, instante de Element, sau vectori de id-uri/instante de elemente. Nu exista al doilea parametru (de altfel, primind un numar aleator de parametri, nu are sens notiunea de ‘unde inserez elementul’ – care dintre elemente?)
  • Element.wraps este foarte interesant: primeste ca parametru un id/o referinta de element si are urmatorul efect: elementul curent va avea ca unic descendent direct elementul transmis ca parametru, si il va inlocui pe acesta in locul unde era inainte in DOM (practic, incapsuleaza vechiul element)
  • Element.appendText functioneaza ca Element.grab, doar ca parametrul este de tip string, iar elementul creat este un nod de tip text.
  • Element.replaces primeste ca parametru un id/un element si are ca efect inlocuirea elementului transmis ca parametru, cu elementul curent
  • Element.clone creaza o copie a elementului curent. Daca tineti minte (Manipularea DOM cu JavaScript), Element.cloneNode avea ca efect si copierea atributului id in clona, ceea ce ducea la doua elemente diferite cu acelasi id. De aceasta data, Element.clone primeste doi parametri de tip boolean, ambii optionali. Daca primul parametru este true, in clona vor fi create copii ale tuturor descendentilor elementului curent (si implicit, aceasta valoare este true), altfel va fi clonat doar elementul, dar nu va avea niciun descendent. Al doilea parametru, implicit false, spune daca ar trebui sa nu copiat si id-ul. Implicit fiind false, nu vor exista probleme cu acelasi id la doua elemente diferite.

Cat despre distrugerea de elemente, avem la dispozitie urmatoarele metode:

  • Element.dispose scoate un element din DOM, dar nu il distruge – acesta poate fi modificat si/sau reinserat oriunde in DOM.
  • Element.empty distruge toti descendentii elementului (nu si elementul)
  • Element.dispose distruge complet atat elementul, cat si toti descendentii sai

In clasa Element mai exista doar doua metode despre care nu am discutat inca (nu au legatura cu DOM, insa ne vor folosi la AJAX):

  • Element.toQueryString parcurge toti descendentii elementului, si creaza un query string pe baza elementelor de tip input: casute text, checkbox-uri, butoane radio si liste de selectie, si a valorilor acestora.
  • Element.getSelected are sens numai pentru elementele de tip select; returneaza intotdeauna un vector care contine elementele DOM (nu valorile, ci elementele de tip option) care sunt selectate (poate fi vorba de un multi-select).

Data viitoare vom vedea cum ne ajuta MooTools atunci cand avem de-a face cu evenimente in JavaScript.

, , , , , ,