Skip to content

Module 13 : JavaScript – Événements clavier et souris, sélecteurs multiples

Introduction

Ce module explore les événements du clavier et de la souris en JavaScript, ainsi que les méthodes de sélection d'éléments HTML qui retournent plusieurs résultats. Vous apprendrez à détecter les touches enfoncées par l'utilisateur, à réagir au survol de la souris, et à cibler efficacement des groupes d'éléments dans le DOM avec getElementsByClassName(), getElementsByTagName() et querySelectorAll().

Objectifs du cours

  • Comprendre et utiliser les événements clavier : keydown et keyup
  • Détecter quelle touche a été pressée grâce à event.key
  • Comprendre et utiliser les événements souris : mouseover et mouseout
  • Sélectionner plusieurs éléments du DOM avec getElementsByClassName(), getElementsByTagName() et querySelectorAll()
  • Parcourir une collection d'éléments HTML pour leur appliquer des traitements

Théorie

1 : Événements du clavier et détection des touches

Définition

Les événements du clavier permettent de détecter l'interaction de l'utilisateur avec les touches de son clavier. JavaScript offre deux événements principaux : keydown et keyup. Ces événements sont accompagnés de la propriété event.key qui permet de savoir exactement quelle touche a été pressée.

Contexte d'utilisation

Ces événements sont couramment utilisés pour :

  • Filtrer une liste de données au fur et à mesure que l'utilisateur tape dans un champ de recherche
  • Empêcher la saisie de certains caractères (par exemple, bloquer les lettres dans un champ numérique)
  • Déclencher une action lorsqu'une touche spécifique est pressée (ex. : Entrée pour soumettre, Échap pour fermer)

Les événements clavier principaux

ÉvénementDéclenchementUsage courant
keydownQuand l'utilisateur appuie sur une toucheDétecter les raccourcis, filtrer en temps réel
keyupQuand l'utilisateur relâche une toucheValider la saisie après la frappe

Important : keydown est l'événement le plus utilisé pour détecter les touches du clavier. keyup est utile lorsqu'on veut accéder à la valeur mise à jour du champ de saisie.

Détecter quelle touche a été pressée avec event.key

Lorsqu'un événement clavier est déclenché (keydown ou keyup), l'objet event contient la propriété key, qui est une chaîne de caractères représentant la touche pressée. Cela permet de savoir exactement quelle touche l'utilisateur a utilisée et d'agir en conséquence.

Valeurs courantes de event.key

ToucheValeur de event.key
Lettre A"a" ou "A" (selon Shift)
Chiffre 5"5"
Entrée"Enter"
Échapper"Escape"
Espace" "
Flèche haut"ArrowUp"
Flèche bas"ArrowDown"
Retour arrière"Backspace"
Tabulation"Tab"

Note : La propriété event.key retourne une chaîne de caractères. La comparaison est sensible à la casse pour les lettres ("a""A"), mais les touches spéciales ont un nom fixe ("Enter", "Escape", etc.).

Exemple pratique : démonstration des événements et détection des touches

HTML correspondant

html
<p>Taper le nom d'un produit et, au fur à mesure que les touchents du clavier sont tappées, vérifier la console pour voir les types d'événements déclanchés</p>
<input type="text" id="recherche" placeholder="Rechercher un produit...">
javascript
const champRecherche = document.getElementById('recherche');

function gererKeydown(event) {
    console.log('keydown — touche enfoncée : ' + event.key);
    console.log('Contenu du champ : ' + champRecherche.value); // Ne contient PAS encore le nouveau caractère
}

function gererKeyup(event) {
    console.log('keyup — touche relâchée : ' + event.key);
    console.log('Contenu du champ : ' + champRecherche.value); // Contient le nouveau caractère
}

function init()
{
    // Se déclenche AVANT que le caractère apparaisse dans le champ
    champRecherche.addEventListener('keydown', gererKeydown);

    // Se déclenche APRÈS que le caractère soit apparu dans le champ
    champRecherche.addEventListener('keyup', gererKeyup);

}
addEventListener('load', init);

Exemple pratique : détecter la touche Entrée et Échapper

Prenons un exemple concret avec une source de données :

HTML correspondant

html
<input type="text" id="recherche" placeholder="Rechercher un produit...">
<p id="resultats"></p>
javascript
// Tableau linéaire (indexé) contenant des objets. Chaque objet est un PRODUIT
const produits = [
    { 
        nom: 'Clavier mécanique', 
        prix: 89 
    },
    { 
        nom: 'Souris ergonomique',
        prix: 45 
    },
    { 
        nom: 'Écran 27 pouces',
        prix: 350 
    }
];

function gererToucheClavier(event) {
    const champRecherche = document.getElementById('recherche');

    // Si la touche enter est appuyée
    if (event.key === 'Enter') {
        // Afficher les résultats de recherche
        const terme = champRecherche.value.toLowerCase();
        const resultats = document.getElementById('resultats');
        resultats.textContent = '';

        for (const produit of produits) {
            if (produit.nom.toLowerCase().includes(terme)) {
                resultats.textContent += produit.nom + ' — ' + produit.prix + ' $ | ';
            }
        }
    }

    // Si ESCAPE est appuyée
    if (event.key === 'Escape') {
        // Vider le champ de recherche
        champRecherche.value = '';
        document.getElementById('resultats').textContent = '';
    }
}

function init() {
    const champRecherche = document.getElementById('recherche');
    champRecherche.addEventListener('keydown', gererToucheClavier);
}

addEventListener('load', init);

Exemple pratique : filtrer une liste en temps réel avec keyup

Dans cet exemple, on utilise keyup pour filtrer une liste de produits affichés dans la page, au fur et à mesure que l'utilisateur tape dans le champ de recherche.

HTML correspondant

