Manipuler le code HTML (partie 1/2)

Dans ce premier chapitre consacré à la manipulation du code HTML, nous allons voir le concept du DOM. Nous étudierons tout d'abord comment naviguer entre les différents nœuds qui composent une page HTML puis nous aborderons l'édition du contenu d'une page en ajoutant, modifiant et supprimant des nœuds.

Vous allez rapidement constater qu'il est plutôt aisé de manipuler le contenu d'une page web et que cela va vous devenir indispensable par la suite.

Le Document Object Model

Le Document Object Model (abrégé DOM) est une interface de programmation pour les documents XML et HTML.

Le DOM est donc une API qui s'utilise avec les documents XML et HTML, et qui va nous permettre, via le JavaScript, d'accéder au code XML et/ou HTML d'un document. C'est grâce au DOM que nous allons pouvoir modifier des éléments HTML (afficher ou masquer un<div>par exemple), en ajouter, en déplacer ou même en supprimer.

Petit historique

À l'origine, quand le JavaScript a été intégré dans les premiers navigateurs (Internet Explorer et Netscape Navigator), le DOM n'était pas unifié, c'est-à-dire que les deux navigateurs possédaient un DOM différent. Et donc, pour accéder à un élément HTML, la manière de faire différait d'un navigateur à l'autre, ce qui obligeait les développeurs Web à coder différemment en fonction du navigateur. En bref, c'était un peu la jungle.

Le W3C a mis de l'ordre dans tout ça, et a publié une nouvelle spécification que nous appellerons « DOM-1 » (pour DOM Level 1). Cette nouvelle spécification définit clairement ce qu'est le DOM, et surtout comment un document HTML ou XML est schématisé. Depuis lors, un document HTML ou XML est représenté sous la forme d'un arbre, ou plutôt hiérarchiquement. Ainsi, l'élément<html>contient deux éléments enfants :<head>et<body>, qui à leur tour contiennent d'autres éléments enfants.

Ensuite, la spécification DOM-2 a été publiée. La grande nouveauté de cette version 2 est l'introduction de la méthodegetElementById()qui permet de récupérer un élément HTML ou XML en connaissant son ID.

L'objetwindow

Avant de véritablement parler du document, c'est-à-dire de la page Web, nous allons parler de l'objetwindow. L'objetwindowest ce qu'on appelle un objet global qui représente la fenêtre du navigateur. C'est à partir de cet objet que le JavaScript est exécuté.

Si nous reprenons notre « Hello World! » du début, nous avons :

<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<script>
alert('Hello world!');
</script>
</body>
</html>

Contrairement à ce qui a été dit dans ce cours,alert()n'est pas vraiment une fonction. Il s'agit en réalité d'une méthode appartenant à l'objetwindow. Mais l'objetwindowest dit implicite, c'est-à-dire qu'il n'y a généralement pas besoin de le spécifier. Ainsi, ces deux instructions produisent le même effet, à savoir ouvrir une boîte de dialogue :

window.alert('Hello world!');
alert('Hello world!');

Puisqu'il n'est pas nécessaire de spécifier l'objetwindow, on ne le fait généralement pas sauf si cela est nécessaire, par exemple si on manipule des frames.

De même, lorsque vous déclarez une variable dans le contexte global de votre script, cette variable deviendra en vérité une propriété de l'objetwindow. Afin de vous démontrer facilement la chose, regardez donc ceci :

var text = 'Variable globale !';
(function() { // On utilise une IIFE pour « isoler » du code
var text = 'Variable locale !';
alert(text); // Forcément, la variable locale prend le dessus
alert(window.text); // Mais il est toujours possible d'accéder à la variable globale grâce à l'objet « window »
})();

Une dernière chose importante qu'il vous faut mémoriser : toute variable non déclarée (donc utilisée sans jamais écrire le mot-clévar) deviendra immédiatement une propriété de l'objetwindow, et ce, quel que soit l'endroit où vous utilisez cette variable ! Prenons un exemple simple :

(function() { // On utilise une IIFE pour « isoler » du code
text = 'Variable accessible !'; // Cette variable n'a jamais été déclarée et pourtant on lui attribue une valeur
})();
alert(text); // Affiche : « Variable accessible ! »

Notre variable a été utilisée pour la première fois dans une IIFE et pourtant nous y avons accès depuis l'espace global ! Alors pourquoi cela fonctionne-t-il de cette manière ? Tout simplement parce que le JavaScript va chercher à résoudre le problème que nous lui avons donné : on lui demande d'attribuer une valeur à la variabletext, il va donc chercher cette variable mais ne la trouve pas, la seule solution pour résoudre le problème qui lui est donné est alors d'utiliser l'objetwindow. Ce qui veut dire qu'en écrivant :

