Cours 2
Interaction avec la page Web DOM, Évènements

Le Web 2.0

Le rôle du JavaScript

Rôle du JavaScript

L’évolution du Web

  • Web 1.0 : Pages statiques
    Une adresse = une page qui ne bouge pas

  • Web 1.5 : Pages dynamiques
    • Génération de page côté serveur (ex: PHP)
      Mais une page ne varie pas entre deux requêtes
    • Script côté client (ex: JavaScript)
      Permet des applications côté client (des calculs …)
      Permet des activités sur la page sans la recharger
      (changement de la page, de son style …)
  • Web 2.0 (Cours prochain)
    Communications asynchrones (non liés au chargement des pages) entre le serveur et le client
    XMLHttpRequest, Ajax, WebSocket …

JavaScript dans un document HTML

Différentes syntaxes :

  1. Chargement d’un script JavaScript externe

    <script defer src="code/hello.js"></script>
    

    defer : attendre la fin du chargement de la page avant l’exécution du script

  2. Script directement dans le HTML (peu conseillé)

    <script>alert("hello!");</script>
    
  3. Actions directement dans le HTML (déconseillé)

    <button onclick="alert('Boom!')">Do not press</button>
    

Structure du code

De la même manière que nous séparons le style CSS du document HTML, nous séparerons les actions JavaScript.

Question : Pourquoi séparer HTML, CSS et JS ?

  • Clarté : Cela donne de la structure au code.
    • Le document HTML décrit la structure du document et son sens (sa sémantique). Par exemple, tel élément est un titre <h1>, tel élément est un menu class=menu.
    • Le CSS associe un style à chaque élément en fonction de son sens.
    • Le JS rajoute des interactions avec le document.
  • Maintenabilité : Le style étant regroupé dans les feuilles CSS, il est plus simple de le retrouver et l’éditer. De plus, on évite les répétitions en associant plusieurs fois le même style à des éléments différents. C’est pareil pour les actions JS.

Structuration du code JavaScript :

  • Dans un fichier JavaScript séparé
    • Définition des actions
    • Association des actions aux éléments HTML à l’aide des sélecteurs CSS.
  • Ainsi, nous séparons le style, les actions et le contenu.

Le Document Object Model

Le DOM

Le modèle objet de document DOM (Document Object Model) est une interface de programmation (API) avec le document HTML.

Le DOM JavaScript est accessible via l’objet document.

Exemples

document.documentElement; // Renvoie le <html>
document.head; // Renvoie le <head> de la page
document.body; // Renvoie le <body> de la page
document.URL; // Renvoie l'adresse
document.cookie; // Renvoie les cookies
document.title; // Renvoie le titre
// Renvoie l'URL de la page précédente 
document.referrer; 

La structure d’arbre du HTML

Les documents HTML ont une structure d’arbre

<!doctype html>
<html>
  <head>
    <title>Ma page d'accueil</title>
  </head>
  <body>
    <h1>Ma page d'accueil</h1>
    <p>Bonjour, je m'appelle Romain.</p>
    <p>Allez voir mon cours de JavaScript à 
      <a href="http://romainlebreton.github.io">
        cette adresse</a>.</p>
  </body>
</html>

La structure d’arbre du HTML

Ce code HTML correspond à l’arbre suivant

Structure d'arbre du HTML

Source de l’image

Les méthodes de base pour naviguer dans l’arbre :

  • childNodes, parentNode,
  • firstChild, lastChild,
  • previousSibling, nextSibling

Navigation dans l'arbre

Problème : En pratique, c’est plus compliqué à cause des espaces et sauts de lignes entre balises : Démo

Source de l’image

Différents types de nœuds

Les nœuds de l’arbre sont du type Node. Il existe différents types de Node, que l’on peut distinguer par leur propriété nodeType :

NodeType

Exemple:

// body est un Element (= une balise)
document.body.nodeType; // → 1
// Son premier fils est un noeud texte
document.body.firstChild.nodeType // → 3
// Son premier fils Element est une balise
document.body.firstElementChild.nodeType // → 1 


Tous les Node Seulement les Element
parentNode parentElement
childNodes children
firstChild firstElementChild
lastChild lastElementChild
previousSibling previousElementSibling
nextSibling nextElementSibling

Recherche dans le DOM

Problème : Trouver un élément particulier n’est pas très pratique

Solution : on utilise les méthodes de recherche

  • getElementById, (renvoie un élément)
  • getElementsByTagName, (renvoie un tableau d’éléments)
  • getElementsByClassName. (renvoie un tableau d’éléments)

Exemple :

// Identifiant unique donc on renvoie un élément
let i1 = document.getElementById("id1");
// Tous les enfants de i1 de classe myclass
let tab_e = i1.getElementsByClassName("myclass");