html
<input type="text" id="recherche" placeholder="Filtrer les produits...">
<div id="liste-produits"></div>
javascript
const produits = [
    { nom: 'Clavier mécanique', prix: 89 },
    { nom: 'Souris ergonomique', prix: 45 },
    { nom: 'Écran 27 pouces', prix: 350 }
];

function afficherProduits(liste) {
    const zone = document.getElementById('liste-produits');
    zone.textContent = '';

    for (const produit of liste) {
        const paragraphe = document.createElement('p');
        paragraphe.textContent = produit.nom + ' — ' + produit.prix + ' $';
        zone.appendChild(paragraphe);
    }
}

function filtrerProduits(event) {
    const terme = event.currentTarget.value.toLowerCase();
    const resultats = [];

    for (const produit of produits) {
        if (produit.nom.toLowerCase().includes(terme)) {
            resultats.push(produit);
        }
    }

    afficherProduits(resultats);
}

function init() {
    // Afficher tous les produits au chargement
    afficherProduits(produits);

    const champRecherche = document.getElementById('recherche');
    champRecherche.addEventListener('keyup', filtrerProduits);
}

addEventListener('load', init);

Note : On utilise keyup ici plutôt que keydown parce qu'on veut lire la valeur mise à jour du champ. Avec keydown, la valeur ne contient pas encore le caractère en cours de frappe.

Points importants

  • keydown est l'événement le plus utilisé pour détecter les touches du clavier
  • keyup est utile lorsqu'on veut accéder à la valeur mise à jour du champ de saisie
  • Si la touche est maintenue enfoncée, keydown se répète continuellement, mais keyup ne se déclenche qu'une seule fois au relâchement
  • event.key permet de connaître exactement quelle touche a été pressée
  • Pour les lettres, la valeur change selon que Shift est enfoncé ou non
  • Toujours utiliser event.key plutôt que event.keyCode (déprécié)
  • keyup est préférable pour lire la valeur du champ de saisie, car elle est déjà à jour

Exercices pratiques

Voir l'exercice 1


2 : Les événements de la souris — mouseover et mouseout

Définition

Les événements mouseover et mouseout permettent de détecter le survol de la souris sur un élément HTML. mouseover se déclenche lorsque le curseur entre sur un élément, et mouseout se déclenche lorsque le curseur quitte cet élément.

Contexte d'utilisation

Ces événements sont utiles pour :

  • Afficher des informations supplémentaires au survol d'un produit ou d'une carte
  • Mettre en surbrillance un élément de liste lorsque la souris passe dessus
  • Créer des effets visuels interactifs sans recourir uniquement au CSS :hover
  • Afficher ou masquer des détails dynamiquement selon la position de la souris

Note : Pour des effets purement visuels (changement de couleur, ombre, etc.), le pseudo-sélecteur CSS :hover est souvent suffisant. Les événements mouseover et mouseout en JavaScript sont utiles lorsqu'on doit exécuter de la logique (modifier du texte, afficher des données, etc.) au survol.

Exemple pratique : afficher les détails d'un employé au survol

HTML correspondant

html
<div id="liste-employes"></div>
<p id="details">Survolez un employé pour voir ses détails.</p>

CSS correspondant

