Modifier le DOM avec Javascript

Jean-Marc Lecarpentier
Université de Caen
En partie adapté des cours de Alexandre Niveau et Hervé Le Crosnier

DOM : Document Object Model

Objectifs :

  • Disposer en mémoire vive d'une représentation en arbre d'un document
  • Définir comment modifier cet arbre de façon unifiée pour tous les navigateurs
  • Page web vue = Représentation graphique de l'arbre
  • Changement de l'arbre ⇒ changement de la vue
  • Document Object Model : spécification du WhatWG et norme du W3C

Nécessité du DOM

  • Les programmes en Javascript sont exécutés par le navigateur
  • Leur but : modifier la page HTML/CSS en fonction des actions de l'internaute
  • Il faut donc avoir un modèle de la page et de sa structure, ainsi que des fonctions permettant de manipuler ce modèle : une API (application programming interface)
  • Le modèle des pages HTML (et XML) s'appelle le DOM, document object model
  • Langage utilisé dans les navigateurs : Javascript
  • Notions de DOM et fonctionnalités existantes dans d'autres langages (Python, Java, PHP, etc)

Historique du DOM

  • 1996 : sortie de Javascript avec Netscape 2.0 puis de JScript avec IE 3.0
    • Les deux versions ont un DOM majoritairement compatible, JScript étant un portage de Javascript
    • On l'appelle souvent DOM niveau 0
  • 1997 : Netscape et IE versions 4.0, développés en parallèle, introduisent des modifications incompatibles dans leurs DOM
  • Il fallait donc plusieurs versions de chaque programme pour une même page web…
  • 1998 : Standardisation par le W3C du DOM niveau 1
  • DOM 2 en 2000, DOM 3 en 2004, DOM 4 en 2014
  • DOM Living Standard édité par le WhatWG unifie les anciennes normes et les implémentations existantes dans les navigateurs

HTML ⇒ DOM ⇒ Vue

  • Navigateur = parseur HTML + moteur graphique
    • Parseur HTML : construit l'arbre DOM en mémoire
    • Moteur graphique : construit une représentation de l'arbre DOM, suivant les règles données dans les CSS
    • Principaux moteurs :
      • Gecko (Mozilla - Firefox)
      • WebKit (Apple - Safari)
      • Blink (Google - Chrome, Edge)
Du HTML au DOM puis la vue
Passage du HTML au DOM puis à la Vue

Modification du DOM

  • Javascript : implémente l'API DOM ⇒ possibilité de transformer l'arbre
  • Toute modification de l'arbre DOM est immédiatement répercutée dans la représentation graphique
  • Attention : l'arbre DOM est modifié dans la mémoire du navigateur, mais « afficher le code source » montre toujours le code de départ !
  • Pour voir le code HTML correspondant à l'état réel du DOM à tout instant, utiliser l'inspecteur du navigateur
Modification du DOM reflétée dans la vue
Modification du DOM reflétée dans la Vue

Types de nœuds

  • Noeud de type texte (Text node) :
    • représente du texte
    • Possibilité de changer le texte seulement
    • Noeud de texte est forcément une feuille de l'arbre
  • Noeud de type élément (Element node):
    • Représente un élément HTML
    • Possibilité de changer les nœuds éléments, par ex. changer ses attributs
    • Noeud de type élément a des fils de type élément et/ou texte
    • Noeud de type élément est une feuille de l'arbre si c'est un élément vide (par ex. un élément img)
  • Noeud de type commentaire (Comment node)
    • Représente un commentaire HTML
    • Peu utile en général
  • Plus d'autres types moins utiles pour les documents HTML

Exemple

Arbre DOM
Voir le code HTML de cet arbre

Implémentation de l'API DOM avec Javascript

  • Les scripts en Javascript permettent au navigateur d'agir sur l'arbre DOM du document en cours de visualisation
  • Les objets du DOM sont implémentés par des classes en Javascript
  • On peut accéder aux propriétés des divers objets (document, nœud, élément, etc)
  • Des fonctions ou méthodes permettent d'agir sur ces objets

Classes

  • Une classe est une structure de données (un objet) sur lequel on peut agir
  • Un objet est une instance de classe avec des :
    • propriétés : les données internes de l'objet
    • méthodes : les fonctions qui informent ou agissent sur l'objet lui-même
  • Par ex., une chaine de caractères est aussi un objet de type String.
    let nom = "Pr. Tournesol";
    L'objet nom possède la propriété length :
    console.log(nom.length)); (affiche 13)
    L'objet nom possède (entre autres) une méthode toUpperCase() :
    console.log(nom.toUpperCase()); (affiche PR. TOURNESOL)