text = 'Variable accessible !';

le code sera alors interprété de cette manière si aucune variable accessible n'existe avec ce nom :

window.text = 'Variable accessible !';

Si nous vous montrons cette particularité du JavaScript c'est pour vous conseiller de ne pas vous en servir ! Si vous n'utilisez jamais le mot-clévaralors vous allez très vite arriver à de grandes confusions dans votre code (et à de nombreux bugs). Si vous souhaitez déclarer une variable dans l'espace global alors que vous vous trouvez actuellement dans un autre espace (une IIFE, par exemple), spécifiez donc explicitement l'objetwindow. Le reste du temps, pensez bien à écrire le mot-clévar.

Le document

L'objetdocumentest un sous-objet dewindow, l'un des plus utilisés. Et pour cause, il représente la page Web et plus précisément la balise<html>. C'est grâce à cet élément-là que nous allons pouvoir accéder aux éléments HTML et les modifier. Voyons donc, dans la sous-partie suivante, comment naviguer dans le document.

Naviguer dans le document

La structure DOM

Comme il a été dit précédemment, le DOM pose comme concept que la page Web est vue comme un arbre, comme une hiérarchie d'éléments. On peut donc schématiser une page Web simple comme ceci :

Une page Web peut être vue comme un arbre
Une page Web peut être vue comme un arbre

Voici le code source de la page :

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Le titre de la page</title>
</head>
<body>
<div>
<p>Un peu de texte <a>et un lien</a></p>
</div>
</body>
</html>

Le schéma est plutôt simple : l'élément<html>contient deux éléments, appelés enfants :<head>et<body>. Pour ces deux enfants,<html>est l'élément parent. Chaque élément est appelé nœud (node en anglais). L'élément<head>contient lui aussi deux enfants :<meta>et<title>.<meta>ne contient pas d'enfant tandis que<title>en contient un, qui s’appelle#text. Comme son nom l'indique,#textest un élément qui contient du texte.

Il est important de bien saisir cette notion : le texte présent dans une page Web est vu par le DOM comme un nœud de type#text. Dans le schéma précédent, l'exemple du paragraphe qui contient du texte et un lien illustre bien cela :

<p>
Un peu de texte
<a>et un lien</a>
</p>

Si on va à la ligne après chaque nœud, on remarque clairement que l'élément<p>contient deux enfants :#textqui contient « Un peu de texte » et<a>, qui lui-même contient un enfant#textreprésentant « et un lien ».

Accéder aux éléments

L'accès aux éléments HTML via le DOM est assez simple mais demeure actuellement plutôt limité. L'objetdocumentpossède trois méthodes principales :getElementById(),getElementsByTagName()etgetElementsByName().

getElementById()

Cette méthode permet d'accéder à un élément en connaissant son ID qui est simplement l'attributidde l'élément. Cela fonctionne de cette manière :

<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
alert(div);
</script>

Essayer le code

En exécutant ce code, le navigateur affiche ceci :

Notre div est bien un objet de type HTMLDivElement
Notre div est bien un objet de type HTMLDivElement

Il nous dit quedivest un objet de typeHTMLDivElement. En clair, c'est un élément HTML qui se trouve être un<div>, ce qui nous montre que le script fonctionne correctement.

getElementsByTagName()

Cette méthode permet de récupérer, sous la forme d'un tableau, tous les éléments de la famille. Si, dans une page, on veut récupérer tous les<div>, il suffit de faire comme ceci :

var divs = document.getElementsByTagName('div');
for (var i = 0, c = divs.length ; i < c ; i++) {
alert('Element n° ' + (i + 1) + ' : ' + divs[i]);
}

Essayer le code

La méthode retourne une collection d'éléments (utilisable de la même manière qu'un tableau). Pour accéder à chaque élément, il est nécessaire de parcourir le tableau avec une petite boucle.

Deux petites astuces :

  1. Cette méthode est accessible sur n'importe quel élément HTML et pas seulement sur l'objetdocument.

  2. En paramètre de cette méthode vous pouvez mettre une chaîne de caractères contenant un astérisque * qui récupérera tous les éléments HTML contenus dans l'élément ciblé.

getElementsByName()

Cette méthode est semblable àgetElementsByTagName()et permet de ne récupérer que les éléments qui possèdent un attributnameque vous spécifiez. L'attributnamen'est utilisé qu'au sein des formulaires, et est déprécié depuis la spécification HTML5 dans tout autre élément que celui d'un formulaire. Par exemple, vous pouvez vous en servir pour un élément<input>mais pas pour un élément<map>.

