Objectifs:
Plan du cours:
Remarque: Dans le TP précédent, vous avez compris qu’on ne pouvait plus utiliser l’outil repl.it pour tester du code mélant HTML, CSS et JS. En effet, repl.it n’est pas capable d’interpréter du code HTML et CSS. Maintenant que vous savez comment lier une page HTML à une feuille de style CSS et un script JS, nous allons pouvoir utiliser des outils équivalents à repl.it qui supportent les langages HTML et CSS, en plus du JS: jsbin, jsfiddle, codepen ou codesandbox. À partir de ce TP, tous les exercices sont réalisables sur n’importe lequel de ces quatre outils, ou en créant vous-même vos fichiers HTML, CSS et JS.
En HTML, chaque élément peut être associé à des classes. On les spécifie en les énumérant dans l’attribut class
de l’élément.
Exemple:
<p id="premier">texte visible</p>
<p id="second" class="hidden">ce texte devrait être caché</p>
Dans cet exemple, l’élément <p>
est associé à une classe hidden
. Pour que tous les éléments associés à cette classe soient cachés, il suffirait alors de définir la règle CSS suivante:
.hidden {
display: none;
}
En JavaScript, il est possible de modifier dynamiquement la liste de classes associées à un élément (et donc son affichage, en fonction des règles CSS qui sont associées à ces classes), grâce à la propriété classList
, et Ă ses fonctions add()
et remove()
.
Exemple:
var element = document.getElementById('un-element');
element.classList.add('hidden'); // => la classe hidden va être associée à l'élément
<p>
tels que définis plus haut..hidden
tel que définie plus haut.hidden
du deuxième paragraphe ?Dans la partie précédente, nous avons utilisé la fonction getElementById()
pour accéder à un des éléments de notre page, à partir de son identifiant id
.
L’API du DOM donne également accès à une fonction qui permet d’accéder à l’ensemble des éléments qui portent une même classe class
: getElementsByClassName()
.
Supposons que nous soyons sur une page HTML contenant plusieurs paragraphes portant une mĂŞme classe:
<p>C'est l'histoire d'un garçon troublé</p>
<p class="spoiler">car il s'avère que son ours en peluche a disparu</p>
<p class="spoiler">mais finalement il le retrouve</p>
Pour accéder à tous les éléments portant la classe spoiler
, nous allons utiliser le code JavaScript suivant:
var elements = document.getElementsByClassName('spoiler'); // => tableau d'éléments
Ă€ noter que, contrairement Ă getElementById()
, cette fonction ne retourne pas un seul élément, mais un tableau de plusieurs éléments.
Pour effectuer une même manipulation sur chacun des éléments de ce tableau, il suffit d’utiliser une boucle for
, tel que nous l’avons vu dans un précédent chapitre:
for (var i = 0; i < elements.length; i++) {
var element = elements[i]; // i-ème élément du tableau d'éléments
}
<p>
tels que définis dans l’exemple ci-dessus..hidden
tel que définie plus haut.hidden
aux paragraphes qui portent la classe spoiler
?Attention, aucun code JavaScript ne doit pas apparaître dans votre fichier HTML => faire un fichier .js à part.
Nous savons à présent:
id
ou class
;Dans cette partie, nous allons voir qu’il est possible de modifier dynamiquement le style d’éléments HTML sans avoir à manipuler de classes CSS.
De la même manière que les éléments HTML <input>
fournissent une propriété JavaScript value
permettant d’accéder à leur valeur, tous les éléments HTML fournissent une propriété JavaScript style
.
Alors que la propriété value
est de type String
(chaîne de caractères), la propriété style
est de type Object
.
L’objet associé à la propriété style
est structuré similairement à une règle CSS: il est constitué de propriétés clé-valeur.
Exemple de règle CSS:
#mon-element {
border: 1 solid black;
background-color: red;
}
… et l’objet JavaScript correspondant, tel qu’associé à la propriété style
de l’élément:
{
border: '1 solid black',
backgroundColor: 'red'
}
Il y a deux différences importantes à noter:
style
de l’élément doit respecter la syntaxe d’objet JavaScript (JSON), donc les valeurs de propriétés sont des chaînes de caractères (entre apostrophes), et les propriétés doivent être séparées par des virgules (au lieu des point-virgules de la notation CSS);Prenons le fichier HTML suivant:
<p id="premier">texte important</p>
<p id="second">autre texte</p>
Pour modifier la couleur de fond du premier paragraphe, il faut exécuter le code JavaScript suivant:
var element = document.getElementById('premier');
element.style.backgroundColor = 'red';
<p>
tels que définis dans l’exemple ci-dessus.onclick
, nous ne l’avons pas encore vu en cours)Vous allez développer une page web contenant une liste de produits, et permettant à l’utilisateur de filtrer l’affichage de ces produits, en activant les catégories de produits qui l’intéressent.
Exemple minimaliste (animation):
Vous êtes libre sur le choix des produits et des catégories qui seront proposés sur votre page, sur leur rendu visuel, ainsi que sur la manière d’activer les catégories.
Contraintes Ă respecter:
class
;Exemple de liste de produits en HTML:
<article class="ludique pratique">iPad</article>
<article class="ludique sportif">Batte de baseball</article>
<article class="pratique">Ventilateur</article>
Vous serez évalué(e) selon les critères suivants:
Les solutions les plus élégantes seront présentées en cours.
getElementsByTagName()
Jusqu’ici, nous avons vu deux fonctions de sélection d’éléments du DOM:
getElementById()
retourne un élément, quand on fournit son id
;getElementsByClassName()
retourne un tableau d’éléments, quand on fournit leur class
.Dans certains cas, il est pratique d’adresser les éléments par type (nom de balise). C’est ce que permet la fonction getElementsByTagName()
.
Exemple:
var images = document.getElementsByTagName('img');
// => images est un tableau contenant tous les éléments <img> de la page
Ă€ noter que, comme getElementsByClassName()
, getElementsByTagName()
retourne un tableau d’éléments.
getAttribute()
et setAttribute()
Nous avons vu qu’il était possible de récupérer et/ou modifier la valeur d’un champ <input>
à l’aide de la propriété value
de l’objet JavaScript représentant ce champ.
Il se trouve que value
est aussi le nom de l’attribut HTML correspondant. En effet, le DOM donne accès à des propriétés correspondant aux attributs standards des éléments HTML.
En guise d’exemple, imaginons l’élément HTML suivant:
<button id="mon-bouton" data-numero="5">
L’attribut data-numero
n’est pas standard, mais nous avons le droit de nous en servir pour rattacher des informations à cet élément.
Pour récupérer la valeur de cet attribut, utilisons la fonction getAttribute()
de la manière suivante:
document.getElementById('mon-bouton').getAttribute('data-numero');
// => cet appel de fonction retournera 5
Pour modifier la valeur de cet attribut, utilisons la fonction setAttribute()
de la manière suivante:
document.getElementById('mon-bouton').setAttribute('data-numero', 7);
// => l'attribut va prendre la valeur 7, au lieu de 5
innerHTML
Avant que nous décrivions comment modifier la structure du DOM d’une page web, sachez qu’il est possible de modifier directement depuis JavaScript le code HTML contenu par un élément HTML: à l’aide de la propriété innerHTML
de cet élément.
Exemple:
<!-- Avant: -->
<p id="mon-parag">texte initial</p>
document.getElementById('mon-parag').innerHTML = 'image: <img src="image.jpg">';
Cette affectation modifiera le DOM de la page web de la manière suivante:
<!-- Après: -->
<p id="mon-parag">image: <img src="image.jpg"></p>
Nous avons vu plus haut qu’il fallait utiliser une boucle for
pour effectuer une même opération sur un ensemble d’éléments HTML.
Exemple:
// supposons que elements soit un tableau d'éléments HTML
for (var i = 0; i < elements.length; i++) {
elements[i].classList.add('bordure-rouge');
}
Ce genre de code ne pose aucun problème.
Par contre, dès qu’une boucle for
définit une fonction qui sera appelée plus tard et dépend de la variable de boucle, notre programme se comporte de manière inattendue…
C’est notamment le cas si on définit une fonction onclick
dans une boucle, tel que dans l’exemple suivant:
// supposons que elements soit un tableau d'éléments HTML
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = function() {
alert('vous avez cliqué sur l\'élément nº' + i);
}
}
En cliquant sur les éléments sur lesquels s’applique cette boucle, vous constaterez que l’alert
affichera toujours le même élément, alors que la variable de boucle i
prend bien comme valeur l’indice de chaque élément de la page.
Ce comportement est dû à la manière dont JavaScript référence les variables: leur “portée” (appelée “scope”, en Anglais).
En JavaScript, toute variable définie par var
est rattachée à la fonction qui contient sa définition, ou dans l’espace “global” sinon.
Illustration:
var variableGlobale = 4;
function maFonction() {
var variableLocale = 5;
console.log('(maFonction) variableGlobale:', variableGlobale); // => 4
console.log('(maFonction) variableLocale:', variableLocale); // => 5
}
maFonction();
console.log('variableGlobale:', variableGlobale); // => 4
console.log('variableLocale:', variableLocale); // => ReferenceError: variableLocale is not defined
Quand on mentionne une variable à l’intérieur d’une définition de fonction, JavaScript va d’abord chercher s’il existe une variable localement définie, puis chercher dans les contextes parents dans lesquels ont été définis cette fonction, jusqu’à l’espace global, si besoin.
Dans le cas de notre boucle for
(cf exemple plus haut), la fonction affectée à la propriété onclick
utilise la variable i
, et cette variable n’est pas locale à cette fonction: elle est rattachée au contexte parent (celui qui contient la définition de fonction).
Du coup, lorsque cette fonction est appelée (en l’occurrence: au moment où l’utilisateur clique sur un élément), c’est la valeur de cette variable au moment de l’appel qui va être utilisée. Or, au moment de l’appel, notre boucle a fini d’itérer, et la variable i
vaut donc sa valeur maximum. (en l’occurrence: elements.length
)
Pour éviter ce problème de portée, le plus simple est d’appeler à chaque itération une fonction en passant notre variable i
en paramètre, de manière à ce que sa valeur soit rattachée à la fonction (comme si c’était une variable locale), et non au contexte parent.
Il existe deux moyens classiques d’appliquer cette pratique:
Dans les deux cas, cette fonction devra retourner une autre fonction, de manière à ce que cette dernière ne soit pas appelée immédiatement au moment de la définition de la fonction parente.
Nous avons vu qu’une fonction pouvait retourner une valeur de n’importe quel type, et que les fonctions étaient un des types avancés du langage JavaScript. Il est donc tout à fait possible qu’une fonction retourne une autre fonction !
Exemple:
function mere() {
console.log('fonction mere appelée');
return function fille() {
console.log('fonction fille appelée');
};
}
var fct = mere(); // => affiche 'fonction mere appelée' puis retourne la fonction fille
// => la fonction fille est affectée à la variable fct
fct(); // => affiche 'fonction fille appelée'
Pour éviter le problème de portée expliqué plus haut, il suffit alors de passer la valeur en paramètre de la fonction mère, qui sera appelée à chaque itération de la boucle:
function mere(i) {
// i est *promue* comme variable locale Ă la fonction mere
// => à chaque appel de la fonction qu'elle retourne, elle aura conservé sa valeur
return function fille() {
alert('vous avez cliqué sur l\'élément nº' + i);
// i fait référence à la valeur qui avait été passée en paramètre
// de l'appel à la fonction mere(), à chaque itération de boucle.
};
}
// supposons que elements soit un tableau d'éléments HTML
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = mere(i);
// => la fonction retournée par mere va être affecté à fille
}