Manipulació del DOM

El DOM (Document Object Model) és una representació d'un document HTML com a una estructura d'arbre, on cada node és un objecte que representa una part del document. El DOM es crea quan es carrega una pàgina web, i és manipulable des de JavaScript gràcies a una sèrie d'operacions que permeten:

  • Afegir, modificar i esborrar qualsevol element o atribut HTML.
  • Canviar qualsevol estil CSS.
  • Reaccionar a un esdeveniment.
  • Crear nous esdeveniments.

Imaginem que tenim aquest senzill document:

<!doctype html> <html lang="en-US"> <head> <meta charset="utf-8" /> <title>Simple DOM example</title> </head> <body> <section> <img src="dinosaur.png" alt="A red Tyrannosaurus Rex" /> <p> A link to the <a href="https://www.mozilla.org/">Mozilla homepage</a> </p> </section> </body> </html>

Per poder manipular el DOM primer cal obtenir una referència a l'element i desar-la a una variable. Podem utilitzar Document.querySelector(), que demana un selector CSS com a paràmetre:

Per exemple:

const link = document.querySelector("a");

També tenim Document.querySelectorAll() per a obtenir una llista d'elements en format NodeList:

const paragraphs = document.querySelectorAll("p"); paragraphs.forEach((paragraph) => { // do something with it });

Tot i que amb aquests dos mètodes en tenim prou per fer queries, hi ha altres (menys flexibles) com Document.getElementById(), Document.getElementsByTagName(), Document.getElementsByClassName(), etc. També podem veure els elements fill amb la propietat children, que retorna una col.lecció HTMLCollection.

Si volem moure'ns cap a dalt, podem utilitzar la propietat parentElement o el mètode closest(selector). També ens podem moure cap als costats amb nextElementSibling i previousElementSibling.

Selectors CSS

Aquests són els selectors més habituals:

  • Tots: *
  • Etiqueta: head
  • Classe: .red
  • ID: #nav
  • Etiqueta i classe: div.row
  • Valor d'atribut: [aria-hidden="true"]
  • Fills d'un altre element: li a
  • Fills directes: li > a
  • Tots dos selectors: li, a

Pseudo-selectors:

  • Primer fill: :first-child
  • Últim fill: :last-child
  • Element amb hover o focus: :hover, :focus
  • Element clicat: :active
  • Enllaços no clicats o clicats: :link, :visited

Modificació

També podem afegir nous nodes al DOM. Si es volgués afegir un paràgraf a la secció seria així:

const sect = document.querySelector("section"); const para = document.createElement("p"); para.textContent = "We hope you enjoyed the ride."; sect.appendChild(para);

També podem esborrar un element de diferents formes:

sect.removeChild(para); para.remove(); para.parentNode.removeChild(para);

Una altra opció és utilitzar un template de l'html i clonar-lo al contingut.

<main class="container"> <h1>Template!</h1> </main> <template id="message"> <section> <h2 class="heading"></h2> <p class="text"></p> </section> </template>
const template = document.querySelector("#message"); const message = template.content.cloneNode(true); message.querySelector('.heading').textContent = 'A title'; message.querySelector('.text').textContent = 'Some text here'; const main = document.querySelector('main'); main.appendChild(message);

Atributs i propietats

Els elements tenen atributs i propietats:

  • Els atributs són les característiques dels elements que apareixen dins de l'etiqueta, i es defineixen a l'HTML. S'anomenen amb paraules separades per guions. Exemple: src, alt.
  • Les propietats defineixen el comportament intern i la funcionalitat d'un element, i es manipulen des de JS. S'anomenen amb camel case. Exemple: value o innerHTML.

Tenim Element.getAttribute(), Element.setAttribute(), Element.removeAttribute() i Element.hasAttribute() per a gestionar els atributs d'un element. Les propietats, en canvi, es manipulen com a propietats de l'objecte.

Podem tenir atributs que tenen una representació com a propietats. Per exemple, id és un atribut i propietat. Normalment es mantenen sincronitzats:

p.setAttribute("id", "one"); let id1 = p.getAttribute("id"); // one let id2 = p.id; // one

En canvi, les propietats d'un formulari modificables per l'usuari (value, checked, selected) no estan sincronitzades: l'atribut és el valor inicial de l'HTML i la propietat, l'actual. De fet, Document.setAttribute() només canvia el valor si no ho ha fet l'usuari, però value ho fa sempre.

La manipulació d'estils es realitza accedint a la propietat HTMLElement.style. També és molt útil la propietat Element.classList, que retorna una DOMTokenList. Aquesta permet afegir i esborrar classes d'un element amb els mètodes add i remove, per exemple.

Atributs data

Val la pena parlar dels atributs data. Se solen utilitzar des de JS, en contraposició als atributs HTML, ja que no tenen significat a l'hora de visualitzar un element.

<div class="expand" data-expand> <p>Some content that can be collapsed or expanded.</p> <button data-click="sayHi">Say Hello!</button> <button data-click="showMore">Show More</button> </div>
let accordion = document.querySelector('[data-expand]'); let btnHi = document.querySelector('[data-click="sayHi"]'); let btnMore = document.querySelector('[data-click="showMore"]');

En general, si volem manipular amb JS els elements del DOM, és preferible utilitzar atributs data, que no condicionen la creació d'IDs o classes, que habitualment associem als estils CSS.

Esdeveniments

Podem afegir esdeveniments associats a un node del DOM utilitzant EventTarget.addEventListener(). La sintaxi més habitual inclou dos paràmetes:

  • El type indica quin tipus d'esdeveniment vol escoltar-se.
  • El listener sol ser una funció callback que rep un objecte Event. Aquest objecte té tres propietats interessants: type, el tipus d'esdeveniment; target, l'element que ha generat l'esdeveniment; i currentTarget, el que té associat el listener. Aquestes dues últimes poden ser diferents perquè els esdeveniments són bombolles que pugen.

Exemple de l'efecte bombolla.

<main class="container"> <h1>Bubbling!</h1> <button id="b1">First!</button> <button id="b2">Second!</button> </main>
const main = document.querySelector("main"); const btn1 = document.querySelector("#b1"); const btn2 = document.querySelector("#b2"); function handle(e) { console.log(`${e.type} target: ${e.target.tagName}, current: ${e.currentTarget.tagName}`); } document.body.addEventListener("click", handle); main.addEventListener("click", handle); btn1.addEventListener("click", handle);