css
.employe-item {
    padding: 10px;
    margin: 5px 0;
    background-color: #ecf0f1;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

.employe-item:hover {
    background-color: #3498db;
    color: white;
}
javascript
const employes = [
    { 
        nom: 'Alice Tremblay', 
        poste: 'Développeuse', 
        salaire: 72000 },
    { 
        nom: 'Bob Martin', 
        poste: 'Designer', 
        salaire: 65000 },
    { 
        nom: 'Clara Dubois', 
        poste: 'Analyste', 
        salaire: 68000 
    }
];

function afficherDetails(event) {
    // Rappel : le dataset permet de stocker des données personnalisées sur les éléments HTML
    // Dans ce cas-ci, on extrait l'attribut "data-index"
    const index = event.currentTarget.dataset.index;
    const employe = employes[index];
    const details = document.getElementById('details');
    details.textContent = employe.nom + ' — ' + employe.poste + ' — ' + employe.salaire + ' $';
}

function cacherDetails() {
    const details = document.getElementById('details');
    details.textContent = 'Survolez un employé pour voir ses détails.';
}

function init() {
    const div = document.getElementById('liste-employes');

    for (let i = 0; i < employes.length; i++) {
        const element = document.createElement('p');
        element.textContent = employes[i].nom;
        // On créer un attribut data-index à la balise <p data-index=""> ... </p> pour stocker l'index de l'employé (sa position dans le tableau)
        element.dataset.index = i;
        element.classList.add('employe-item');
        // On ajoute des événements
        element.addEventListener('mouseover', afficherDetails);
        element.addEventListener('mouseout', cacherDetails);
        // On ajoute dans la div l'employé la balise <p> contenant les informations de l'employé
        div.appendChild(element);
    }
}

addEventListener('load', init);

Différence entre mouseover/mouseout et mouseenter/mouseleave

ÉvénementSe propage aux enfants ?Usage
mouseover✓ OuiSe déclenche aussi quand on entre sur un enfant
mouseout✓ OuiSe déclenche aussi quand on entre sur un enfant
mouseenter✗ NonSe déclenche uniquement sur l'élément ciblé
mouseleave✗ NonSe déclenche uniquement sur l'élément ciblé

Note : mouseenter et mouseleave sont souvent préférés lorsqu'un élément contient des enfants, car ils ne se redéclenchent pas à chaque passage sur un élément enfant. Pour des éléments simples (sans enfants), mouseover et mouseout fonctionnent très bien.

Points importants

  • mouseover se déclenche quand le curseur entre sur l'élément
  • mouseout se déclenche quand le curseur quitte l'élément
  • Ces événements se propagent aux éléments enfants (contrairement à mouseenter/mouseleave)
  • event.currentTarget fait référence à l'élément sur lequel l'écouteur est attaché
  • On peut combiner dataset pour stocker l'index de l'objet et retrouver ses données

Exercices pratiques

Voir l'exercice 2


3 : Sélectionner plusieurs éléments du DOM

Définition

Jusqu'ici, nous avons utilisé document.getElementById() pour cibler un seul élément par son identifiant. Cependant, il est fréquent de vouloir cibler plusieurs éléments à la fois — par exemple, tous les paragraphes d'une section, toutes les images d'une galerie, ou tous les éléments ayant une même classe CSS. JavaScript offre trois méthodes pour sélectionner des collections d'éléments.

Contexte d'utilisation

Ces méthodes sont essentielles pour :

  • Parcourir tous les éléments d'une liste pour leur ajouter un événement
  • Modifier le style ou le contenu de plusieurs éléments simultanément
  • Appliquer un traitement à tous les éléments partageant une même classe CSS
  • Cibler un ensemble d'éléments avec un sélecteur CSS précis

4.1 : getElementsByClassName(nomDeClasse)

Cette méthode retourne une collection HTML (HTMLCollection) de tous les éléments possédant la classe CSS spécifiée.

javascript
const elements = document.getElementsByClassName('carte-produit');

Exemple complet :

html
<div class="carte-produit">Produit 1</div>
<div class="carte-produit">Produit 2</div>
<div class="carte-produit">Produit 3</div>
<p id="compteur"></p>
javascript
function init() {
    const cartes = document.getElementsByClassName('carte-produit');

    document.getElementById('compteur').textContent = 'Nombre de cartes : ' + cartes.length;

    // Parcourir la collection avec une boucle for classique
    for (let i = 0; i < cartes.length; i++) {
        cartes[i].style.backgroundColor = '#3498db';
        cartes[i].style.color = 'white';
        cartes[i].style.padding = '10px';
        cartes[i].style.margin = '5px';
    }
}

addEventListener('load', init);

Note : getElementsByClassName() ne prend pas de point (.) devant le nom de la classe. On écrit 'carte-produit' et non '.carte-produit'.

4.2 : getElementsByTagName(nomDeBalise)

Cette méthode retourne une collection HTML (HTMLCollection) de tous les éléments correspondant au nom de balise spécifié.

javascript
const paragraphes = document.getElementsByTagName('p');

Exemple complet :

html
<p>Premier paragraphe</p>
<p>Deuxième paragraphe</p>
<p>Troisième paragraphe</p>
<button id="btn-modifier">Modifier les paragraphes</button>
javascript
function modifierParagraphes() {
    const paragraphes = document.getElementsByTagName('p');

    for (let i = 0; i < paragraphes.length; i++) {
        paragraphes[i].style.fontWeight = 'bold';
        paragraphes[i].style.color = '#e74c3c';
    }
}

function init() {
    const bouton = document.getElementById('btn-modifier');
    bouton.addEventListener('click', modifierParagraphes);
}

addEventListener('load', init);

Note : Le nom de la balise est écrit sans les chevrons (<>). On écrit 'p' et non '<p>'.

4.3 : querySelectorAll(selecteurCSS)

Cette méthode retourne une NodeList de tous les éléments correspondant au sélecteur CSS fourni. C'est la méthode la plus polyvalente, car elle accepte n'importe quel sélecteur CSS valide.

javascript
const elements = document.querySelectorAll('.carte-produit');
const items = document.querySelectorAll('ul li');
const boutons = document.querySelectorAll('button.btn-action');

Exemple complet :

html
<ul id="liste-cours">
    <li class="cours">HTML</li>
    <li class="cours">CSS</li>
    <li class="cours">JavaScript</li>
</ul>
<button id="btn-surligner">Surligner les cours</button>
javascript
function surlignerCours() {
    const cours = document.querySelectorAll('.cours');

    for (const element of cours) {
        element.style.backgroundColor = '#f1c40f';
        element.style.padding = '5px';
    }
}

function init() {
    const bouton = document.getElementById('btn-surligner');
    bouton.addEventListener('click', surlignerCours);
}

addEventListener('load', init);

Important : Avec querySelectorAll(), on utilise la syntaxe des sélecteurs CSS : . pour les classes, # pour les identifiants, rien pour les balises. C'est la même syntaxe que dans un fichier CSS.

Comparaison des trois méthodes

MéthodeParamètreRetourSyntaxe CSS ?
getElementsByClassName('nom')Nom de classe (sans .)HTMLCollection
getElementsByTagName('balise')Nom de balise (sans <>)HTMLCollection
querySelectorAll('sélecteur')Sélecteur CSS completNodeList

Exemple pratique combiné : afficher des données à partir d'un tableau d'objets

Voici un exemple complet qui utilise querySelectorAll() pour cibler et modifier tous les éléments d'une liste générée dynamiquement à partir d'un tableau d'objets.

HTML

html
<div id="zone-livres"></div>
<button id="btn-promotion">Appliquer 10% de rabais</button>
<p id="message-promo"></p>

JavaScript

javascript
const livres = [
    { titre: 'Le Petit Prince', prix: 15 },
    { titre: 'Harry Potter', prix: 25 },
    { titre: 'Dune', prix: 20 }
];

function afficherLivres() {
    const zone = document.getElementById('zone-livres');

    for (const livre of livres) {
        const carte = document.createElement('div');
        carte.classList.add('carte-livre');

        const titre = document.createElement('h3');
        titre.textContent = livre.titre;

        const prix = document.createElement('p');
        prix.classList.add('prix-livre');
        prix.textContent = livre.prix + ' $';

        carte.appendChild(titre);
        carte.appendChild(prix);
        zone.appendChild(carte);
    }
}

function appliquerPromotion() {
    // Sélectionner TOUS les éléments ayant la classe 'prix-livre'
    const prixElements = document.querySelectorAll('.prix-livre');

    for (const element of prixElements) {
        // Extraire le prix numérique du texte (ex: "25 $" → 25)
        const prixOriginal = parseInt(element.textContent);
        const nouveauPrix = (prixOriginal * 0.9).toFixed(2);
        element.textContent = nouveauPrix + ' $';
        element.style.color = '#e74c3c';
        element.style.fontWeight = 'bold';
    }

    document.getElementById('message-promo').textContent = 'Promotion de 10% appliquée !';
}

function init() {
    afficherLivres();

    const bouton = document.getElementById('btn-promotion');
    bouton.addEventListener('click', appliquerPromotion);
}

addEventListener('load', init);

Points importants

  • getElementsByClassName() et getElementsByTagName() n'utilisent pas la syntaxe CSS (pas de . ni de <>)
  • querySelectorAll() utilise la syntaxe CSS complète (.classe, #id, balise, etc.)
  • Les trois méthodes retournent une collection qu'on peut parcourir avec une boucle for classique ou for...of
  • querySelectorAll() est la méthode la plus flexible et la plus recommandée pour la plupart des cas

Exercices pratiques

Voir l'exercice 3


Objectif

Créer un répertoire d'employés qui permet de filtrer la liste par nom en tapant dans un champ de recherche (keyup), d'afficher les détails au survol (mouseover/mouseout), et de mettre en surbrillance tous les éléments d'une classe avec querySelectorAll().

Structure du projet VS Code :

Module13-Demo1/
├── index.html
├── css/
│   └── styles.css
└── js/
    └── scripts.js

index.html

Voir le code
html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Répertoire d'employés</title>
    <link rel="stylesheet" href="css/styles.css">
    <script src="js/scripts.js"></script>
</head>
<body>
    <header>
        <h1>Répertoire d'employés</h1>
    </header>
    <main>
        <section id="section-recherche">
            <input type="text" id="recherche" placeholder="Rechercher un employé... (Échap pour effacer)">
        </section>
        <section id="section-liste">
            <div id="liste-employes"></div>
        </section>
        <section id="section-details">
            <h2>Détails</h2>
            <p id="details">Survolez un employé pour voir ses informations complètes.</p>
        </section>
        <section id="section-actions">
            <button id="btn-surligner">Surligner tous les employés</button>
            <button id="btn-reinitialiser">Réinitialiser</button>
        </section>
    </main>
</body>
</html>

css/styles.css

Voir le code
css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    background-color: #f5f5f5;
    padding: 20px;
}

header {
    text-align: center;
    margin-bottom: 20px;
}

header h1 {
    color: #2c3e50;
}

#section-recherche {
    text-align: center;
    margin-bottom: 20px;
}

