Document Object Model - DOM
Romuald THION
Semestre printemps 2023-2024 UCBL
Cf. Exercice 0 : Préparation de TP du TP2.
<script>
src
de l’élément
<script>
(MDN)
async
et defer
type=module"
async
et
defer
<script defer src="...">
👌
DOMContentLoaded
.defer
, ils sont exécutés dans
l’ordre<script src="...">
<script async src="...">
type="module"
ou type="text/javascript"
(défaut)Chargement du JS en module (javascript.info, MDN) :
defer
;strict
;file://
:
python3 -m http.server
ou Live
Server.javascript.info - Browser: Document, Events, Interfaces
Qu’est-ce que le Document Object Model (DOM) ?
Les navigateurs proposent de nombreuses Web APIs autres que le DOM :
Tous les navigateurs proposent une interface JS avec une implémentation du DOM :
xml.dom
en Python.Le DOM permet de naviguer et d’éditer les documents via leurs représentations en mémoire, le DOM tree.
Source javascript.info
globalThis.document
Node
de l’arbre
Sur une page HTML initialement vide
Interface | Description |
---|---|
Document |
La classe de la racine de l’arbre DOM, l’objet
document . |
EventTarget |
La classe la plus abstraite, la gestion événementielle. |
Interface | Description |
---|---|
Node |
Un nœud abstrait de l’arbre, soit un Element , soit du
texte. |
Element |
Une interface abstraite pour les éléments, hérite de
Node , commun à HTMLElement et
SVGElement . |
Interface | Description |
---|---|
HTMLElement |
Un élément HTML est spécialisé par les éléments concrets comme HTMLAnchorElement |
NodeList |
Un tableau de nœuds, pour stocker les fils par exemple. Similaire,
mais non identique à Array JS. |
… | et bien d’autres. |
Element.querySelector(selector)
sélectionne le
premier élémentElement.querySelectorAll(selector)
sélectionne
tous les élémentsglobalThis.id
Element.getElementById(id)
eEement.getElementByClassName(id)
Element.getElementByName(name)
Les sélecteurs sont des strings dans la syntaxe des sélecteurs CSS. Voir javascript.info.
Il faut donc connaître les sélecteurs CSS 🤔
SO - What is the difference between children and childNodes in JavaScript?
Node.childNodes
(MDN)
: renvoie une NodeList
live.Element.childNodes
(MDN)
: renvoie une HTMLCollection
live.📔 Il est conseillé de transformer ces collections avec
Array.from(iterable)
(MDN
- Array.from), voir MDN
- HTMLCollection
document.createElement(tag, options)
créer un élément
HTML (MDN)
L’API DOM est complète : de nombreuses méthodes pour le remplacement, la suppression, etc. :
Element.getAttribute(name)
(MDN)Element.setAttribute(name, val)
(MDN)Element.removeAttribute(name)
(MDN)
Element.attribute;
Exemple sur MDN - HTMLImageElement.src
Element.innerHTML
HTMLElement.innerText
(MDN)Node.textContent
(MDN)⚠️ innerText
et textContent
suppriment
tout le contenu. ⚠️
On préfère textContent
à innerText
(et à innerHTML
) (unicorn,
SO
- What’s the use of textContent/innerText when innerHTML does the
job better? SO
- Difference between textContent vs innerText).
Element.innerHTML
Les failles de type Cross Site Scripting (XSS) (OWASP - XSS et OWASP - DOM Based XSS Prevention)
const $div = document.createElement("div");
$div.innerHTML = `<img src="!" onerror="alert('XSS')"> `;
document.body.append($div);
☣️ L’ajout non contrôlé de contenu en provenance de l’utilisateur conduit à des failles (OWASP Top 10). ☣️
Voir l’exemple
const cats = [
{ id: "k3", url: "https://cdn2.thecatapi.com/images/k3.jpg" },
/* … */
{ id: "d4d", url: "https://cdn2.thecatapi.com/images/d4d.jpg" },
];
const $catsContainer = document.querySelector("#cats-container");
for (const cat of cats) {
const $img = document.createElement("img");
$img.setAttribute("src", cat.url);
$img.setAttribute("alt", `Cat ${cat.id}`);
$catsContainer.appendChild($img);
}
Variante avec fragment HTML pour ne rafraichir la page qu’une fois.
const $root = document.createDocumentFragment();
const $catsContainer = document.querySelector("#cats-container");
for (const cat of cats) {
const $img = document.createElement("img");
$img.setAttribute("src", cat.url);
$img.setAttribute("alt", `Cat ${cat.id}`);
$root.append($img);
}
$catsContainer.append($root);
L’API DOM est complexe, à cause (notamment) de 20 ans d’évolution des standards avec maintien de la rétro compatibilité par les navigateurs.
La manipulation explicite du DOM est fastidieuse 🥹 Des solutions permettent de l’éviter.
On peut utiliser des moteurs de templating plus ou moins riches pour la création d’éléments complexes :
https://mustache.github.io/, https://handlebarsjs.com/, https://ejs.co/, https://pugjs.org ou https://jinja.palletsprojects.com (Python)
On utilisera côté serveur, mais pas côté client.
mustache.js
Les framework s’appuient sur des composants qui encapsulent contenu (HTML), présentation (CSS) et comportement (JS) via des composants standardisés (Web Components) ou propres (React DOM) :
https://react.dev/, https://preactjs.com/, https://angular.io/ ou https://vuejs.org/ sont les plus connus.
Hors du périmètre de LIFWEB.
JSX = JS + template HTML
Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps. (Web Components)
Combinaison de trois technologies standardisées (javascript.info) :
<mon-composant>
<template>
<slot>
Laborieux et bas niveau, on utilise plutôt :
<template>
HTMLSans utiliser Shadow DOM et Custom elements
lit-html
Une méthode sûre (pas de XSS) pour transformer les
chaînes en objets DOM : lit-html
<template>
et
<slot>
lit-html
Les événements DOM sont déclenchés pour notifier au code des « changements intéressants » qui peuvent affecter l’exécution du code. Ces changements peuvent résulter d’interactions avec l’utilisateur, […], de changements dans l’état de l’environnement […], et d’autres causes. (MDN - Event reference)
Voir MDN et javascript.info.
<form onclick="alert('form')">
FORM
<div onclick="alert('div')">
DIV
<p onclick="alert('p')">P</p>
</div>
</form>
(NB : ne respecte pas les bonnes pratiques)
De nombreux éléments DOM peuvent être paramétrés afin d’accepter (« d’écouter ») ces évènements et d’exécuter du code en réaction pour les traiter (« gérer »). (MDN - Event)
EventTarget
) peut
écouter (listen) certains événements.handler
est appellé à chaque
occurrence de l’événement.EventTarget.addEventListener(type, handler, opts)
.EventTarget.removeEventListener(type, handler, opts)
.
Attribut spécial onevent comme
EventTarget.onclick
:
onevent
est un attribut☣️ À bannir (MDN) ☣️
Important ☠️ le handler est une fonction ☠️
element.onclick = handlerFunct()
element.onclick = handlerFunct
handlerFunct()
sera exécuté à chaque
occurrence de l’événement "click"
.On aura besoin de fermetures, de facto, on va faire de la programmation fonctionnelle.
this
dans les
handlersWhen a function is used as an event handler, its this is set to the element on which the listener is placed
const $btn = document.querySelector("button");
function greet(event) {
// affiche le texte du premier boutton de la page
// et deux attributs de l'évenement "click"
console.log(this.innerText, event.type, event.timeStamp);
}
$btn.addEventListener("click", greet);
☣️ this
\neq
currentTarget
\neq
target
☣️
Extrait du fichier
tp1.js
:
/* Exercice 2 : 99 Bottles of Beer */
const $output2 = document.getElementById("output2");
const $input2 = document.getElementById("input2");
document.getElementById("eval2").onclick = () => {
$output2.innerHTML = bottles($input2.value);
};
⚠️ Ne respecte pas le guide de style ⚠️
Voir l’exemple. Ici, un handler par image.
Variante, avec un handler unique pour le
conteneur, qui distribue au bon enfant via Event.target
,
c’est la délégation
d’événement.
.querySelector()
et
.querySelectorAll()
.
.querySelector()
si besoin..innerHTML
.textContent
à .innerText
..createDocumentFragment()
..addEventListener
à .onevent
.
event.target
..class
et #id
à
element
.classList
🔧 Des outils intégrés aux IDE nous aideront.
Est-il utile de renvoyer une valeur de retour dans un handler ?
element.onevent(event)
:
false
event.preventDefault()
Mais return
reste utile, car met fin (à coup sûr) au
flot d’exécution (comme throw
).
Outillage et programmation asynchrone seront l’objet du CM3.