Sachez aussi que cette méthode est dépréciée en XHTML mais est standardisée en HTML5.

Accéder aux éléments grâce aux technologies récentes

Ces dernières années, le JavaScript a beaucoup évolué pour faciliter le développement Web. Les deux méthodes que nous allons étudier sont récentes et ne sont pas supportées par les très vieilles versions des navigateurs, leur support commence à partir de la version 8 d'Internet Explorer, pour les autres navigateurs vous n'avez normalement pas de soucis à vous faire.

Ces deux méthodes sontquerySelector()etquerySelectorAll()et ont pour particularité de grandement simplifier la sélection d'éléments dans l'arbre DOM grâce à leur mode de fonctionnement. Ces deux méthodes prennent pour paramètre un seul argument : une chaîne de caractères !

Cette chaîne de caractères doit être un sélecteur CSS comme ceux que vous utilisez dans vos feuilles de style. Exemple :

#menu .item span

Ce sélecteur CSS stipule que l'on souhaite sélectionner les balises de type<span>contenues dans les classes.itemelles-mêmes contenues dans un élément dont l'identifiant est#menu.

Le principe est plutôt simple mais très efficace. Sachez que ces deux méthodes supportent aussi les sélecteurs CSS 3, bien plus complets ! Vous pouvez consulter leur liste sur la spécification du W3C .

Voyons maintenant les particularités de ces deux méthodes. La première,querySelector(), renvoie le premier élément trouvé correspondant au sélecteur CSS, tandis quequerySelectorAll()va renvoyer tous les éléments (sous forme de tableau) correspondant au sélecteur CSS fourni. Prenons un exemple simple :

<div id="menu">
<div class="item">
<span>Élément 1</span>
<span>Élément 2</span>
</div>
<div class="publicite">
<span>Élément 3</span>
<span>Élément 4</span>
</div>
</div>
<div id="contenu">
<span>Introduction au contenu de la page...</span>
</div>

Maintenant, essayons le sélecteur CSS présenté plus haut :#menu .item span

var query = document.querySelector('#menu .item span'),
queryAll = document.querySelectorAll('#menu .item span');
alert(query.innerHTML); // Affiche : "Élément 1"
alert(queryAll.length); // Affiche : "2"
alert(queryAll[0].innerHTML + ' - ' + queryAll[1].innerHTML); // Affiche : "Élément 1 - Élément 2"

Nous obtenons bien les résultats escomptés ! Nous vous conseillons de bien vous rappeler ces deux méthodes. Elles sont déjà utiles sur des projets voués à tourner sur des navigateurs récents, et d'ici à quelques années elles pourraient bien devenir habituelles (le temps que les vieilles versions des navigateurs disparaissent pour de bon).

L'héritage des propriétés et des méthodes

Le JavaScript voit les éléments HTML comme étant des objets, cela veut donc dire que chaque élément HTML possède des propriétés et des méthodes. Cependant faites bien attention parce que tous ne possèdent pas les mêmes propriétés et méthodes. Certaines sont néanmoins communes à tous les éléments HTML, car tous les éléments HTML sont d'un même type : le typeNode, qui signifie « nœud » en anglais.

Notion d'héritage

Nous avons vu qu'un élément<div>est un objetHTMLDivElement, mais un objet, en JavaScript, peut appartenir à différents groupes. Ainsi, notre<div>est unHTMLDivElement, qui est un sous-objet d'HTMLElementqui est lui-même un sous-objet d'Element.Elementest enfin un sous-objet deNode. Ce schéma est plus parlant :

En Javascript, un objet peut appartenir à plusieurs groupes
En JavaScript, un objet peut appartenir à plusieurs groupes

L'objetNodeapporte un certain nombre de propriétés et de méthodes qui pourront être utilisées depuis un de ses sous-objets. En clair, les sous-objets héritent des propriétés et méthodes de leurs objets parents. Voilà donc ce que l'on appelle l'héritage.

Éditer les éléments HTML

Maintenant que nous avons vu comment accéder à un élément, nous allons voir comment l'éditer. Les éléments HTML sont souvent composés d'attributs (l'attributhrefd'un<a>par exemple), et d'un contenu, qui est de type#text. Le contenu peut aussi être un autre élément HTML.

Comme dit précédemment, un élément HTML est un objet qui appartient à plusieurs objets, et de ce fait, qui hérite des propriétés et méthodes de ses objets parents.

Les attributs

Via l'objetElement

