18 octobre 2023

Parcourir le DOM

Le DOM nous permet de faire n’importe quoi avec les Ă©lĂ©ments et leur contenu, mais nous devons d’abord atteindre l’objet DOM correspondant.

Toutes les opĂ©rations sur le DOM commencent par l’objet document. C’est le “point d’entrĂ©e” principal du DOM. De lĂ , nous pouvons accĂ©der Ă  n’importe quel nƓud.

Voici une image des liens qui permettent de voyager entre les nƓuds DOM :

Discutons-en plus en détail.

En haut : documentElement et body

Les nƓuds supĂ©rieurs de l’arbre sont disponibles directement en tant que propriĂ©tĂ©s de document :

<html> = document.documentElement
Le nƓud de document le plus haut est document.documentElement. C’est le noeud DOM de la balise <html>.
<body> = document.body
Un autre nƓud DOM largement utilisĂ© est l’élĂ©ment <body> – document.body.
<head> = document.head
La balise <head> est disponible en tant que document.head.
Il y a un hic : document.body peut ĂȘtre null

Un script ne peut pas accĂ©der Ă  un Ă©lĂ©ment qui n’existe pas au moment de l’exĂ©cution.

En particulier, si un script se trouve dans <head>, alors document.body n’est pas disponible, car le navigateur ne l’a pas encore lu.

Ainsi, dans l’exemple ci-dessous, la premiùre alert affiche null :

<html>

<head>
  <script>
    alert( "From HEAD: " + document.body ); // null, il n'y a pas encore de <body>
  </script>
</head>

<body>

  <script>
    alert( "From BODY: " + document.body ); // HTMLBodyElement maintenant existe
  </script>

</body>
</html>
Dans le monde du DOM, null signifie "n’existe pas "

Dans le DOM, la valeur null signifie “n’existe pas” ou “pas ce genre de nƓud”.

Enfants : childNodes, firstChild, lastChild

Nous utiliserons désormais deux termes :

  • Noeuds enfants (ou enfants) – Ă©lĂ©ments qui sont des enfants directs. En d’autres termes, ils sont imbriquĂ©s dans celui donnĂ©. Par exemple, <head> et <body> sont des enfants de l’élĂ©ment <html>.
  • Descendants – tous les Ă©lĂ©ments imbriquĂ©s dans l’élĂ©ment donnĂ©, y compris les enfants, leurs enfants, etc.

Par exemple, ici <body> a des enfants <div> et <ul> (et quelques nƓuds texte vides) :

<html>
<body>
  <div>Begin</div>

  <ul>
    <li>
      <b>Information</b>
    </li>
  </ul>
</body>
</html>


 Et les descendants de <body> ne sont pas seulement des enfants directs <div>, <ul> mais aussi des Ă©lĂ©ments plus profondĂ©ment imbriquĂ©s, tels que <li> (un enfant de <ul> ) et <b> (un enfant de <li>) – le sous-arbre entier.

La collection childNodes rĂ©pertorie tous les nƓuds enfants, y compris les nƓuds texte.

L’exemple ci-dessous montre des enfants de document.body :

<html>
<body>
  <div>Begin</div>

  <ul>
    <li>Information</li>
  </ul>

  <div>End</div>

  <script>
    for (let i = 0; i < document.body.childNodes.length; i++) {
      alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
    }
  </script>
  ...more stuff...
</body>
</html>

Veuillez noter un dĂ©tail intĂ©ressant ici. Si nous exĂ©cutons l’exemple ci-dessus, le dernier Ă©lĂ©ment affichĂ© est <script>. En fait, le document contient plus de choses en dessous, mais au moment de l’exĂ©cution du script, le navigateur ne l’a pas encore lu, donc le script ne le voit pas.

Les propriétés firstChild et lastChild donnent un accÚs rapide aux premier et dernier enfants.

Ce ne sont que des raccourcis. S’il existe des nƓuds enfants, ce qui suit est toujours vrai :

elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild

Il y a aussi une fonction spĂ©ciale elem.hasChildNodes() pour vĂ©rifier s’il y a des nƓuds enfants.

Collections DOM

Comme nous pouvons le voir, childNodes ressemble Ă  un tableau. Mais en rĂ©alitĂ© ce n’est pas un tableau, mais plutĂŽt une * collection * – un objet itĂ©rable spĂ©cial semblable Ă  un tableau.

Il y a deux conséquences importantes :

  1. Nous pouvons utiliser for..of pour itérer dessus :
for (let node of document.body.childNodes) {
  alert(node); // shows all nodes from the collection
}

C’est parce qu’il est itĂ©rable (fournit la propriĂ©tĂ© Symbol.iterator, selon les besoins).

  1. Les mĂ©thodes de tableau ne fonctionneront pas, car ce n’est pas un tableau :
alert(document.body.childNodes.filter); // undefined (there's no filter method!)

La premiĂšre chose est sympa. La seconde est tolĂ©rable, car nous pouvons utiliser Array.from pour crĂ©er un “vrai” tableau Ă  partir de la collection, si nous voulons des mĂ©thodes de tableau :

alert( Array.from(document.body.childNodes).filter ); // function
Les collections DOM sont en lecture seule

Les collections DOM, et plus encore – toutes les propriĂ©tĂ©s de navigation rĂ©pertoriĂ©es dans ce chapitre sont en lecture seule.

Nous ne pouvons pas remplacer un enfant par autre chose en attribuant childNodes[i] = ....

Changer le DOM nĂ©cessite d’autres mĂ©thodes. Nous les verrons dans le prochain chapitre.

Les collections DOM sont live

Presque toutes les collections DOM avec des exceptions mineures sont live. En d’autres termes, elles reflĂštent l’état actuel du DOM.

Si nous gardons une rĂ©fĂ©rence Ă  element.childNodes, et ajoutons/supprimons des nƓuds dans le DOM, alors ils apparaissent automatiquement dans la collection.

N’utilisez pas for..in pour parcourir les collections

Les collections sont itĂ©rables en utilisant for..of. Parfois, les gens essaient d’utiliser for..in pour cela.

À ne pas faire. La boucle for..in parcourt toutes les propriĂ©tĂ©s Ă©numĂ©rables. Et les collections ont des propriĂ©tĂ©s “supplĂ©mentaires” rarement utilisĂ©es que nous ne voulons gĂ©nĂ©ralement pas obtenir :

<body>
<script>
  // affiche 0, 1, length, item, values et plus encore.
  for (let prop in document.body.childNodes) alert(prop);
</script>
</body>

Frùres, sƓurs et parent

Les frĂšres et sƓurs sont des nƓuds qui sont les enfants du mĂȘme parent.

Par exemple, ici <head> et <body> sont des frùres et sƓurs :

<html>
  <head>...</head><body>...</body>
</html>
  • <body> est dit ĂȘtre le frĂšre “suivant” ou “droit” de <head>,
  • <head> est dit ĂȘtre le frĂšre “prĂ©cĂ©dent” ou “gauche” de <body>.

Le frĂšre suivant est dans la propriĂ©tĂ© nextSibling, et le prĂ©cĂ©dent – dans previousSibling.

Le parent est disponible en tant que parentNode.

Par exemple :

// le parent de <body> est <html>
alert( document.body.parentNode === document.documentElement ); // true

// aprĂšs <head> vient <body>
alert( document.head.nextSibling ); // HTMLBodyElement

// avant <body> vient <head>
alert( document.body.previousSibling ); // HTMLHeadElement

Navigation par élément uniquement

Les propriĂ©tĂ©s de navigation rĂ©pertoriĂ©es ci-dessus font rĂ©fĂ©rence Ă  tous les nƓuds. Par exemple, dans childNodes, nous pouvons voir Ă  la fois les nƓuds texte, les nƓuds Ă©lĂ©ment et mĂȘme les nƓuds commentaire s’il en existe.