Remarque : getElementsByTagName et getElementsByClassName sont des méthodes de Element :
on peut les appeler sur document ou toute balise HTML.

Les sélecteurs

Les sélecteurs en CSS permettent de faire des recherches avancées par nom de balise, classe …

Sélecteurs simples (tag est toujours optionnel)

*                   /* Sélectionne tout                         */
tag                 /* Toute balise <tag>                       */
tag.class           /* Tout <tag> de classe class               */
#id                 /* La balise identifiée par id              */
tag:pseudoclass     /* Sélection de contenu spécial             */
tag[att=val]        /* Tout <tag> ayant attribut att égal à val */

Exemples:

  • li:nth-child(2*n) – Éléments pairs d’une liste
  • a:hover – Lien survolé
  • p::first-letter – Première lettre d’un paragraphe

Rappels sur les sélecteurs

Combinateurs de sélecteurs

sel1, sel2        /* Chacun des sélecteurs                   */
parent child      /* child s'il est un fils de parent        */
parent > child    /* child seulement s'il est un fils direct */
sister ~ brother  /* brother s'il suit sister                */
sister + brother  /* brother s'il suit immédiatement sister  */

Exemples:

  • Q. Que sélectionne body > * > p ?
  • R. Les paragraphes qui sont des petits-fils exacts de <body>

  • Q. Que sélectionne ul > * li ?
  • R. Les <li> qui sont au moins petit-fils d’un <ul>

Références : W3schools, le standard CSS3

Recherche avancée

  • querySelectorAll(selector) :
    tous les Element satisfaisant le sélecteur,
  • querySelector(selector) :
    le premier Element satisfaisant le sélecteur

Notes :

  • Raccourci courant :
    • $$ pour document.querySelectorAll.
    • $ pour document.querySelector.
  • Ce sont des méthodes de Element : valable sur toute balise

    // Recherche parmi les descendants de `element`
    element.querySelector(selector)
    // Recherche parmi les ancêtres de `element`
    element.closest(selector)
    // Indique si `element` satisfait le sélecteur 
    element.matches(selector)
    

Modification du contenu

Attribut innerHTML de Element :

  • représentation texte du contenu d’une balise,
    en lecture et en écriture
    h1.innerHTML = "<u>coucou</u>"
    
  • ⚠️ échappement des caractères spéciaux du HTML ⚠️
    h1.innerHTML = "<script>alert('Boom!')</script>"
    h1.textContent = "<script>alert('Boom!')</script>"
    

    element.textContent (équivalent de htmlspecialchars)
    encodeURI / encodeURIComponent (équivalent de urlencode)
    L’insertion de <script> ci-dessus ne marche pas en pratique, mais il reste une faille de sécurité.
    Démo

Attributs d’une balise

On peut changer la valeur de l’attribut d’une balise HTML. On peut aussi créer un attribut et lui donner une valeur, ou le supprimer.

let input = document.querySelector("input")
input.max=25
input.value=17
input.setAttribute("name", "monInput")
input.removeAttribute("min")

Démo

Modification des classes

On peut accéder à la liste des classes d’une balise HTML (voir TD1) :

let div = document.querySelector("div")
div.classList
div.classList.remove("c2")
div.classList.add("c4")
div.classList.toggle("cache")
div.classList.toggle("cache")
div.classList.replace("c4", "c2")

Démo

Insertion de balises HTML (1/2)

let div = document.querySelector("#div_p")
let paragraphes = ["coucou", "hello", "salut"]
for (let paragraphe of paragraphes) {
    pHTML = `<p> ${paragraphe} </p>`
    div.insertAdjacentHTML('beforeend', pHTML)
    // Équivalent à (et plus rapide que)
    // div.innerHTML += pHTML
}
  • Préférer insertAdjacentHTML à innerHTML += ... :
    • équivalent à div.innerHTML = div.innerHTML + pHTML
    • évite de sérialiser et parser tout le HTML existant
    • améliore les performances

Démo

Source

Insertion de balises HTML (2/2)

let newP = document.createElement("p")
newP.setAttribute("id","p2")
newP.textContent = "paragraphe 2"
let div = document.getElementById("div_p")
div.appendChild(newP)
div


Avantages de createElement sur innerHTML :

  • Préserve les références aux Element existants
    → préserve les gestionnaires d’évènements de ces Element
  • Attention à l’échappement HTML au sein de innerHTML

Démo

Suppression de balises HTML (1/2)

Si l’objectif est de supprimer l’intégralité des balises qui descendent d’une balise englobante, on peut brutalement affecter une chaîne vide au innerHTML de la balise englobante…

let div = document.querySelector("#div_p")
div.innerHTML = ""

Démo

Suppression de balises HTML (2/2)