Pour interagir avec les attributs, l'objetElementnous fournit deux méthodes,getAttribute()etsetAttribute()permettant respectivement de récupérer et d'éditer un attribut. Le premier paramètre est le nom de l'attribut, et le deuxième, dans le cas desetAttribute()uniquement, est la nouvelle valeur à donner à l'attribut. Petit exemple :

<body>
<a id="myLink" href="http://www.un_lien_quelconque.com">Un lien modifié dynamiquement</a>
<script>
var link = document.getElementById('myLink');
var href = link.getAttribute('href'); // On récupère l'attribut « href »
alert(href);
link.setAttribute('href', 'http://www.siteduzero.com'); // On édite l'attribut « href »
</script>
</body>

On commence par récupérer l'élémentmyLink, et on lit son attributhrefviagetAttribute(). Ensuite on modifie la valeur de l'attributhrefavecsetAttribute(). Le lien pointe maintenant vershttp://www.siteduzero.com.

Les attributs accessibles

En fait, pour la plupart des éléments courants comme<a>, il est possible d'accéder à un attribut via une propriété. Ainsi, si on veut modifier la destination d'un lien, on peut utiliser la propriétéhref, comme ceci :

<body>
<a id="myLink" href="http://www.un_lien_quelconque.com">Un lien modifié dynamiquement</a>
<script>
var link = document.getElementById('myLink');
var href = link.href;
alert(href);
link.href = 'http://www.siteduzero.com';
</script>
</body>

Essayer le code

C'est cette façon de faire que l'on utilisera majoritairement pour les formulaires : pour récupérer ou modifier la valeur d'un champ, on utilisera la propriétévalue.

La classe

On peut penser que pour modifier l'attributclassd'un élément HTML, il suffit d'utiliserelement.class. Ce n'est pas possible, car le mot-cléclassest réservé en JavaScript, bien qu'il n'ait aucune utilité. À la place declass, il faudra utiliserclassName.

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Le titre de la page</title>
<style>
.blue {
background: blue;
color: white;
}
</style>
</head>
<body>
<div id="myColoredDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
document.getElementById('myColoredDiv').className = 'blue';
</script>
</body>
</html>

Dans cet exemple, on définit la classe CSS.blueà l'élémentmyColoredDiv, ce qui fait que cet élément sera affiché avec un arrière-plan bleu et un texte blanc.

Faites attention : si votre élément comporte plusieurs classes (exemple :<a class="external red u">) et que vous récupérez la classe avecclassName, cette propriété ne retournera pas un tableau avec les différentes classes, mais bien la chaîne « external red u », ce qui n'est pas vraiment le comportement souhaité. Il vous faudra alors couper cette chaîne avec la méthodesplit()pour obtenir un tableau, comme ceci :

var classes = document.getElementById('myLink').className;
var classesNew = [];
classes = classes.split(' ');
for (var i = 0, c = classes.length; i < c; i++) {
if (classes[i]) {
classesNew.push(classes[i]);
}
}
alert(classesNew);

Essayer le code

Là, on récupère les classes, on découpe la chaîne, mais comme il se peut que plusieurs espaces soient présents entre chaque nom de classe, on vérifie chaque élément pour voir s'il contient quelque chose (s'il n'est pas vide). On en profite pour créer un nouveau tableau,classesNew, qui contiendra les noms des classes, sans « parasites ».

Si le support d'Internet Explorer avant sa version 10 vous importe peu, vous pouvez aussi vous tourner vers la propriétéclassList  qui permet de consulter les classes sous forme d'un tableau et de les manipuler aisément :

var div = document.querySelector('div');
// Ajoute une nouvelle classe
div.classList.add('new-class');
// Retire une classe
div.classList.remove('new-class');
// Retire une classe si elle est présente ou bien l'ajoute si elle est absente
div.classList.toggle('toggled-class');
// Indique si une classe est présente ou non
if (div.classList.contains('old-class')) {
alert('La classe .old-class est présente !');
}
// Parcourt et affiche les classes CSS
var result = '';
for (var i = 0; i < div.classList.length; i++) {
result += '.' + div.classList[i] + '\n';
}
alert(result);

Le contenu :innerHTML

La propriétéinnerHTMLest spéciale et demande une petite introduction. Elle a été créée par Microsoft pour les besoins d'Internet Explorer et a été normalisée au sein du HTML5. Bien que non normalisée pendant des années, elle est devenue un standard parce que tous les navigateurs la supportaient déjà, et non l'inverse comme c'est généralement le cas.

Récupérer du HTML

innerHTMLpermet de récupérer le code HTML enfant d'un élément sous forme de texte. Ainsi, si des balises sont présentes,innerHTMLles retournera sous forme de texte :

<body>
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
alert(div.innerHTML);
</script>
</body>