#recherche {
    width: 100%;
    max-width: 400px;
    padding: 12px 16px;
    font-size: 1em;
    border: 2px solid #bdc3c7;
    border-radius: 8px;
}

#recherche:focus {
    outline: none;
    border-color: #3498db;
}

#liste-employes {
    display: flex;
    flex-wrap: wrap;
    gap: 15px;
    justify-content: center;
    margin-bottom: 20px;
}

.carte-employe {
    width: 220px;
    padding: 15px;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 8px;
    text-align: center;
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.carte-employe:hover {
    transform: translateY(-3px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.carte-employe h3 {
    margin-bottom: 5px;
    color: #2c3e50;
}

.carte-employe p {
    color: #7f8c8d;
    font-size: 0.9em;
}

.surligne {
    background-color: #f1c40f;
    border-color: #f39c12;
}

#section-details {
    text-align: center;
    margin: 20px 0;
    padding: 20px;
    background-color: #eaf2f8;
    border-radius: 8px;
}

#section-details h2 {
    margin-bottom: 10px;
    color: #2c3e50;
}

#details {
    font-size: 1.1em;
    color: #34495e;
}

#section-actions {
    text-align: center;
    margin-top: 20px;
}

#section-actions button {
    padding: 10px 20px;
    font-size: 1em;
    margin: 0 10px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

#btn-surligner {
    background-color: #3498db;
    color: white;
}

#btn-surligner:hover {
    background-color: #2980b9;
}

#btn-reinitialiser {
    background-color: #e74c3c;
    color: white;
}

#btn-reinitialiser:hover {
    background-color: #c0392b;
}

js/scripts.js

Voir le code
javascript
"use strict";

const employes = [
    { nom: 'Alice Tremblay', poste: 'Développeuse', salaire: 72000 },
    { nom: 'Bob Martin', poste: 'Designer', salaire: 65000 },
    { nom: 'Clara Dubois', poste: 'Analyste', salaire: 68000 }
];

/**
 * Crée et affiche les cartes d'employés dans la zone de liste.
 * @param {Array} liste - Le tableau d'objets employés à afficher
 */
function afficherEmployes(liste) {
    const zoneEmployes = document.getElementById('liste-employes');
    zoneEmployes.textContent = '';

    for (let i = 0; i < liste.length; i++) {
        const carte = document.createElement('div');
        carte.classList.add('carte-employe');
        carte.dataset.index = i;

        const nom = document.createElement('h3');
        nom.textContent = liste[i].nom;

        const poste = document.createElement('p');
        poste.textContent = liste[i].poste;

        carte.appendChild(nom);
        carte.appendChild(poste);

        carte.addEventListener('mouseover', gererSurvol);
        carte.addEventListener('mouseout', gererQuitter);

        zoneEmployes.appendChild(carte);
    }
}

/**
 * Affiche les détails de l'employé survolé dans la zone de détails.
 * @param {Event} event - L'événement mouseover
 */
function gererSurvol(event) {
    const index = event.currentTarget.dataset.index;
    const employe = employes[index];
    const details = document.getElementById('details');
    details.textContent = employe.nom + ' — ' + employe.poste + ' — ' + employe.salaire + ' $';
}

