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 (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.
À 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.
window
Avant de véritablement parler du document, c'est-à-dire de la page
Web, nous allons parler de l'objetwindow
.
L'objetwindow
est 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>
Hello World!
alert('Hello world!');
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'objetwindow
est 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évar
alors 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
.
L'objetdocument
est 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.
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 :
Voici le code source de la page :
<!doctype html>
charset="utf-8"
Le titre de la page
Un peu de texte et un lien
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,#text
est
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 :
Un peu de texte
et un lien
Si on va à la ligne après chaque nœud,
on remarque clairement que l'élément<p>
contient deux enfants
:#text
qui contient « Un peu de texte » et<a>
,
qui lui-même contient un enfant#text
représentant « et un lien ».
L'accès aux éléments HTML via le DOM est assez simple mais demeure
actuellement plutôt limité. L'objetdocument
possè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'attributid
de l'élément. Cela fonctionne de cette manière :
id="myDiv"
Un peu de texte et un lien
var div = document.getElementById('myDiv');
alert(div);
En exécutant ce code, le navigateur affiche ceci :
Il nous dit
quediv
est 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]);
}
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 :
Cette méthode
est accessible sur n'importe quel élément HTML et pas seulement sur
l'objetdocument
.
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 attributname
que vous spécifiez.
L'attributname
n'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.
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.item
elles-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 :
id="menu"
class="item"
Élément 1
Élément 2
class="publicite"
Élément 3
Élément 4
id="contenu"
Introduction au contenu de la page...
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).
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.
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'HTMLElement
qui est lui-même un sous-objet
d'Element
.Element
est
enfin un sous-objet deNode
. Ce schéma est plus parlant :
L'objetNode
apporte
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.
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'attributhref
d'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.
Element
Pour interagir avec les attributs,
l'objetElement
nous 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 :
id="myLink" href="http://www.un_lien_quelconque.com"Un lien modifié dynamiquement
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 »
On commence par récupérer
l'élémentmyLink
, et on lit son attributhref
viagetAttribute()
.
Ensuite on modifie la valeur de
l'attributhref
avecsetAttribute()
.
Le lien pointe maintenant vershttp://www.siteduzero.com
.
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 :
id="myLink" href="http://www.un_lien_quelconque.com"Un lien modifié dynamiquement
var link = document.getElementById('myLink');
var href = link.href;
alert(href);
link.href = 'http://www.siteduzero.com';
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
.
On
peut penser que pour modifier l'attributclass
d'un élément HTML, il suffit
d'utiliserelement.class
. Ce n'est pas possible, car le
mot-cléclass
est réservé en JavaScript, bien qu'il n'ait aucune
utilité. À la place declass
, il faudra
utiliserclassName
.
<!doctype html>
charset="utf-8"
Le titre de la page
.blue {
background: blue;
color: white;
}
id="myColoredDiv"
Un peu de texte et un lien
document.getElementById('myColoredDiv').className = 'blue';
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);
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);
innerHTML
La
propriétéinnerHTML
est 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.
innerHTML
permet de
récupérer le code HTML enfant d'un élément sous forme de texte. Ainsi, si des balises sont
présentes,innerHTML
les retournera sous forme de texte :
id="myDiv"
Un peu de texte et un lien
var div = document.getElementById('myDiv');
alert(div.innerHTML);
Nous avons donc bien une boîte de
dialogue qui affiche le contenu demyDiv
, sous forme de texte :
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,innerHTML
ralentit
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
Penchons-nous maintenant sur deux propriétés analogues
àinnerHTML
:innerText
pour
Internet Explorer ettextContent
pour les autres navigateurs.
innerText
La propriétéinnerText
a
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'innerText
est le même qu'innerHTML
excepté
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 :
id="myDiv"
Un peu de texte et un lien
var div = document.getElementById('myDiv');
alert(div.innerText);
Ce qui nous donne bien « Un peu de texte et un lien », sans les balises :
textContent
La
propriététextContent
est 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 !
Il est possible via une simple condition de tester si le navigateur prend en charge telle ou telle méthode ou propriété.
id="myDiv"
Un peu de texte et un lien
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);
Il suffit donc de tester par le biais
d'une condition si l'instruction fonctionne. SitextContent
ne
fonctionne pas, pas de soucis, on prendinnerText
:
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 || '';
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'objetwindow
est un objet global qui représente la fenêtre du
navigateur.document
, quant à lui, est un sous-objet
dewindow
et 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éinnerHTML
permet de récupérer ou de définir le code HTML
présent à l'intérieur d'un élément.
De leur côté,textContent
etinnerText
ne
sont capables que de définir ou récupérer du texte brut, sans aucunes balises HTML.