Nous avons donc bien une boîte de dialogue qui affiche le contenu demyDiv, sous forme de texte :

Le contenu de myDiv est bien affiché
Le contenu de myDiv est bien affiché
Ajouter ou éditer du HTML

Pour éditer ou ajouter du contenu HTML, il suffit de faire l'inverse, c'est-à-dire de définir un nouveau contenu :

document.getElementById('myDiv').innerHTML = '<blockquote>Je mets une citation à la place du paragraphe</blockquote>';

Si vous voulez ajouter du contenu, et ne pas modifier le contenu déjà en place, il suffit d’utiliser+=à la place de l'opérateur d'affectation :

document.getElementById('myDiv').innerHTML += ' et <strong>une portion mise en emphase</strong>.';

Toutefois, une petite mise en garde : il ne faut pas utiliser le += dans une boucle ! En effet,innerHTMLralentit considérablement l'exécution du code si l'on opère de cette manière, il vaut donc mieux concaténer son texte dans une variable pour ensuite ajouter le tout viainnerHTML. Exemple :

var text = '';
while ( /* condition */ ) {
text += 'votre_texte'; // On concatène dans la variable « text »
}
element.innerHTML = text; // Une fois la concaténation terminée, on ajoute le tout à « element » via innerHTML

innerText et textContent

Penchons-nous maintenant sur deux propriétés analogues àinnerHTML:innerTextpour Internet Explorer ettextContentpour les autres navigateurs.

innerText

La propriétéinnerTexta aussi été introduite dans Internet Explorer, mais à la différence de sa propriété sœurinnerHTML, elle n'a jamais été standardisée et n'est pas supportée par tous les navigateurs. Internet Explorer (pour toute version antérieure à la neuvième) ne supporte que cette propriété et non pas la version standardisée que nous verrons par la suite.

Le fonctionnement d'innerTextest le même qu'innerHTMLexcepté le fait que seul le texte est récupéré, et non les balises. C'est pratique pour récupérer du contenu sans le balisage, petit exemple :

<body>
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
alert(div.innerText);
</script>
</body>

Ce qui nous donne bien « Un peu de texte et un lien », sans les balises :

Le texte est affiché sans les balises HTML
Le texte est affiché sans les balises HTML

textContent

La propriététextContentest la version standardisée d'innerText; elle est reconnue par tous les navigateurs à l'exception des versions d'Internet Explorer antérieures à la 9. Le fonctionnement est évidemment le même. Maintenant une question se pose : comment faire un script qui fonctionne à la fois pour Internet Explorer et les autres navigateurs ? C'est ce que nous allons voir !

Tester le navigateur

Il est possible via une simple condition de tester si le navigateur prend en charge telle ou telle méthode ou propriété.

<body>
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
var txt = '';
if (div.textContent) { // « textContent » existe ? Alors on s'en sert !
txt = div.textContent;
} else if (div.innerText) { // « innerText » existe ? Alors on doit être sous IE.
txt = div.innerText + ' [via Internet Explorer]';
} else { // Si aucun des deux n'existe, cela est sûrement dû au fait qu'il n'y a pas de texte
txt = ''; // On met une chaîne de caractères vide
}
alert(txt);
</script>
</body>

Il suffit donc de tester par le biais d'une condition si l'instruction fonctionne. SitextContentne fonctionne pas, pas de soucis, on prendinnerText:

La fenêtre s'affiche avec Internet Explorer
La fenêtre s'affiche avec Internet Explorer

Cela dit, ce code est quand même très long et redondant. Il est possible de le raccourcir de manière considérable :

txt = div.textContent || div.innerText || '';
En résumé
  • Le DOM va servir à accéder aux éléments HTML présents dans un document afin de les modifier et d'interagir avec eux.

  • L'objetwindowest un objet global qui représente la fenêtre du navigateur.document, quant à lui, est un sous-objet dewindowet représente la page Web. C'est grâce à lui que l'on va pouvoir accéder aux éléments HTML de la page Web.

  • Les éléments de la page sont structurés comme un arbre généalogique, avec l'élément<html>comme élément fondateur.

  • Différentes méthodes, commegetElementById(),getElementsByTagName(),querySelector()ouquerySelectorAll(), sont disponibles pour accéder aux éléments.

  • Les attributs peuvent tous être modifiés grâce àsetAttribute(). Certains éléments possèdent des propriétés qui permettent de modifier ces attributs.

  • La propriétéinnerHTMLpermet de récupérer ou de définir le code HTML présent à l'intérieur d'un élément.

  • De leur côté,textContentetinnerTextne sont capables que de définir ou récupérer du texte brut, sans aucunes balises HTML.