/**
 * Réinitialise le texte de la zone de détails quand la souris quitte un employé.
 */
function gererQuitter() {
    const details = document.getElementById('details');
    details.textContent = 'Survolez un employé pour voir ses informations complètes.';
}

/**
 * Filtre les employés affichés selon le texte tapé dans le champ de recherche.
 * Détecte aussi la touche Échap pour vider le champ.
 * @param {Event} event - L'événement keyup
 */
function gererRecherche(event) {
    const champRecherche = document.getElementById('recherche');

    if (event.key === 'Escape') {
        champRecherche.value = '';
    }

    const terme = champRecherche.value.toLowerCase();
    const resultats = [];

    for (const employe of employes) {
        if (employe.nom.toLowerCase().includes(terme)) {
            resultats.push(employe);
        }
    }

    afficherEmployes(resultats);
}

/**
 * Ajoute la classe 'surligne' à toutes les cartes d'employés avec querySelectorAll.
 */
function surlignerTous() {
    const cartes = document.querySelectorAll('.carte-employe');

    for (const carte of cartes) {
        carte.classList.add('surligne');
    }
}

/**
 * Retire la classe 'surligne' de toutes les cartes et réaffiche tous les employés.
 */
function reinitialiser() {
    const cartes = document.querySelectorAll('.carte-employe');

    for (const carte of cartes) {
        carte.classList.remove('surligne');
    }

    document.getElementById('recherche').value = '';
    afficherEmployes(employes);
}

/**
 * Initialise la page au chargement : affiche les employés et attache les événements.
 */
function init() {
    afficherEmployes(employes);

    const champRecherche = document.getElementById('recherche');
    champRecherche.addEventListener('keyup', gererRecherche);

    const btnSurligner = document.getElementById('btn-surligner');
    btnSurligner.addEventListener('click', surlignerTous);

    const btnReinitialiser = document.getElementById('btn-reinitialiser');
    btnReinitialiser.addEventListener('click', reinitialiser);
}

addEventListener('load', init);

Résultat attendu

Un répertoire d'employés interactif qui combine les trois concepts vus dans ce module :

  • La recherche filtre les employés en temps réel avec keyup, et la touche Escape vide le champ
  • Le survol de chaque carte affiche les détails complets (nom, poste, salaire) grâce à mouseover et mouseout
  • Le bouton « Surligner » utilise querySelectorAll() pour cibler toutes les cartes et leur ajouter une classe CSS
  • Le bouton « Réinitialiser » retire la surbrillance et réaffiche la liste complète

Exercices pratiques

Exercice 1 : Recherche de jeux vidéo au clavier

Énoncé

Créer une page qui affiche une liste de jeux vidéo et permet de les filtrer en temps réel à l'aide du clavier.

Source de données

javascript
const jeux = [
    { 
        titre: 'The Legend of Zelda', 
        plateforme: 'Nintendo', 
        prix: 79 },
    { 
        titre: 'God of War', 
        plateforme: 'PlayStation', 
        prix: 69 },
    { 
        titre: 'Halo Infinite', 
        plateforme: 'Xbox', 
        prix: 59 
}
];

Fichiers à créer

  • index.html
  • css/styles.css
  • js/scripts.js

Consignes

  1. Au chargement de la page, afficher tous les jeux dans une zone <div id="zone-jeux"> sous forme de paragraphes affichant le titre, la plateforme et le prix
  2. Ajouter un champ de recherche <input> dans la page
  3. Utiliser l'événement keyup sur le champ pour filtrer les jeux dont le titre contient le texte saisi (insensible à la casse)
  4. Si l'utilisateur appuie sur la touche Escape, vider le champ de recherche et réafficher tous les jeux
  5. Si l'utilisateur appuie sur la touche Enter, afficher dans un <p id="compteur"> le nombre de résultats trouvés (ex : « 2 jeux trouvés »)

Exercice 2 : Catalogue de films avec survol

Énoncé

Créer un catalogue de films affiché sous forme de cartes. Au survol d'une carte, afficher les détails du film dans une zone dédiée. Au départ de la souris, remettre le message par défaut.

Source de données

javascript
const films = [
    { 
        titre: 'Inception', 
        realisateur: 'Christopher Nolan', 
        annee: 2010 
    },
    { 
        titre: 'Parasite', 
        realisateur: 'Bong Joon-ho', 
        annee: 2019 
    },
    { 
        titre: 'Interstellar', 
        realisateur: 'Christopher Nolan', 
        annee: 2014
     }
];

Fichiers à créer

  • index.html
  • css/styles.css
  • js/scripts.js

Consignes

  1. Au chargement de la page, générer dynamiquement une carte <div class="carte-film"> pour chaque film, contenant uniquement le titre dans un <h3>
  2. Utiliser dataset sur chaque carte pour stocker l'index du film dans le tableau
  3. Attacher un événement mouseover à chaque carte pour afficher, dans un <p id="info-film">, les détails du film survolé : titre, réalisateur et année
  4. Attacher un événement mouseout à chaque carte pour remettre le texte du paragraphe à : « Survolez un film pour voir ses détails. »
  5. Styliser les cartes avec CSS pour qu'elles soient disposées en ligne avec Flexbox, avec un effet visuel au survol (changement de couleur de fond, ombre, etc.)

Exercice 3 : Gestion d'une liste de tâches avec sélecteurs multiples

Énoncé

Créer une page affichant une liste de tâches. L'utilisateur peut ajouter une tâche via un champ de texte (touche Enter), marquer toutes les tâches comme complétées avec un bouton, et voir les détails de chaque tâche au survol. Cet exercice combine les événements clavier, souris et les sélecteurs multiples.

Fichiers à créer

  • index.html
  • css/styles.css
  • js/scripts.js