Mais pour de nombreuses tĂąches, nous ne voulons pas de nƓuds texte ou commentaire. Nous voulons manipuler des nƓuds Ă©lĂ©ment qui reprĂ©sentent des balises et forment la structure de la page.

Voyons donc plus de liens de navigation qui ne prennent en compte que les nƓuds Ă©lĂ©ment :

Les liens sont similaires Ă  ceux donnĂ©s ci-dessus, juste avec le mot Element Ă  l’intĂ©rieur :

  • children – seuls les enfants qui sont des nƓuds Ă©lĂ©ment.
  • firstElementChild, lastElementChild – enfants du premier et du dernier Ă©lĂ©ment.
  • previousElementSibling, nextElementSibling – Ă©lĂ©ments voisins.
  • parentElement – Ă©lĂ©ment parent.
Pourquoi parentElement ? Le parent peut-il ne pas ĂȘtre un Ă©lĂ©ment ?

La propriĂ©tĂ© parentElement renvoie l’élĂ©ment parent, tandis que parentNode retourne le parent “peu importe le nƓud”. Ces propriĂ©tĂ©s sont gĂ©nĂ©ralement les mĂȘmes : elles obtiennent toutes deux le parent.

À la seule exception de document.documentElement :

alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null

La raison en est que le nƓud racine document.documentElement (<html>) a document comme parent. Mais document n’est pas un nƓud Ă©lĂ©ment, donc parentNode le renvoie et pas parentElement.

Ce dĂ©tail peut ĂȘtre utile lorsque nous voulons passer d’un Ă©lĂ©ment arbitraire elem Ă  <html>, mais pas au document :

while(elem = elem.parentElement) { // remonter jusqu'Ă  <html>
  alert( elem );
}

Modifions l’un des exemples ci-dessus : remplaçons childNodes par children. Maintenant, il ne montre que des Ă©lĂ©ments :

<html>
<body>
  <div>Begin</div>

  <ul>
    <li>Information</li>
  </ul>

  <div>End</div>

  <script>
    for (let elem of document.body.children) {
      alert(elem); // DIV, UL, DIV, SCRIPT
    }
  </script>
  ...
</body>
</html>

Plus de liens : tableaux

Jusqu’à prĂ©sent, nous avons dĂ©crit les propriĂ©tĂ©s de navigation de base.

Certains types d’élĂ©ments DOM peuvent fournir des propriĂ©tĂ©s supplĂ©mentaires, spĂ©cifiques Ă  leur type, pour plus de commoditĂ©.

Les tableaux en sont un excellent exemple et représentent un cas particuliÚrement important :

L’élĂ©ment <table> supporte (en plus de ce qui prĂ©cĂšde) ces propriĂ©tĂ©s :

  • table.rows – la collection d’élĂ©ments <tr> du tableau.
  • table.caption/tHead/tFoot – rĂ©fĂ©rences aux Ă©lĂ©ments <caption>, <thead>, <tfoot>.
  • table.tBodies – la collection d’élĂ©ments <tbody> (peut ĂȘtre multiple selon la norme, mais il y en aura toujours au moins une – mĂȘme si elle n’est pas dans le HTML source, le navigateur la mettra dans le DOM).

<thead>, <tfoot>, <tbody> les éléments fournissent la propriété rows :

  • tbody.rows – la collection de <tr> Ă  l’intĂ©rieur.

<tr>:

  • tr.cells – la collection de cellules <td> et <th> Ă  l’intĂ©rieur du <tr> donnĂ©.
  • tr.sectionRowIndex – la position (index) du <tr> donnĂ© Ă  l’intĂ©rieur du <thead>/<tbody>/<tfoot>.
  • tr.rowIndex – le nombre de <tr> dans le tableau dans son ensemble (y compris toutes les lignes du tableau).

**<td> et <th> : **

  • td.cellIndex – le numĂ©ro de la cellule Ă  l’intĂ©rieur du <tr> qui l’entoure.

Un exemple d’utilisation :

