Retourner � la page d'accueil de TJSRetourner � la page d'accueil de TJS

Manipuler le document avec le Dynamic HTML

Que signifie DHTML ?

Le DHTML, signifie simplement Dynamic HTML. Il ne s'agit pas d'un langage particulier, mais d'une notion de développement web qui permet la manipulation de la page, de ses éléments, de leur contenu et leur apparence, une fois que la page est chargée.

Historiquement, le HTML ne permettait pas de modifier le contenu d'une page une fois que l'événement onload était déclenché. La seule solution pour le développeur était d'utiliser des formulaires pour y faire apparaitre des résultats.

Grâce aux travaux de normalisation des langages JS et HTML et des navigateurs, le DHTML est à l'origine de toutes les interfaces web qui remplacent les applications et logiciels exécutables traditionnels, comme les outils de messagerie mail, d'éditeurs de document, les tableurs ou même les sites comme Facebook.

Un premier exemple

Utilisons un premier exemple simple de ce que permet le DHTML : mise à jour de contenu, modification de l'apparence, réaction aux actions de l'utilisateur.

J'ai commencé la mise à jour de ce tutoriel le jour où le Bitcoin a dépassé les 10 000 $ (le 28/11/2018). L'occasion était évidente pour en faire un exemple didactique. (Envie d'en savoir plus sur le Bitcoin ?)

Le module a ce code HTML :

<div id="exemple">
	<div id="titre">Cours du Bitcoin (BTC)</div>
	<div id="cours" data-devise="USD"> </div>
	<div class="footer">
		<div id="euro" class="deviseOFF" onclick="clickDevise('euro')"> EUR </div>
		<div id="usd"  class="deviseON"  onclick="clickDevise('usd')"> USD </div>
	</div>
	<div id="info">Récupération de la valeur du Bitcoin...</div>
	<div id="chrono"> </div>
	<div id="legal">Cours provenant de cryptocompare.com</div>
</div>

Le code JS est assez long. Il n'est pas affiché, mais vous pouvez le voir en cliquant sur le lien :

/* Formatage de nombre */
function formatNombre(n) {
	var intlN=new Intl.NumberFormat();
	return intlN.format(n);
}

/* Formatage de date et heure */
function formatHeure(dt) {
	var options={hour12: false, hour: "2-digit", minute: "2-digit", second:"2-digit"};
	var intlD=new Intl.DateTimeFormat("fr-FR", options);
	return intlD.format(dt);
}

/* Gestion du clic sur EUR ou USD et mise à jour de l'affichage */
function clickDevise(devise) {
	if (devise=="euro") {
		document.getElementById("euro").className="deviseON";
		document.getElementById("usd").className="deviseOFF";
		document.getElementById("cours").dataset.devise="EUR";
	}
	if (devise=="usd") {
		document.getElementById("usd").className="deviseON";
		document.getElementById("euro").className="deviseOFF";
		document.getElementById("cours").dataset.devise="USD";
	}
	afficheCours();
}

/* Récupération des données de cours du Bitcoin */
function getCours() {
  /* Affichage de l'appel en cours */
  document.getElementById("info").innerHTML="Récupération de la valeur du Bitcoin...";
  document.getElementById("chrono").innerHTML=" ";
  
  /* Appel AJAX vers cryptocompare.com */
  var ajax = new XMLHttpRequest();
  ajax.onload = function() {
    if (this.status == 200) {
      var json=JSON.parse(this.response);
      var usd=json.USD, eur=json.EUR;
      var dt=new Date();
      var cours=document.getElementById("cours");
      cours.dataset.last=formatHeure(dt);
      cours.dataset.euro=json.EUR;
      cours.dataset.usd=json.USD; 
      cours.dataset.timestamp=dt.getTime()+60*1000; /* Timestamp du prochain appel ajax */
   	  afficheCours(); 
   	  chrono();
    }
  } 
  ajax.open("GET", "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,EUR", true);
  ajax.send();
}


/* Mise à jour de l'affichage */
function afficheCours() {
	/* Recherche des infos de cours à utiliser */
    var cours=document.getElementById("cours");
	var devise=cours.dataset.devise;
	if (devise=="EUR") {
		var valeur=formatNombre(cours.dataset.euro)+" €";
	}
	if (devise=="USD") {
		var valeur=formatNombre(cours.dataset.usd)+" $";
	}
	cours.innerHTML = valeur;
    document.getElementById("info").innerHTML = "Cours récupéré à "+cours.dataset.last;
}

/* Fonction de chrono qui affiche le temps restant et lance l'appel AJAX  */
function chrono() {	
	var dt=new Date();
	var timestamp=parseFloat(document.getElementById("cours").dataset.timestamp);
	var delai=Math.round((timestamp-dt.getTime())/1000);
	var chrono=document.getElementById("chrono");
	chrono.innerHTML="Prochaine mise à jour du cours dans "+delai+" seconde"+( delai>1 ? "s":"" );
	if (delai <= 0) {
		getCours();
	} else {
		setTimeout("chrono()", 500);	
	}	
}

/* Démarrage du traitement */
getCours();

Ce qui donne ce module :

Emulation de la console

Le module d'affichage du cours du Bitcoin propose les fonctionnalités suivantes :
- Recherche du cours du Bitcoin via un appel AJAX vers le service de cryptocompare.com
- Traitement du retour au format JSON
- Enregistrement des données de cours dans le dataset
- Mise à jour de l'affichage
- Détection du clic de l'utilisateur sur les boutons de devises EUR ou USD et mise à jour de l'affichage
- Lancement d'un chrono pour mettre à jour le cours toutes les 60 secondes

Ce petit script utilise, en plus du DHTML qui sera détaillé dans les prochains paragraphes, de nombreux éléments du JavaScript comme le traitement du format JSON, les appels AJAX avec XMLHttpRequest, la création de timers avec setTimeout(), le formatage de nombre avec NumberFormat et le formatage de date et heure avec DateTimeFormat.

Retrouvez ce script dans un formulaire de conversion automatique de montants en BTC/$/US

La compatibilité entre navigateurs

Un mot sur la compatibilité entre navigateurs est indispensable, tant le souvenir de différences flagrantes hantent encore les rêves de développeurs web de plus d'une dizaine d'années d'ancienneté.

Il n'est maintenant plus nécessaire de prévoir plusieurs versions radicalement différentes de script en détectant le navigateur

Maintenant tous les navigateurs récents sur PC sont compatibles entre eux, au moins pour les fonctionnalités de base présentées ici. Les navigateurs mobiles sont eux encore plus en avance.

Dans tous les cas, la bonne habitude de tester vos développements sur plusieurs appareils et dans différentes configurations de connectivité et de performance reste d'actualité : Un script validé sur PC avec une connexion fibre risque de ne pas être aussi agréable sur un smartphone peu puissant avec un accès réseau limité en situation de mobilité.

Accéder aux éléments du DOM

Trouver un élément par son identifiant

La première fonction à laquelle on pense en parlant de DHTML est getElementById("identifiant") qui retourne un objet JavaScript de type HTMLElement correspondant au premier élément HTML du document ayant la propriété id="identifiant". C'est cet objet que le JavaScript va pouvoir manipuler pour modifier son contenu, son apparence, sa position, ses classes CSS et même le détruire.

Ainsi l'objet div suivant :

<div id="monDiv">Contenu du DIV</div>	

sera accessible en JavaScript par cette simple ligne :

var element=document.getElementById("monDiv")

Attention, getElementById() retourne null si aucun élément n'est trouvé dans le document. Il n'est donc pas possible de l'utiliser par la suite sans provoquer une erreur JavaScript.

De nombreuses autres fonctions, au fonctionnement similaire, sont également disponibles pour faciliter l'accès à tous les éléments du document. Ces fonctions retournent un ou plusieurs objets HTMLElement.

Trouver un élément par son tag

Pour récupérer tous les éléments dont la balise est <tag>, utilisez getElementsByTagName("tag"). Notez le s à getElements qui indique que le retour est un tableau d'éléments HTML. Pour traiter chacun des éléments retournés, il sera nécessaire de faire une boucle sur le tableau ou d'utiliser la méthode rapide forEach().

Trouver un élément par ses propriétés CSS

Pour récupérer tous les éléments possédant une certaine classe, utilisez getElementsByClassName("className").

Les méthodes querySelector() et querySelectorAll() attendent en paramètre une chaîne de caractères au format CSS.

Attention avec les sélecteurs trop complexes : les incompatibilités sont possibles dans certains navigateurs qui n'implémentent cette recherche que de manière plus récente.

Modifier le contenu d'un élément

Le contenu d'un élément est accessible et modifiable par sa propriété innerHTML. Cette propriété contient une chaîne de caractères au format HTML qu'il est possible de modifier par programmation. Chaque modification a un impact direct dans le rendu sur la page.

Il est donc très facile de modifier le contenu d'un élément. Dans l'exemple du bitcoin, nous utilisons la syntaxe :

document.getElementById("cours").innerHTML=valeur+" $";

Stocker des informations dans le document

Il existe un ensemble de propriétés de balise valides au sens de la norme W3C sur les. Toutes ces propriétés de balise sont accessibles par la notation pointée sur dataset. L'exemple Bitcoin utilise largement cette possibilité pour stocker les informations reçues de l'appel AJAX et les réutiliser lors des interactions de l'utilisateurs.

Dans l'exemple Bitcoin, les informations sont stockées avec à partir du retour au format JSON :

var cours = document.getElementById("cours");
cours.dataset.euro=json.EUR;
cours.dataset.usd=json.USD;

Dans le document, l'élément HTML id="cours" contient ce type de valeurs :

<div id="cours" 
 data-timestamp="1511885079594" 
 data-last="17:03:39" 
 data-euro="8356.15" 
 data-usd="9902.12" 
 data-devise="USD">
    9 902,12 $
</div>

Vous pouvez l'observer facilement sur la page grâce à la console du navigateur et à l'inspecteur de document.

Modifier le style des éléments

Les classes d'un élément sont accessibles par className, chaîne de caractères équivalente à la propriété class de la balise ou par classList, tableau contenant toutes les classes de l'élément.

Tous les éléments de style d'un élément HTML sont accessibles par la propriété style. Le JavaScript reprend l'ensemble des propriétés CSS. Leur nom est identique. Il suffit de remplacer les - dans les noms CSS par une majscule. Ainsi, background-color devient style.backgroundColor en JS.

Il est temps de regrouper les premières notions de DHTML dans un exemple complet, avec quelques éléments HTML à manipuler :

<div id="monDiv" class="monDiv1 monDiv2"></div>

Et la partie script :

var monDiv=document.getElementById("monDiv");
console.log(monDiv);	
console.log(monDiv.classList);
monDiv.innerHTML="Contenu ajouté par le script";
monDiv.style.border="2px solid #E00";

Le script accède à l'élément monDiv, affiche ses classes dans la console, modifie son contenu et lui ajoute une bordure rouge :

Emulation de la console

Créer et supprimer des éléments

Si innerHTML permet de modifier le contenu d'un élément avec du code HTML et donc d'ajouter des éléments facilement, il existe d'autres fonctions plus efficaces pour créer des éléments et les ajouter précisément dans le document.

Crééer un élément

createElement(tagName) retourne un objet HTML ayant la balise tagName.

L'appel à setAttribute() permet de lui affecter des propriétés et leurs valeurs.

Ajouter un élément au document

La méthode appendChild(enfant) ajoute l'objet enfant en dernière position sur l'objet sur lequel elle est appelée.

La méthode insertBefore(enfant, position) ajoute l'objet enfant avant l'objet position.

Supprimer un élément

De manière réciproque, la méthode removeChild(enfant) supprime l'objet enfant de l'objet sur lequel elle est appelée.

Ce script va récupérer tous les éléments h2 de la page pour les surligner :

Avec la fonction de surlignage :

Cliquez sur le bouton pour surligner le plan de la page :

Emulation de la console

Créer des animations

Animations JavaScript

Il est possible d'animer des éléments de la page en utilisant le JavaScript pur, en incrémentant une variable ou en effectuant un calcul à chaque intervalle de temps. Voici un exemple pour faire apparaitre un élément div :

<div id="anim1">Mon élément</div>
<div class="btn radius btn-outline cursor-pointer" onclick="anim1()">Lancer l'animation JS</div>

Et la fonction anim1() :

var opacity1=1;
function anim1() {
	opacity1-=0.05;
	if (opacity1 < 0) {opacity1=0;}
	document.getElementById("anim1").style.opacity=opacity1;
	if (opacity1>0) {
		setTimeout("anim1()",50);	
	} else {
		opacity1=1;
		document.getElementById("anim1").style.opacity=1;
	}
}

Cliquez sur le bouton pour lancer l'animation :

Emulation de la console

La fonction lance un timer avec setTimeout() renouvelé tant que opacity1 est positif. Une fois que l'objet a complétement disparu, il est réaffiché.

Animation en CSS

Les navigateurs intègrent nativement des mécanismes d'optimisation des traitements graphiques bien plus performants avec les propriétés CSS, parfois en utilisant le co-processeur graphique de l'appareil de l'utilisateur. Les animations via CSS sont à privilégier : elles sont nettement plus fluides et moins gourmandes en puissance et en énergie.

Reprenons notre code HTML :

<div id="anim2">Mon élément</div>
<div class="btn radius btn-outline cursor-pointer" onclick="anim2()">Lancer l'animation CSS</div>

Voici la définition de l'animation CSS :

@keyframes disparition {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
.disparait {
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-delai: 0s;
  animation-iteration-count: 1;
  animation-name: disparition;
}

Ici, disparition est l'ensemble des étapes de l'animation simplifiée au maximum partant de from à to.

La classe disparait contient toutes les propriétés de l'animation.

Et la fonction anim2() qui affecte la classe CSS disparait à div#anim2 :

/* Détection du lancement et de la fin de l'animation */
document.getElementById("anim2").addEventListener("animationstart", 
function() {
  console.log("animation disparait START")
}, false);
document.getElementById("anim2").addEventListener("animationend", 
function() {
  console.log("animation disparait END")
  this.className="";
}, false);

function anim2() {
  document.getElementById("anim2").className="disparait";
}

Comparer les 2 exécutions, la version CSS est plus agréable à l'oeil, tout en étant moins gourmande pour le navigateur.


Emulation de la console

Notez que la console affiche un message grâce à la détection des événements de lancement et de fin de l'animation générée par addEventListener.

En plus de la fluidité, les animations CSS apportent une simplicité de programmation intéressante. Il est possible de créer des animations simplement par la déclaration CSS sans avoir de calcul de timing ou de transformation à effectuer (comme un code couleur RGB par exemple).

PS : Le but n'est pas ici de faire un cours sur les transformations et les transitions CSS, mais de montrer l'interaction puissante que le JavaScript peut apporter.

Trier dynamiquement un tableau de données

Les animations doivent être utilisées à bon escient. Le but d'un site classique est de rendre service à ses utilisateurs, pas de faire une démonstration technologique ou artistique.

Le DHTML permet de faire des traitements directement dans la page sur les données présentes et afficher un rendu très rapidement, sans avoir à faire appel au serveur et donc sans latence réseau supplémentaire.

Un bon exemple est le tri de tableau par clic sur les entêtes de colonnes. Imaginons un tableau de clients qui contient le nombre de commandes, le total de chiffre d'affaires et le panier moyen :

<table id="tritable">
  <tr>
    <th onclick="triTable(this, 0)">Client</th>
    <th onclick="triTable(this, 1)">Commandes</th>
    <th onclick="triTable(this, 2)">Chiffre Affaires</th>
    <th onclick="triTable(this, 3)">Panier Moyen</th>
  </tr>
  <tr><td>Raymond</td><td class="number">2</td><td class="number">250</td><td class="number">125</td></tr>
  <tr><td>Gérard</td><td class="number">1</td><td class="number">150</td><td class="number">150</td></tr>
  <tr><td>Lucie</td><td class="number">10</td><td class="number">850</td><td class="number">85</td></tr>
  <tr><td>Arthur</td><td class="number">5</td><td class="number">50</td><td class="number">10</td></tr>
</table>

Définissons la fonction de tri triTable() :

function triTable(th, colonne) { 
  /* th est l'élément cliqué qui permet de remonter dans le DOM à la table à trier */	
  /* colonne est le numéro de colonne à utiliser pour le tri */

  var table=th.parentNode.parentNode.parentNode;
  console.log(table.getAttribute("id"));	
  var tbody=table.getElementsByTagName("tbody")[0];
  var trs=tbody.getElementsByTagName("tr"); /* Liste des lignes */
  
  var triABulle=true; /* Indicateur que le tri n'est pas terminé */
  var nbMouvement=0;
  
  while (triABulle) {
    var isMouvement=false;
    for (var i=1; i < trs.length - 1; i++) {
	  var tdi=trs[i].getElementsByTagName("td")[colonne];
	  var tdiplus1=trs[i+1].getElementsByTagName("td")[colonne];
	  var ei=tdi.innerText.toLowerCase();
	  var eiplus1=tdiplus1.innerText.toLowerCase();
	  if (tdi.className=="number") { /* Conversion en nombres */
		  var ei=parseFloat(tdi.innerText);
		  var eiplus1=parseFloat(tdiplus1.innerText);
	  }
	  if ( ei > eiplus1 ) {
	    /* Un mouvement à effectuer a été détecté */
	    isMouvement=true;
	    nbMouvement++;
	    break;  /* La boucle for est interrompue */
	  }
    }

    if (isMouvement) { 
      /* il faut inverser les lignes i et i+1 */
      trs[i].parentNode.insertBefore(trs[i + 1], trs[i]);
      console.log("mouvement sur la ligne "+i);
    } else {
	   /* Pas de mouvement, le tri à bulles est terminé */ 
	   triABulle=false;
	}
  }
}

Le tableau s'affiche sous la forme classique. Cliquez sur les titres de colonnes pour changer l'ordre des lignes.

Emulation de la console

L'algorithme utilisé est le tri à bulles qui consiste à parcourir le tableau autant de fois que nécessaire pour remonter (comme une bulle) chaque élément selon son classement et le tri à effectuer. Le parcourt du tableau s'arrête quand il n'y a plus de mouvements de ligne.

Une fonction de quelques lignes appelée au clic d'une entête de colonne peut trier un tableau et être réutilisée partout.

Naturellement, cet exemple est simpliste, avec des capacités de tris limitées. Sur les dates par exemple, le tri ne serait pas correct et un traitement spécial (comme pour les nombres serait nécessaire). L'exemple ne gère pas non plus la pagination pour les tableaux très longs. On pourrait ajouter des flèches dans les titres de colonnes pour indiquer le sens et la colonne triée.

Les frameworks

Des développeurs de talent ont partagé leurs travaux sur l'optimisation des accès au DOM, en rendant le code plus compact, plus lisible et en gérant les cas particuliers entre navigateurs de manière transparente pour l'utilisateur.

Lisez l'introduction à jQuery, le framework indispensable à connaître.

Tutoriel écrit par webmaster mis à jour le

Troisième édition Tout JavaScript chez Dunod

Tout JavaScript le livre chez DunodEn savoir plus
Sortie le 4 janvier 2023

Version papier 29€90
Format électronique 22€99.

Commandez en ligne

Chercher une fonction, un objet, ...

Le 18/04/2024 21:04:17 sur php 7 en 24.2 ms