Objets et DOM

  • Javascript possède des objets prédéfinis : String Array Date Number...
  • De même le DOM définit un ensemble d'objets pour le manipuler
  • Les objets de base du DOM sont utilisables pour tout document XML
  • Pour les documents HTML, l'extension DOM HTML ajoute des objets spécifiques pour manipuler les pages web

Principaux objets du DOM

  • Document : l'arbre du document qui a été parsé
  • Node : les nœuds, qui peuvent être de différents types :
    • Element : nœuds éléments
    • CharacterData : nœuds de texte

Voir la référence sur MDN

L'objet document

  • Modélise le document manipulé
  • L'élément racine du document : document.documentElement
  • L'élément <body> du document : document.body
  • Obtenir un élément par son identifiant : document.getElementById("toto")
  • Obtenir une liste d'éléments via un sélecteur CSS : document.querySelectorAll("div p")
  • Plus des fonctions pour créer des nœuds (cf. suite du cours)
Démo (en console)

Objets de de type Node

  • Modélise tous les types de nœuds, que ce soit des élements ou non
  • Pas toujours utilisable car ne fait pas la distinction entre éléments et texte
  • Se dérive en 2 autres objets pour les éléments et le texte
  • Fonctionnalités des objets Node :
    • Tester si le nœud a des fils : e.hasChildNodes()
    • Liste des fils : n.childNodes
    • Nombre de fils : n.childNodes.length
    • Premier fils : n.firstChild
    • Dernier fils : n.lastChild
    • Nœud parent : n.parentNode
    • Frère suivant : n.nextSibling
    • Frère précédent : n.previousSibling
  • Attention aux nœuds de texte vides !
Démo (en console)

Objet de type Element

  • Généralement, on préfère parcourir le DOM en ignorant les nœuds de texte (notamment à cause des nœuds de texte vides)
  • Les fonctionnalités des objets de type Element permettent de le faire simplement :
    • Contenu textuel : e.textContent
      Attention cela renvoie le texte nettoyé des éventuels éléments HTML contenus dans le nœud
    • Liste des éléments fils : e.children
    • Nombre d'éléments fils : e.childElementCount plus efficace que e.children.length
    • Premier/dernier fils qui est un élément : e.firstElementChild et e.lastElementChild
    • Frère suivant/précédent qui est un élément : e.nextElementSibling et e.previousElementSibling
    • Obtenir un attribut : e.getAttribute(nomAttribut)
    • Modifier un attribut : e.setAttribute(nomAttribut, nouvelleValeur)
Démo (en console)

Obtenir des éléments de l'arbre DOM

  • Avec l'identifiant d'un élément : document.getElementById("toto")
  • Avec un sélecteur CSS :
    • document.querySelector("#tutu div.erreur") : renvoie le premier de tous les éléments correspondant au sélecteur CSS donné
    • document.querySelectorAll("#tutu div.erreur") : renvoie la liste statique de tous les éléments correspondant au sélecteur CSS donné
    • Autres méthodes à éviter car elles renvoient des listes live donc qui changent en fonction des modifications de l'arbre DOM, ce qui pose un problème quand on parcourt la liste avec un for : getElementsByTagName() et getElementsByClassName().
    • Note : on peut avoir la même fonctionnalité avec des listes statiques en utilisant document.querySelectorAll()
Démo (en console)

Propriété textContent : modifier le contenu d'un élément

  • Utiliser la proporiété textContent de l'élément :
  • Attention cela écrase et remplace tout le contenu de l'élément e
  • let para = document.querySelector("p");
    para.textContent = "Le nouveau texte";
                

Propriété innerHTML

  • Parfois on veut remplacer le contenu du nœud par d'autres nœuds
  • innerHTML fonctionne de la même façon que textContent, mais en « gardant les éléments HTML »
  • Peut provoquer des failles de sécurité (injections) et n'est pas très efficace
  • Très pratique pour les tests et les bidouilles rapides, mais :
  • interdiction de l'utiliser pour les TPs