<table id="table">
  <tr>
    <td>one</td><td>two</td>
  </tr>
  <tr>
    <td>three</td><td>four</td>
  </tr>
</table>

<script>
  // obtenir td avec "two" (premiĂšre ligne, deuxiĂšme colonne)
  let td = table.rows[0].cells[1];
  td.style.backgroundColor = "red"; // le mettre en valeur
</script>

La spécification : tabular data.

Il existe également des propriétés de navigation supplémentaires pour les formulaires HTML. Nous les examinerons plus tard lorsque nous commencerons à travailler avec des formulaires.

Résumé

Étant donnĂ© un nƓud DOM, nous pouvons aller vers ses voisins immĂ©diats en utilisant les propriĂ©tĂ©s de navigation.

Il en existe deux ensembles principaux :

  • Pour tous les nƓuds : parentNode, childNodes, firstChild, lastChild, previousSibling, nextSibling.
  • Pour les nƓuds Ă©lĂ©ment uniquement : parentElement, children, firstElementChild, lastElementChild, previousElementSibling, nextElementSibling.

Certains types d’élĂ©ments DOM, par exemple , fournissent des propriĂ©tĂ©s et des collections supplĂ©mentaires pour accĂ©der Ă  leur contenu.

Exercices

importance: 5

Regardez cette page :

<html>
<body>
  <div>Users:</div>
  <ul>
    <li>John</li>
    <li>Pete</li>
  </ul>
</body>
</html>

Pour chacun des Ă©lĂ©ments suivants, donnez au moins un moyen d’y accĂ©der :

  • Le noeud <div> du DOM ?
  • Le noeud <ul> du DOM ?
  • Le deuxiĂšme <li> (avec Pete) ?

Il existe de nombreuses façons, par exemple :

Le noeud <div> du DOM :

document.body.firstElementChild
// ou
document.body.children[0]
// ou (le premier nƓud est l'espace, nous prenons donc le deuxiùme)
document.body.childNodes[1]

Le nƓud <ul> du DOM :

document.body.lastElementChild
// ou
document.body.children[1]

Le deuxiĂšme <li> (avec Pete) :

// obtenir <ul>, puis obtenir son dernier élément enfant
document.body.lastElementChild.lastElementChild
importance: 5

Si element – est un nƓud Ă©lĂ©ment arbitraire du DOM 


  • Est-il vrai que elem.lastChild.nextSibling est toujours null ?
  • Est-il vrai que elem.children[0].previousSibling est toujours null ?
  1. Oui c’est vrai. L’élĂ©ment elem.lastChild est toujours le dernier, il n’a pas de nextSibling.
  2. Non, c’est faux, car elem.children[0] est le premier enfant parmi les Ă©lĂ©ments. Mais il peut exister des nƓuds non-Ă©lĂ©ments avant lui. Ainsi, previousSibling peut ĂȘtre un nƓud texte.

Remarque: dans les deux cas, s’il n’y a pas d’enfants, il y aura une erreur.

S’il n’y a pas d’enfants, elem.lastChild est null, nous ne pouvons donc pas accĂ©der Ă  elem.lastChild.nextSibling. Et la collection elem.children est vide (comme un tableau vide []).

importance: 5

Écrivez le code pour colorer toutes les cellules du tableau diagonal en rouge.

Vous devrez obtenir toutes les diagonales <td> de la <table> et les colorer en utilisant le code :

// td doit ĂȘtre la rĂ©fĂ©rence Ă  la cellule du tableau
td.style.backgroundColor = 'red';

Le rĂ©sultat devrait ĂȘtre :

Open a sandbox for the task.

Nous utiliserons les propriétés rows et cells pour accéder aux cellules du tableau en diagonale.

Ouvrez la solution dans une sandbox.

Carte du tutoriel

Commentaires

lire ceci avant de commenter

  • Si vous avez des amĂ©liorations Ă  suggĂ©rer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de prĂ©ciser.
  • Pour insĂ©rer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen
)