Consignes

  1. Afficher dans la page un champ de saisie <input> et un <div id="zone-taches">
  2. Lorsque l'utilisateur tape du texte et appuie sur Enter, créer dynamiquement un élément <div class="tache"> contenant le texte saisi, et l'ajouter dans la zone de tâches. Vider ensuite le champ de saisie
  3. Si l'utilisateur appuie sur Escape, vider le champ de saisie sans ajouter de tâche
  4. Chaque tâche créée doit réagir au mouseover en changeant sa couleur de fond, et au mouseout en revenant à la couleur d'origine
  5. Ajouter un bouton « Tout compléter » qui utilise querySelectorAll('.tache') pour parcourir toutes les tâches et leur ajouter la classe CSS completee (texte barré, couleur atténuée)
  6. Ajouter un bouton « Réinitialiser » qui utilise getElementsByClassName('tache') pour retirer la classe completee de toutes les tâches

Exercice 4 : Bataille de cartes (Synthèse)

Énoncé

Créer un jeu de bataille de cartes. L'utilisateur crée ses propres cartes avec une valeur et une couleur, puis affronte 10 combats en devinant laquelle de deux cartes tirées au hasard est la plus forte.

Source de données

javascript
const cartes = [];

Fichiers à créer

  • index.html
  • css/styles.css
  • js/scripts.js

Consignes

Partie 1 — Création des cartes

  1. Afficher un formulaire contenant :
    • Un champ numérique <input type="number"> pour la valeur de la carte (entre 1 et 13)
    • Une liste de choix <select> pour la couleur de fond de la carte
    • Un bouton « Ajouter la carte »
  2. Lorsque l'utilisateur clique sur le bouton, ajouter un objet { valeur: ..., backgroundColor: '...' } dans le tableau cartes
  3. L'utilisateur peut ajouter un maximum de 10 cartes. Une fois la limite atteinte, désactiver le bouton d'ajout
  4. On prend pour acquis que l'utilisateur ne mettra pas deux cartes ayant la même valeur
  5. À droite du formulaire, afficher en temps réel le contenu du tableau dans une zone <div id="zone-liste-cartes">, sous la forme : « Carte #1 : 10, red », « Carte #2 : 5, blue », etc.
  6. Lorsque l'utilisateur survole (mouseover) un élément de la liste des cartes, changer sa couleur de fond pour qu'elle corresponde à la propriété backgroundColor de la carte. Au mouseout, remettre la couleur de fond par défaut

Partie 2 — Bataille

L'utilisateur doit avoir créé au moins 2 cartes avant de pouvoir commencer la bataille. Sinon, afficher un message

  1. En dessous de la section de création, afficher une zone de combat avec un bouton « Prochain combat »
    1. Lorsque ce bouton est appuyée, désactiver le bouton (disabled) pour éviter de cliquer plusieurs fois pendant un combat
  2. À chaque clic sur le bouton, piger 2 cartes au hasard dans le tableau cartes et les afficher côte à côte dans la zone de combat. Chaque carte affiche sa valeur et a comme couleur de fond sa propriété backgroundColor. Se baser sur W3Schools — JavaScript Random pour utiliser une fonction Random afin de générer la position de la carte à tirer dans le tableau
  3. L'utilisateur doit cliquer sur la carte qu'il pense être la plus forte (valeur la plus élevée)
    • Si le choix est correct, incrémenter le score de 1 et afficher un message de succès
    • Si le choix est incorrect, afficher un message d'erreur
  4. Penser à une façon de prévenir la situation où l'utilisateur pour cliquer plusieurs fois sur les cartes avant de passer au prochain combat.
  5. Lorsque le choix a été fait, le message affichée en conséquence, il faut réactiver le bouton « Prochain combat »
  6. Après 10 combats, afficher un alert indiquant le score final de l'utilisateur sur 10 (ex. : « Votre score : 7 / 10 »), puis désactiver le bouton de combat

Correction

Correction Exercice 1

Voir le code complet

Solution HTML

html
<body>
    <header>
        <h1>Recherche de jeux vidéo</h1>
    </header>
    <main>
        <input type="text" id="recherche" placeholder="Rechercher un jeu... (Entrée = compter, Échap = effacer)">
        <p id="compteur"></p>
        <div id="zone-jeux"></div>
    </main>
</body>

Solution CSS

css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    padding: 20px;
    background-color: #f5f5f5;
}

header {
    text-align: center;
    margin-bottom: 20px;
}

#recherche {
    display: block;
    width: 100%;
    max-width: 400px;
    margin: 0 auto 15px auto;
    padding: 12px 16px;
    font-size: 1em;
    border: 2px solid #bdc3c7;
    border-radius: 8px;
}

#recherche:focus {
    outline: none;
    border-color: #3498db;
}

#compteur {
    text-align: center;
    font-weight: bold;
    color: #2c3e50;
    margin-bottom: 15px;
}

#zone-jeux {
    max-width: 500px;
    margin: 0 auto;
}

.jeu-item {
    padding: 12px;
    margin: 8px 0;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 5px;
}

.jeu-item strong {
    color: #2c3e50;
}

Solution JavaScript

javascript
"use strict";

const jeux = [
    { titre: 'The Legend of Zelda', plateforme: 'Nintendo', prix: 79 },
    { titre: 'God of War', plateforme: 'PlayStation', prix: 69 },
    { titre: 'Halo Infinite', plateforme: 'Xbox', prix: 59 }
];

function afficherJeux(liste) {
    const zone = document.getElementById('zone-jeux');
    zone.textContent = '';

    for (const jeu of liste) {
        const paragraphe = document.createElement('div');
        paragraphe.classList.add('jeu-item');
        paragraphe.innerHTML = '<strong>' + jeu.titre + '</strong> — ' + jeu.plateforme + ' — ' + jeu.prix + ' $';
        zone.appendChild(paragraphe);
    }
}