Au choix :

  1. parent.removeChild(child) supprime la balise child, et retourne l’élément supprimé
  2. balise.remove() supprime la balise balise
let parent = document.querySelector("p")
let child = parent.children[0]
// Au choix
parent.removeChild(child)
// Ou
child.remove()

Les évènements en JavaScript

Gestionnaires d’évènements

Il y a 3 manières d’associer une action à un évènement

Par exemple, pour exécuter la fonction act() lors d’un clic sur un <button> (variable b) :

  1. b.addEventListener('click',act);
    
  2. b.onclick = act;
    
  3. <button onclick='act()'>
    

Gestionnaires d’évènements

Utilisez la première syntaxe

b.addEventListener('click',act);

car

  • on peut associer plusieurs actions au même évènement
  • on peut supprimer une action d’un évènement

    b.removeEventListener('click',act);
    
  • on peut ajouter des options avancées : once, …

L’objet évènement

La fonction donnée au gestionnaire reçoit un paramètre :

l’objet évènement du type Event

Exemple: La propriété button indique le bouton cliqué de la souris

<button>Cliquez-moi de toutes les manières</button>
<script>
  let button = document.querySelector("button");
  button.addEventListener("mousedown", function(event) {
    if (event.button == 0)
      console.log("Left button");
    else if (event.button == 1)
      console.log("Middle button");
    else if (event.button == 2)
      console.log("Right button");
  });
</script>

Types d’évènements courants

  • Clavier :
    • keydown : À chaque appui ou répétition d’une touche
    • keyup : À chaque relâchement d’une touche
  • Souris :
    • mousedown, mouseup, click, dblclick : Clics de souris
    • mousemove, mouseenter, mouseout : Déplacements
  • Défilement d’écran : scroll
  • Élément actif : focus, blur
  • Entrée de formulaire : input, change
  • Chargement terminé :
    • load : chargement terminé d’une ressource et de ses dépendances
    • DOMContentLoaded : chargement terminé du DOM

Classes d’évènements

Exemples :

  • class KeyboardEvent (hérite de UIEvent, qui hérite de Event)
    • key : valeur String de la touche
      Exemple : a-z, A-Z, é, €, ArrowUp, Enter
    • code : liée à la position physique de la touche,
      donc pas lié à la disposition du clavier (azerty, qwerty)
    • ctrlKey, shiftKey, altKey, metaKey (boolean)
    • repeat : true si appui long qui amène une répétition
  • class MouseEvent
    • screenX, screenY, clientX, clientY : position
    • ctrlKey, shiftKey, altKey, metaKey (boolean)
    • button : bouton appuyé
    • detail : compte le nombre de clic
      Permet de différencier un clic, d’un double clic

Propagation des évènements

Un gestionnaire d’évènement va recevoir les évènements qui se produisent sur ses fils.

<p>Un paragraphe avec un <button>bouton</button>.</p>
<script>
  let par = document.querySelector("p");
  let button = document.querySelector("button");

  par.addEventListener("mousedown", function() {
    console.log("Gestionnaire du paragraphe.");
  });
  button.addEventListener("mousedown", function() {
    console.log("Gestionnaire du bouton.");
  });
</script>

Un paragraphe avec un .

Propagation des évènements

  • event.stopPropagation() arrête la propagation vers la racine.

  • target : cible réelle de l’évènement.

  • currentTarget : balise cible de addEventListener.

<p>Un paragraphe avec un <button>bouton</button>.</p>
<script>
  let par = document.querySelector("p");
  let button = document.querySelector("button");
  par.addEventListener("mousedown", function(event) {
    console.log("Gestionnaire du paragraphe : ", event.target);
  });
  button.addEventListener("mousedown", function(event) {
    console.log("Gestionnaire du bouton : ", event.target);
    if (event.button == 2)
      event.stopPropagation();
  });
</script>

Un paragraphe avec un .

Utilité de la propagation

Regrouper de nombreux EventListener sur un ancêtre commun.

Par exemple, pour associer un comportement à un sélecteur
(similaire à l’association d’un style à un sélecteur dans un fichier CSS)

// Loggue tous les évènements des balises 
// de classe "log"
document.addEventListener("click", function(event) {
  if (event.target.matches(".log")) {
    console.log(event);
  }
})

Source

Comportement par défaut

event.preventDefault() stoppe l’action par défaut.

Exemple :

  1. Si on clique sur le bouton “Envoyer” d’un formulaire, cela l’empêche de le soumettre

  2. Sur on clique sur un lien, cela l’empèche de suivre l’URL

  3. Si on clique sur une checkbox , elle ne se coche pas…

Biblio : Plus de détails sur la gestion du clavier, les mouvements de la souris, le déroulement de la page et les actions par défaut sur le site de Eloquent JavaScript.

Sources