Modifier le DOM : créer des nœuds

  • Créer un élément : méthode createElement() du document
    let newP = document.createElement("p");
  • Cloner un élément : méthode cloneNode de Element :
    let newP = unAutreP.cloneNode()
    Attention toujours faire un clone pour copier un élément
    Attention les attributs sont aussi clonés, cela peut induire des id dupliqués
  • Cloner un sous-arbre : méthode cloneNode de Element avec l'argument true :
    let newP = unAutreP.cloneNode(true
    Clone l'élément ET tous ses descendants
  • Créer un nœud de texte : méthode createTextNode() du document
    let texte = document.createTextNote("Le texte du nœud");
  • Un nœud créé n'est pas rattaché à l'arbre du document

Modifier le DOM : ajouter un nœud à l'arbre DOM

  • Un nœud créé ne sera visible dans le navigateur qu'après avoir été rattaché à l'arbre DOM.
    Le nœud rattaché doit être un élément ou un nœud de texte
    Si le nœud est en fait un sous-arbre alors tout le sous-arbre rattaché devient visible.
  • Plusieurs possiblités :
    • noeudParent.appendChild(noeudEnfant) : attache noeudEnfant en dernier fils de noeudParent
    • noeudParent.insertBefore(noeudEnfant, reference) : attache noeudEnfant comme fils de noeudParent avant le nœud reference
    • noeud.insertAdjacentElement(position, element) ajoute le nœud element dans l'arbre en fonction de la position spécifiée
      position peut être beforebegin, afterbegin, beforeend ou afterend
      Voir la documentation

Modifier le DOM : ajouter des nœuds ou du texte à l'arbre DOM

  • Les méthodes suivantes permettent d'ajouter directement un ou plusieurs éléments ou textes à un élément donné.
    Les arguments fils1, fils2, ... peuvent être des objets élément ou des chaines de caractères. Si filsN est une chaine de caractère, un nœud de texte est automatiquement créé pour l'ajouter à l'arbre.
  • noeudParent.append(fils1, fils2, fils3, ...) : attache chacun des filsN en derniers fils
  • noeudParent.prepend(fils1, fils2, fils3, ...) : attache chacun des filsN en premiers fils
  • noeud.before(fils, fils2, fils3, ...) : attache chacun des filsN en frères précédents du noeud
  • noeud.after(fils1, fils2, fils3, ...) : attache chacun des filsN en frères suivants du noeud
Démo (en console)

Modifier le DOM : supprimer des nœuds de l'arbre DOM

  • Supprimer un nœud de l'arbre supprime aussi tous ses éventuels enfants (i.e. tout le sous-arbre)
  • noeudParent.removeChild(filsASupprimer) : enlève filsASupprimer (et tout son sous-arbre) des fils de noeudParent
    filsASupprimer peut-être un nœud élément ou un nœud de texte
  • element.remove() supprime element lui-même (et tout son sous-arbre)
  • noeudParent.replaceChild(ancienFils, nouveauFils) remplace ancienFils par nouveauFils (avec leurs sous-arbres éventuels)
  • element.replaceWith(fils1, fils2, ...) remplace element lui-même par les noeuds fils1, fils2, ...
    Si filsN est une chaine de caractère, un nœud de texte est automatiquement créé pour l'ajouter à l'arbre
  • Pour vider un élément de tout son contenu (i.e. de tous ses fils éléments et textes), on peut :
    • réinitialiser son textContent :
      element.textContent = '';
    • supprimer tous ses fils :
      while (element.firstChild) {
          element.removeChild(element.firstChild);
      }
Démo (en console)

DOM et styles CSS

  • On a souvent besoin de modifier le style d'un élément
  • On peut modifier les propriétés de style de chaque élément :
    let unElement = document.getElementById("toto")
    unElement.style.color="green";
    unElement.style.backgroundColor="blue";
    unElement.style.display="none";
    
  • Chaque propriété CSS correspond à une propriété de style
    Attention les tirets sont remplacés par du camelCase
  • Cela équivaut à mettre à l'élément l'attribut style="color: green; background-color: blue; display: none;"
Démo

Récupérer le style CSS

  • Attention, les propriétés récupérées avec .style correspondent uniquement au contenu de l'attribut HTML style="…"
  • Pour récupérer le style appliqué par le navigateur (depuis une feuille de style par exemple), il faut utiliser getComputedStyle(element)
Démo

Manipuler les classes d'un élément

  • En général on ne manipulera pas directement le style : séparation entre présentation (CSS) et comportement (JS)
  • La façon propre de faire est de passer par des classes, dont le style est défini indépendamment du script
  • Pour manipuler les classes, on utilisera la propriété classList des éléments :
    let toto = document.getElementById("toto");
    toto.classList.add("tutu");
    toto.classList.remove("titi");
    if (toto.classList.contains("foobar")) {
        toto.classList.toggle("erreur");
    }
    

Bilan de cours

  • Le DOM permet :
    • de construire une représentation en arbre d'un document
    • d'offrir une interface commune d'accès et modification du document, quel que soit le langage applicatif
  • Grâce à l'implémentation de l'interface DOM dans le langage Javascript, nous pouvons directement :
    • repérer un élément dans l'arbre DOM
    • modifier ses attributs, notamment ses classes, pour modifier son apparence
    • ajouter ou supprimer des éléments du DOM