function filtrerJeux() {
    const terme = document.getElementById('recherche').value.toLowerCase();
    const resultats = [];

    for (const jeu of jeux) {
        if (jeu.titre.toLowerCase().includes(terme)) {
            resultats.push(jeu);
        }
    }

    return resultats;
}

function gererRecherche(event) {
    const champRecherche = document.getElementById('recherche');

    if (event.key === 'Escape') {
        champRecherche.value = '';
        document.getElementById('compteur').textContent = '';
        afficherJeux(jeux);
        return;
    }

    const resultats = filtrerJeux();
    afficherJeux(resultats);

    if (event.key === 'Enter') {
        document.getElementById('compteur').textContent = resultats.length + ' jeux trouvés';
    }
}

function init() {
    afficherJeux(jeux);

    const champRecherche = document.getElementById('recherche');
    champRecherche.addEventListener('keyup', gererRecherche);
}

addEventListener('load', init);

Explications

La fonction filtrerJeux() est séparée pour être réutilisable. L'événement keyup permet de lire la valeur mise à jour du champ. La touche Escape vide le champ et réaffiche tous les jeux, tandis que Enter affiche le compteur de résultats.


Correction Exercice 2

Voir le code complet

Solution HTML

html
<body>
    <header>
        <h1>Catalogue de films</h1>
    </header>
    <main>
        <div id="zone-films"></div>
        <p id="info-film">Survolez un film pour voir ses détails.</p>
    </main>
</body>

Solution CSS

css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    padding: 20px;
    background-color: #f5f5f5;
}

header {
    text-align: center;
    margin-bottom: 20px;
}

#zone-films {
    display: flex;
    flex-wrap: wrap;
    gap: 15px;
    justify-content: center;
    margin-bottom: 20px;
}

.carte-film {
    width: 200px;
    padding: 20px;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 8px;
    text-align: center;
    cursor: pointer;
    transition: background-color 0.3s ease, box-shadow 0.3s ease;
}

.carte-film:hover {
    background-color: #3498db;
    color: white;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.carte-film h3 {
    font-size: 1.1em;
}

#info-film {
    text-align: center;
    font-size: 1.1em;
    padding: 15px;
    background-color: #eaf2f8;
    border-radius: 8px;
    color: #34495e;
}

Solution JavaScript

javascript
"use strict";

const films = [
    { titre: 'Inception', realisateur: 'Christopher Nolan', annee: 2010 },
    { titre: 'Parasite', realisateur: 'Bong Joon-ho', annee: 2019 },
    { titre: 'Interstellar', realisateur: 'Christopher Nolan', annee: 2014 }
];

function afficherDetailsFilm(event) {
    const index = event.currentTarget.dataset.index;
    const film = films[index];
    const info = document.getElementById('info-film');
    info.textContent = film.titre + ' — Réalisé par ' + film.realisateur + ' (' + film.annee + ')';
}

function cacherDetailsFilm() {
    const info = document.getElementById('info-film');
    info.textContent = 'Survolez un film pour voir ses détails.';
}

function init() {
    const zone = document.getElementById('zone-films');

    for (let i = 0; i < films.length; i++) {
        const carte = document.createElement('div');
        carte.classList.add('carte-film');
        carte.dataset.index = i;

        const titre = document.createElement('h3');
        titre.textContent = films[i].titre;

        carte.appendChild(titre);
        carte.addEventListener('mouseover', afficherDetailsFilm);
        carte.addEventListener('mouseout', cacherDetailsFilm);
        zone.appendChild(carte);
    }
}

addEventListener('load', init);

Points de discussion

  • Chaque carte utilise dataset.index pour stocker sa position dans le tableau, ce qui permet de retrouver l'objet complet lors du survol
  • Un seul gestionnaire afficherDetailsFilm est partagé par toutes les cartes
  • La séparation entre la logique de survol (mouseover) et le retour à l'état initial (mouseout) rend le code clair et maintenable

Correction Exercice 3

Voir le code complet

Solution HTML

html
<body>
    <header>
        <h1>Gestion de tâches</h1>
    </header>
    <main>
        <section id="section-ajout">
            <input type="text" id="nouvelle-tache" placeholder="Ajouter une tâche (Entrée pour ajouter, Échap pour effacer)">
        </section>
        <section id="section-actions">
            <button id="btn-completer">Tout compléter</button>
            <button id="btn-reinitialiser">Réinitialiser</button>
        </section>
        <div id="zone-taches"></div>
    </main>
</body>

Solution CSS

css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    padding: 20px;
    background-color: #f5f5f5;
}

header {
    text-align: center;
    margin-bottom: 20px;
}

#section-ajout {
    text-align: center;
    margin-bottom: 15px;
}

#nouvelle-tache {
    width: 100%;
    max-width: 500px;
    padding: 12px 16px;
    font-size: 1em;
    border: 2px solid #bdc3c7;
    border-radius: 8px;
}

#nouvelle-tache:focus {
    outline: none;
    border-color: #3498db;
}

#section-actions {
    text-align: center;
    margin-bottom: 20px;
}

#section-actions button {
    padding: 10px 20px;
    font-size: 1em;
    margin: 0 10px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    color: white;
}

#btn-completer {
    background-color: #27ae60;
}

#btn-completer:hover {
    background-color: #219a52;
}

#btn-reinitialiser {
    background-color: #e74c3c;
}

#btn-reinitialiser:hover {
    background-color: #c0392b;
}

#zone-taches {
    max-width: 500px;
    margin: 0 auto;
}

.tache {
    padding: 12px;
    margin: 8px 0;
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.2s ease;
}

.completee {
    text-decoration: line-through;
    color: #95a5a6;
    background-color: #ecf0f1;
}

Solution JavaScript

javascript
"use strict";

function gererSurvolTache(event) {
    if (event.currentTarget.classList.contains('completee') === false) {
        event.currentTarget.style.backgroundColor = '#d5f5e3';
    }
}

