Le DHTML, signifie simplement Dynamic HTML. Il ne s'agit pas d'un langage particulier, mais d'une notion de d騅eloppement web qui permet la manipulation de la page, de ses 駘駑ents, de leur contenu et leur apparence, une fois que la page est charg馥.
Historiquement, le HTML ne permettait pas de modifier le contenu d'une page une fois que l'騅駭ement onload 騁ait d馗lench?. La seule solution pour le d騅eloppeur 騁ait d'utiliser des formulaires pour y faire apparaitre des r駸ultats.
Gr稍e 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馗utables traditionnels, comme les outils de messagerie mail, d'馘iteurs de document, les tableurs ou m麥e les sites comme Facebook.
Utilisons un premier exemple simple de ce que permet le DHTML : mise ? jour de contenu, modification de l'apparence, r饌ction aux actions de l'utilisateur.
J'ai commenc? la mise ? jour de ce tutoriel le jour o? le Bitcoin a d駱ass? les 10 000 $ (le 28/11/2018). L'occasion 騁ait 騅idente 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馗up駻ation 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馗up駻ation des donn馥s de cours du Bitcoin */
function getCours() {
/* Affichage de l'appel en cours */
document.getElementById("info").innerHTML="R馗up駻ation 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馗up駻? ? "+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駑arrage du traitement */
getCours();
Ce qui donne ce module :
Le module d'affichage du cours du Bitcoin propose les fonctionnalit駸 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馥s de cours dans le dataset
- Mise ? jour de l'affichage
- D騁ection 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騁aill? dans les prochains paragraphes, de nombreux 駘駑ents du JavaScript comme le traitement du format JSON, les appels AJAX avec XMLHttpRequest, la cr饌tion 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
Un mot sur la compatibilit? entre navigateurs est indispensable, tant le souvenir de diff駻ences flagrantes hantent encore les r黐es de d騅eloppeurs web de plus d'une dizaine d'ann馥s d'anciennet?.
Il n'est maintenant plus n馗essaire de pr騅oir plusieurs versions radicalement diff駻entes de script en d騁ectant le navigateur
Maintenant tous les navigateurs r馗ents sur PC sont compatibles entre eux, au moins pour les fonctionnalit駸 de base pr駸ent馥s ici. Les navigateurs mobiles sont eux encore plus en avance.
Dans tous les cas, la bonne habitude de tester vos d騅eloppements sur plusieurs appareils et dans diff駻entes configurations de connectivit? et de performance reste d'actualit? : Un script valid? sur PC avec une connexion fibre risque de ne pas 黎re aussi agr饌ble sur un smartphone peu puissant avec un acc鑚 r駸eau limit? en situation de mobilit?.
La premi鑽e fonction ? laquelle on pense en parlant de DHTML est getElementById("identifiant") qui retourne un objet JavaScript de type HTMLElement correspondant au premier 駘駑ent HTML du document ayant la propri騁? 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麥e le d騁ruire.
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 駘駑ent 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 馮alement disponibles pour faciliter l'acc鑚 ? tous les 駘駑ents du document. Ces fonctions retournent un ou plusieurs objets HTMLElement.
Pour r馗up駻er tous les 駘駑ents dont la balise est <tag>, utilisez getElementsByTagName("tag"). Notez le s ? getElements qui indique que le retour est un tableau d'駘駑ents HTML. Pour traiter chacun des 駘駑ents retourn駸, il sera n馗essaire de faire une boucle sur le tableau ou d'utiliser la m騁hode rapide forEach().
Pour r馗up駻er tous les 駘駑ents poss馘ant une certaine classe, utilisez getElementsByClassName("className").
Les m騁hodes querySelector() et querySelectorAll() attendent en param鑼re une cha?ne de caract鑽es au format CSS.
Attention avec les s駘ecteurs trop complexes : les incompatibilit駸 sont possibles dans certains navigateurs qui n'impl駑entent cette recherche que de mani鑽e plus r馗ente.
Le contenu d'un 駘駑ent est accessible et modifiable par sa propri騁? innerHTML. Cette propri騁? contient une cha?ne de caract鑽es 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鑚 facile de modifier le contenu d'un 駘駑ent. Dans l'exemple du bitcoin, nous utilisons la syntaxe :
document.getElementById("cours").innerHTML=valeur+" $";
Il existe un ensemble de propri騁駸 de balise valides au sens de la norme W3C sur les. Toutes ces propri騁駸 de balise sont accessibles par la notation point馥 sur dataset. L'exemple Bitcoin utilise largement cette possibilit? pour stocker les informations re輹es de l'appel AJAX et les r騏tiliser lors des interactions de l'utilisateurs.
Dans l'exemple Bitcoin, les informations sont stock馥s 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'駘駑ent 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稍e ? la console du navigateur et ? l'inspecteur de document.
Les classes d'un 駘駑ent sont accessibles par className, cha?ne de caract鑽es 駲uivalente ? la propri騁? class de la balise ou par classList, tableau contenant toutes les classes de l'駘駑ent.
Tous les 駘駑ents de style d'un 駘駑ent HTML sont accessibles par la propri騁? style. Le JavaScript reprend l'ensemble des propri騁駸 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鑽es notions de DHTML dans un exemple complet, avec quelques 駘駑ents 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鐡e ? l'駘駑ent monDiv, affiche ses classes dans la console, modifie son contenu et lui ajoute une bordure rouge :
Si innerHTML permet de modifier le contenu d'un 駘駑ent avec du code HTML et donc d'ajouter des 駘駑ents facilement, il existe d'autres fonctions plus efficaces pour cr馥r des 駘駑ents et les ajouter pr馗is駑ent dans le document.
createElement(tagName) retourne un objet HTML ayant la balise tagName.
L'appel ? setAttribute() permet de lui affecter des propri騁駸 et leurs valeurs.
La m騁hode appendChild(enfant) ajoute l'objet enfant en derni鑽e position sur l'objet sur lequel elle est appel馥.
La m騁hode insertBefore(enfant, position) ajoute l'objet enfant avant l'objet position.
De mani鑽e r馗iproque, la m騁hode removeChild(enfant) supprime l'objet enfant de l'objet sur lequel elle est appel馥.
h2 de la page pour les surligner :
<button class="btn radius btn-outline cursor-pointer" onclick="surlignerMenu()">Surligner le plan</button>
Avec la fonction de surlignage :
function surlignerMenu() {
h2s=document.getElementsByTagName("h2");
console.log(h2s.length+" 駘駑ents h2 trouv駸 dans la page ");
for (var i=0; i<h2s.length; i++) {
h2s[i].className+=" H2ON"; /* Ajout de la classe de surlignage */
/* Cr饌tion du picto h2 */
var elt=document.createElement("div");
elt.innerHTML="h2";
elt.className="h2Info";
/* Ajout du picto dans l'駘駑ent de plan */
h2s[i].appendChild(elt);
}
}
Cliquez sur le bouton pour surligner le plan de la page :
Il est possible d'animer des 駘駑ents de la page en utilisant le JavaScript pur, en incr駑entant une variable ou en effectuant un calcul ? chaque intervalle de temps. Voici un exemple pour faire apparaitre un 駘駑ent div :
<div id="anim1">Mon 駘駑ent</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 :
La fonction lance un timer avec setTimeout() renouvel? tant que opacity1 est positif. Une fois que l'objet a compl騁ement disparu, il est r饌ffich?.
Les navigateurs int鑒rent nativement des m馗anismes d'optimisation des traitements graphiques bien plus performants avec les propri騁駸 CSS, parfois en utilisant le co-processeur graphique de l'appareil de l'utilisateur. Les animations via CSS sont ? privil馮ier : elles sont nettement plus fluides et moins gourmandes en puissance et en 駭ergie.
Reprenons notre code HTML :
<div id="anim2">Mon 駘駑ent</div> <div class="btn radius btn-outline cursor-pointer" onclick="anim2()">Lancer l'animation CSS</div>
Voici la d馭inition 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 騁apes de l'animation simplifi馥 au maximum partant de from ? to.
La classe disparait contient toutes les propri騁駸 de l'animation.
Et la fonction anim2() qui affecte la classe CSS disparait ? div#anim2 :
/* D騁ection 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馗utions, la version CSS est plus agr饌ble ? l'oeil, tout en 騁ant moins gourmande pour le navigateur.
Notez que la console affiche un message gr稍e ? la d騁ection des 騅駭ements de lancement et de fin de l'animation g駭駻馥 par addEventListener.
En plus de la fluidit?, les animations CSS apportent une simplicit? de programmation int駻essante. Il est possible de cr馥r des animations simplement par la d馗laration 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.
Les animations doivent 黎re utilis馥s ? bon escient. Le but d'un site classique est de rendre service ? ses utilisateurs, pas de faire une d駑onstration technologique ou artistique.
Le DHTML permet de faire des traitements directement dans la page sur les donn馥s pr駸entes et afficher un rendu tr鑚 rapidement, sans avoir ? faire appel au serveur et donc sans latence r駸eau suppl駑entaire.
Un bon exemple est le tri de tableau par clic sur les ent黎es 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駻ard</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馭inissons la fonction de tri triTable() :
function triTable(th, colonne) {
/* th est l'駘駑ent cliqu? qui permet de remonter dans le DOM ? la table ? trier */
/* colonne est le num駻o 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 騁? d騁ect? */
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.
L'algorithme utilis? est le tri ? bulles qui consiste ? parcourir le tableau autant de fois que n馗essaire pour remonter (comme une bulle) chaque 駘駑ent selon son classement et le tri ? effectuer. Le parcourt du tableau s'arr黎e quand il n'y a plus de mouvements de ligne.
Une fonction de quelques lignes appel馥 au clic d'une ent黎e de colonne peut trier un tableau et 黎re r騏tilis馥 partout.
Naturellement, cet exemple est simpliste, avec des capacit駸 de tris limit馥s. Sur les dates par exemple, le tri ne serait pas correct et un traitement sp馗ial (comme pour les nombres serait n馗essaire). L'exemple ne g鑽e pas non plus la pagination pour les tableaux tr鑚 longs. On pourrait ajouter des fl鐵hes dans les titres de colonnes pour indiquer le sens et la colonne tri馥.
Des d騅eloppeurs de talent ont partag? leurs travaux sur l'optimisation des acc鑚 au DOM, en rendant le code plus compact, plus lisible et en g駻ant les cas particuliers entre navigateurs de mani鑽e transparente pour l'utilisateur.
Lisez l'introduction ? jQuery, le framework indispensable ? conna?tre.