Il DOM ci consente di fare qualsiasi cosa con gli elementi ed il loro contenuto, ma prima dobbiamo essere in grado di raggiungere lâoggetto DOM corrispondente.
Tutte le operazioni sul DOM iniziano con lâoggetto document. Questo è il principale âpunto di ingressoâ per il DOM, dal quale possiamo accedere a qualsiasi nodo.
Ecco unâimmagine dei collegamenti che consentono di muoversi tra i nodi del DOM:
Esaminiamoli nel dettaglio.
In cima: documentElement e body
I nodi in cima allâalbero sono disponibili direttamente come proprietà di document:
<html>=document.documentElement- Il nodo del DOM più in alto è
document.documentElement, esso corrisponde al tag<html>. <body>=document.body- Un altro nodo DOM largamente utilizzato è lâelemento
<body>, ossiadocument.body. <head>=document.head- Il tag
<head>è disponibile comedocument.head.
document.body può essere nullUno script non può accedere ad un elemento che non esiste al momento dellâesecuzione.
In particolare, se uno script si trova allâinterno di <head>, document.body non è disponibile, perché il browser non lo ha ancora letto.
Quindi, nellâesempio sottostante, il primo alert mostra null:
<html>
<head>
<script>
alert( "Da HEAD: " + document.body ); // null, <body> non esiste ancora
</script>
</head>
<body>
<script>
alert( "Da BODY: " + document.body ); // HTMLBodyElement, ora <body> esiste
</script>
</body>
</html>
null significa ânon esisteâNel DOM, il valore null significa ânon esisteâ o ânessun nodo trovatoâ.
Nodi figli: childNodes, firstChild, lastChild
Ci sono due termini che useremo dâora in poi:
- Nodi figli (children o child node) : elementi che sono figli diretti, ossia che sono annidati al primo livello del nodo specificato. Ad esempio,
<head>e<body>sono figli dellâelemento<html>. - Discendenti : Tutti gli elementi che sono annidati a qualsiasi livello nellâelemento specificato. Sono inclusi i figli, i figli dei figli, e così via.
Ad esempio, qui <body> ha come figli <div> e <ul> (ed alcuni nodi di testo vuoti):
<html>
<body>
<div>Inizio</div>
<ul>
<li>
<b>Informazione</b>
</li>
</ul>
</body>
</html>
â¦E come discendenti di <body>, oltre a <div> e <ul>, abbiamo anche gli elementi annidati più in profondità , come <li> (figlio di <ul>) e <b> (figlio di <li>). In pratica lâintero albero sotto a <body>.
La collection childNodes raccoglie tutti i nodi figlio, inclusi quelli di testo
Lâesempio qui sotto mostra i figli di document.body:
<html>
<body>
<div>Inizio</div>
<ul>
<li>Informazione</li>
</ul>
<div>Fine</div>
<script>
for (let i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
</script>
...altro...
</body>
</html>
Da notare un particolare interessante. Se eseguiamo lâesempio, lâultimo elemento mostrato è <script>. Di fatto lâultimo elemento sarebbe ââ¦altroâ¦â, ma al momento dellâesecuzione dello script esso non è ancora stato letto, quindi non viene visto.
Le proprietà firstChild e lastChild permettono un accesso più veloce al primo ed allâultimo figlio.
Sono delle semplici scorciatoie. Se sono presenti dei nodi figlio, allora le seguenti espressioni sono sempre vere :
elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
Esiste anche la funzione speciale elem.hasChildNodes(), per verificare se sono presenti nodi figlio.
DOM collections
Come possiamo vedere, childNodes sembra un array. Ma in realtà non è un array, ma piuttosto una collection: uno speciale oggetto iterabile, simile ad un array.
Questo comporta due importanti conseguenze:
- Possiamo usare
for..ofper iterare:
for (let node of document.body.childNodes) {
alert(node); // mostra tutti i nodi della collection
}
Questo perché è iterabile (ha la proprietà Symbol.iterator che contraddistingue gli iterabili).
- I metodi degli array non funzionano, perché non è un array:
alert(document.body.childNodes.filter); // undefined (il metodo filter non c'è!)
Il primo punto è ottimo, il secondo è tollerabile. Infatti nel caso avessimo bisogno dei metodi degli array possiamo usare Array.from e creare un âveroâ array a partire dalla collection:
alert( Array.from(document.body.childNodes).filter ); // function
Le DOM collections, ed in generale tutte le proprietà utili alla navigazione elencate in questo capitolo, funzionano in sola lettura.
Non possiamo sostituire un nodo figlio con qualcosâaltro con lâassegnazione childNodes[i] = ....
Modificare il DOM richiede lâutilizzo di altri metodi, che vedremo nel prossimo capitolo.
Salvo poche eccezioni, tutte le DOM collections sono vive. In altre parole, riflettono lo stato corrente del DOM.
Se prendiamo come riferimento elem.childNodes, ed aggiungiamo o rimuoviamo nodi dal DOM, essi appariranno o scompariranno automaticamente dalla collection.
for..in per eseguire cicli sulle collectionsLe collections sono iterabili usando for..of, ma a volte qualcuno prova ad usare for..in.
Eâ una pratica sconsigliata. I cicli for..in iterano su tutte le proprietà enumerabili, e le collections possiedono alcune proprietà âextraâ, che vengono usate raramente, e che in genere non vorremmo ci fossero restituite dal ciclo:
<body>
<script>
// mostra 0, 1, length, item, values ed altro.
for (let prop in document.body.childNodes) alert(prop);
</script>
</body>
I fratelli (siblings) ed il genitore (parent)
I fratelli sono nodi figli dello stesso genitore.
Ad esempio, qui <head> e <body> sono fratelli:
<html>
<head>...</head><body>...</body>
</html>
<body>è chiamato fratello âsuccessivoâ o âa destraâ di<head>,<head>è chiamato fratello âprecedenteâ o âa sinistraâ di<body>.
Il fratello successivo è nella proprietà nextSibling, quello precedente in previousSibling.
Il genitore è disponibile come parentNode.
Ad esempio
// il genitore di <body> è <html>
alert( document.body.parentNode === document.documentElement ); // true
// dopo di <head> c'è <body>
alert( document.head.nextSibling ); // HTMLBodyElement
// prima di <body> c'è <head>
alert( document.body.previousSibling ); // HTMLHeadElement
Navigazione solo tra elementi
Le proprietà di navigazione viste finora fanno riferimento a tutti i nodi. Ad esempio, in childNodes possiamo trovare: nodi elemento, nodi di testo, ed anche nodi commento se ce ne sono.
Ma per alcuni compiti non vogliamo nodi di testo o di commento. Vogliamo solo manipolare nodi che rappresentano i tags e che costituiscono la struttura della pagina.
Quindi vediamo altri collegamenti di navigazione che prendono in considerazione solo nodi elemento:
I collegamenti sono simili a quelli visti prima, ma con lâaggiunta della parola Element:
childrenâ solo i figli che sono nodi elemento.firstElementChild,lastElementChildâ il primo e lâultimo elemento figlio.previousElementSibling,nextElementSiblingâ gli elementi vicini.parentElementâ lâelemento genitore.
parentElement? Può un genitore non essere un elemento?La proprietà parentElement restituisce lâelemento genitore, mentre parentNode restituisce âqualsiasi nodoâ genitore. Queste proprietà in genere coincidono: entrambe restituiscono lo stesso genitore.
Con lâunica eccezione di document.documentElement:
alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null
La ragione è che il nodo radice document.documentElement (<html>) ha document come genitore, ma document non è un nodo elemento, quindi parentNode lo restituisce, mentre parentElement no.
Questo dettaglio può essere utile quando vogliamo risalire da un elemento arbitrario elem verso <html>, ma senza arrivare a document:
while(elem = elem.parentElement) { // su, fino a <html>
alert( elem );
}
Modifichiamo uno degli esempi sopra, sostituendo childNodes conchildren. Ora vengono mostrati solo gli elementi:
<html>
<body>
<div>Inizio</div>
<ul>
<li>Informazione</li>
</ul>
<div>Fine</div>
<script>
for (let elem of document.body.children) {
alert(elem); // DIV, UL, DIV, SCRIPT
}
</script>
...
</body>
</html>
Ulteriori collegamenti: tabelle
Finora abbiamo descritto le proprietà base per la navigazione.
Alcuni tipi di elemento DOM possono fornire proprietà aggiuntive, specifiche per il loro tipo.
Le tabelle ne sono un esempio, e rappresentano un caso particolarmente importante:
Lâelemento <table> supporta (in aggiunta a quelle trattate sinora) le seguenti proprietà :
table.rowsâ la collection degli elementi<tr>della tabella.table.caption/tHead/tFootâ riferimenti agli elementi<caption>,<thead>,<tfoot>.table.tBodiesâ la collection degli elementi<tbody>(secondo lo standard possono essere molti, ma ce ne sarà sempre almeno uno. Anche se non è nellâHTML di origine, il browser lo inserirà automaticamente nel DOM).
Gli elementi <thead>, <tfoot>, <tbody> hanno la proprietà rows:
tbody.rowsâ la collection degli elementi<tr>contenuti.
<tr>:
tr.cellsâ la collection delle celle<td>e<th>contenute nel<tr>specificato.tr.sectionRowIndexâ la posizione (index) di un dato<tr>incluso in<thead>/<tbody>/<tfoot>.tr.rowIndexâ il numero di<tr>nella tabella nel suo insieme (include tutte le righe della tabella).
<td> e <th>:
td.cellIndexâ il numero della celle allâinterno del contenitore<tr>.
Un esempio di utilizzo:
<table id="table">
<tr>
<td>uno</td><td>due</td>
</tr>
<tr>
<td>tre</td><td>quattro</td>
</tr>
</table>
<script>
// estrai td con "two" (prima riga, seconda colonna)
let td = table.rows[0].cells[1];
td.style.backgroundColor = "red"; // evidenzialo
</script>
La specifica: tabular data.
Proprietà di navigazione aggiuntive, sono anche disponibili per i moduli HTML. Le esamineremo più avanti, quando inizieremo a lavorare con i moduli.
Riepilogo
Dato un nodo DOM, possiamo spostarci tra i suoi vicini usando le proprietà di navigazione.
Ne esistono due tipi principali:
- Per tutti i nodi:
parentNode,childNodes,firstChild,lastChild,previousSibling,nextSibling. - Solo per i nodi elemento:
parentElement,children,firstElementChild,lastElementChild,previousElementSibling,nextElementSibling.
Alcuni tipi di elemento DOM, es. le tabelle, Some types of DOM elements, e.g. tables, forniscono proprietà e collections aggiuntive per accedere al loro contenuto.
Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)