function gererQuitterTache(event) {
    if (event.currentTarget.classList.contains('completee') === false) {
        event.currentTarget.style.backgroundColor = 'white';
    }
}

function ajouterTache(event) {
    const champTache = document.getElementById('nouvelle-tache');

    if (event.key === 'Escape') {
        champTache.value = '';
        return;
    }

    if (event.key === 'Enter') {
        const texte = champTache.value.trim();

        if (texte === '') {
            return;
        }

        const zone = document.getElementById('zone-taches');

        const tache = document.createElement('div');
        tache.classList.add('tache');
        tache.textContent = texte;
        tache.addEventListener('mouseover', gererSurvolTache);
        tache.addEventListener('mouseout', gererQuitterTache);

        zone.appendChild(tache);
        champTache.value = '';
    }
}

function completerTout() {
    const taches = document.querySelectorAll('.tache');

    for (const tache of taches) {
        tache.classList.add('completee');
        tache.style.backgroundColor = '';
    }
}

function reinitialiser() {
    const taches = document.getElementsByClassName('tache');

    for (let i = 0; i < taches.length; i++) {
        taches[i].classList.remove('completee');
        taches[i].style.backgroundColor = '';
    }
}

function init() {
    const champTache = document.getElementById('nouvelle-tache');
    champTache.addEventListener('keyup', ajouterTache);

    const btnCompleter = document.getElementById('btn-completer');
    btnCompleter.addEventListener('click', completerTout);

    const btnReinitialiser = document.getElementById('btn-reinitialiser');
    btnReinitialiser.addEventListener('click', reinitialiser);
}

addEventListener('load', init);

Vérifications des connaissances

Testez vos connaissances acquises dans ce cours en répondant aux questions suivantes. Cliquez sur chaque question pour révéler la réponse.

Question 1

Quelle est la différence principale entre keydown et keyup ?

Réponse

keydown se déclenche au moment où la touche est enfoncée (avant que le caractère apparaisse dans le champ), tandis que keyup se déclenche au moment où la touche est relâchée (après que le caractère soit apparu dans le champ). Pour lire la valeur mise à jour d'un champ de saisie, on préfère keyup.

Question 2

Comment accéder au nom de la touche pressée dans un gestionnaire d'événement clavier ?

Réponse

On utilise la propriété event.key, qui retourne une chaîne de caractères représentant la touche pressée. Par exemple :

javascript
function gererTouche(event) {
    if (event.key === 'Enter') {
        // L'utilisateur a appuyé sur Entrée
    }
}

Question 3

Quelle est la différence entre mouseover et mouseenter ?

Réponse

mouseover se propage aux éléments enfants : il se redéclenche lorsque la souris passe sur un élément enfant contenu dans l'élément ciblé. mouseenter ne se propage pas aux enfants et ne se déclenche qu'une seule fois lorsque la souris entre sur l'élément ciblé. Pour des éléments contenant des enfants, mouseenter évite les déclenchements multiples.

Question 4

Quelle est la syntaxe correcte pour sélectionner tous les éléments ayant la classe produit avec getElementsByClassName() ?

Réponse
javascript
const produits = document.getElementsByClassName('produit');

On écrit le nom de la classe sans le point (.). Le point est utilisé uniquement avec querySelectorAll(), qui accepte la syntaxe CSS :

javascript
const produits = document.querySelectorAll('.produit');

Question 5

Quelle méthode de sélection multiple accepte un sélecteur CSS complet ?

Réponse

querySelectorAll() est la seule méthode qui accepte un sélecteur CSS complet. On peut lui passer n'importe quel sélecteur valide en CSS :

javascript
document.querySelectorAll('.carte-produit');   // Par classe
document.querySelectorAll('ul li');            // Sélecteur descendant
document.querySelectorAll('button.btn-action'); // Balise + classe

Question 6

Comment, à l'aide de getElementsByTagName, modifier la couleur de tous les liens <a> d'un site web pour « red » ?

Réponse

Vous pouvez utiliser getElementsByTagName('a') pour sélectionner tous les liens, puis parcourir la collection et modifier la propriété style.color de chaque élément. Exemple :

javascript
function colorerLiensEnRouge() {
    const liens = document.getElementsByTagName('a');
    for (let i = 0; i < liens.length; i++) {
        liens[i].style.color = 'red';
    }
}

addEventListener('load', colorerLiensEnRouge);

Question 7

Comment ajouter, à l'aide de JavaScript, un événement mouseover sur toutes les images d'un site web afin que, au survol, l'image survolée reçoive une bordure de 3 pixels ?

Réponse
javascript
function gererMouseOver(event)
{
    // Lors du survol de la souris sur cette image, changer sa bordure.
    event.currentTarget.style.border = '3px solid #000';
}

function init()
{
    // Récupérer les balises img
    const images = document.querySelectorAll('img');
    // Les parcourir, et ajouter un evenement
    for (const img of images) {
        img.addEventListener('mouseover', gererMouseOver);
    }
}

addEventListener('load', init);

Question 8

Soit le code HTML suivant :

html
<nav>
    <ul>
        <li><a href="#" id="lien1" class="lien">Accueil</a></li>
        <li><a href="#" id="lien2" class="lien">À propos</a></li>
        <li><a href="#" id="lien3" class="lien">Services</a></li>
        <li><a href="#" id="lien4" class="lien">Contact</a></li>
    </ul>
</nav>

Question : comment déterminer et afficher (alert) le nombre d'items dans le menu ?

Réponse

On peut sélectionner les éléments <li> présents dans le nav avec document.querySelectorAll('nav ul li'), puis utiliser la propriété length et afficher le nombre avec alert(). Exemple :

javascript
function afficherNombreItemsMenu() {
    const items = document.querySelectorAll('nav ul li');
    alert('Nombre d\'items dans le menu : ' + items.length);
}

addEventListener('load', afficherNombreItemsMenu);