JavaScript

JavaScript est un langage de programmation qui permet, entre autres, de modifier le contenu d'un document Web de façon interactive (clic souris, saisie clavier, etc).

Il existe deux moyens d'incorporer un programme JavaScript au sein d'un document Web. Le premier consiste à renseigner le code source du script directement dans le document à l'aide de l'élément script.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>My first JavaScript program</title>
  </head>
  <body>
    <h1></h1>

    <script>
      let message = "Hello World Wide Web!";
      document.querySelector("h1").textContent = message;
    </script>
  </body>
</html>

Le code source du script peut aussi être sauvegardé dans un fichier externe. L'attribut src de l'élément script permet de renseigner le chemin et le nom du fichier JavaScript.

/*
 * Fichier hello.js dans le sous-répertoire js
 *
 */
let message = "Hello World Wide Web!";
document.querySelector("h1").textContent = message;
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>My first JavaScript program</title>
  </head>
  <body>
    <h1></h1>

    <script src="js/hello.js"></script>
  </body>
</html>

Arbre DOM (Document Object Model)

L'arbre DOM correspond à la représentation mémoire d'un document Web. Chaque élément HTML est représenté par un noeud et ses éléments imbriqués (enfants) apparaissent comme des ramifications. JavaScript propose un mécanisme permettant d'accéder à un élément donné du document et d'en modifier le contenu.

Modification d'un élément

L'objet JavaScript document représente la racine de l'arbre DOM. La méthode querySelector() permet de formuler un sélecteur CSS afin d'accéder à un élément HTML du document. L'objet JavaScript DOMELement retourné dispose de plusieurs propriétés dont textContent qui contient, sous la forme d'une chaîne de caractères, le contenu textuel de l'élément.

Ainsi, pour modifier dynamiquement le contenu d'un document Web, il suffit de remplacer la valeur de la propriété textContent d'un élément par une nouvelle chaîne de caractères. Dans l'exemple ci-dessus, le contenu (initialement vide) de l'élément h1 est remplacé par la valeur de la variable message.

Création d'un nouvel élément

La méthode createElement() de l'objet document permet de créer un nouvel élément. Celui-ci peut ensuite être ajouté à l'arbre DOM avec la méthode appendChild(). L'exemple suivant illuste cette démarche.

variables

JavaScript est un langage à typage dynamique. Lors de la création d'une variable, inutile de préciser son type. Il suffit juste d'utiliser le mot-clé let.

let message = "Hello!";   // string
let width = 640;          // number
let radius = 15.25;       // number
let running = false;      // boolean

Boucles

Comme dans beaucoup de langages de programmation, il est possible de créer des structures itératives (boucles). Leur syntaxe est différente de Python mais est très semblable à celle de langages comme Java, C, C++.

#
# 10 itérations
# i allant de 0 à 9
#
for i in range(10):
  print(i)

/*
 * 10 itérations
 * i allant de 0 à 9
 */
for(let i=0; i<10; i++) {
  console.log(i);
}

En JavaScript, les instructions à l'intérieur de la boucle sont délimitées par les accolades ouvrante {et fermante }.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Multiplications by 7</title>
  </head>
  <body>
    <ul></ul>

    <script>
      /*  
       * Récupération de l'élément ul dans 
       * l'arbre DOM
       */
      let ul = document.querySelector("ul");

      for (let i=0;i<=10; i++) {
        /*
         * Création d'un élément li
         */
        let li = document.createElement("li");

        /*
         * Mise à jour du contenu de l'élément li à l'aide
         * de la propriété innerHTML car la chaîne de caractères
         * contient du code HTML (entité HTML)
         */
        li.innerHTML = "7 &times; " + i + " = " + (7*i);

        /*
         * Ajout d'un nouvel enfant (dernier) à l'élément ul
         */
        ul.appendChild(li);
      }
    </script>
  </body>
</html>

Une boucle en JavaScript peut aussi être utilisée pour parcourir une liste d'éléments. Par exemple, la méthode querySelectorAll() retourne une liste de DOMElement.

Le code ci-dessous parcourt tous les éléments h2 afin de numéroter automatiquement tous les titres de niveau 2 du fichier cartoon_physics.html vu au chapitre Hypertexte.

let chapterNumber = 1;
      
let h2List = document.querySelectorAll("h2");
for (let i = 0; i < h2List.length; i++) {
  let h2 = h2List[i];

  h2.textContent = chapterNumber + '. ' + h2.textContent;
  chapterNumber++;
}

Et une version alternative avec la structure for of.

let chapterNumber = 1;

let h2List = document.querySelectorAll("h2");
for (let h2 of h2List) {
  h2.textContent = chapterNumber + '. ' + h2.textContent;
  chapterNumber++;
}

Exercice

Écrire un programme JavaScript qui génère automatiquement le sommaire du fichier cartoon_physics.html

Info

La méthode setAttribute(name, value) permet d'ajouter un attribut à un objet de type DOMElement.

Correction
/*  
 * Récupération de l'élément ul dans 
 * l'arbre DOM
 */
let h1 = document.querySelector("h1");

/*
 * Création d'un attribut id dans l'élément h1
 */
h1.setAttribute("id", "top");

/*  
 * Récupération de l'élément ul dans 
 * l'arbre DOM
 */
let ul = document.querySelector("ul");

let h2List = document.querySelectorAll("h2");
for (let i = 0; i < h2List.length; i++) {
  let h2 = h2List[i];

  /*
   * Création d'un élément li
   */
  let li = document.createElement("li");

  /*
   * Création d'un élément a
   */
  let a = document.createElement("a");

  /*
   * Mise à jour du contenu de l'élément li à l'aide
   * de la propriété innerHTML car la chaîne de caractères
   * contient du code HTML (entité HTML)
   */
  a.textContent = h2.textContent;

  /*
   * Création d'un attribut dans l'élément h2
   */
  let idValue = "l" + (i + 1);
  a.setAttribute("href", "#" + idValue);

  /*
   * Ajout de l'élément A à l'élément LI
   */
  li.appendChild(a);

  /*
   * Ajout d'un nouvel enfant (dernier) à l'élément ul
   */
  ul.appendChild(li);

  /*
   * Création d'un attribut dans l'élément h2
   */
  h2.setAttribute("id", idValue);

  /*
   * Ajout de code HTML dans l'élément h2
   */
  h2.innerHTML = h2.innerHTML + " <a href=\"#top\">&uparrow;</a>";
}

Tests

La syntaxe des tests est aussi différente de Python.

if hour<12:
  print("Good morning)
else:
  print("Good afternoon)


if (hour<12) {
  console.log("Good morning");
}
else {
  console.log("Good afternoon");
}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Test</title>
  </head>
  <body>
    <p></p>

    <script>
      // Récupération de la date courante
      let now = new Date();
      // Extraction de l'heure
      let hours = now.getHours();

      // Affectation du message en fonction de l'heure
      let message = "";
      if (hours<12) {
        message = "Good morning";
      }
      else {
        message = "Good afternoon";
      }

      // Modification du contenu du paragraphe (p)
      document.querySelector("p").textContent = message;
    </script>
  </body>
</html>

Exercice

Concevoir un programme JavaScript qui affiche l'heure au format HH:MM:SS à laquelle la page a été ouverte (l'heure est figée). Pour plus de détails, consulter la documentation de l'objet JavaScript Date.

10:07:01

Correction
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Time is frozen</title>
  </head>
  <body>
    <time></time>

    <script>
      let now = new Date();

      let hours = now.getHours();
      if (hours<10) hours = "0" + hours;

      let minutes = now.getMinutes();
      if (minutes<10) minutes = "0" + minutes;

      let seconds = now.getSeconds();
      if (seconds<10) seconds = "0" + seconds;

      document.querySelector("time").textContent = hours + ":" + minutes + ":" + seconds;
    </script>
  </body>
</html>

Fonctions

Une fonction JavaScript est définie à l'aide du mot-clé function. Toujours en raison du typage dynamique, il est inutile de préciser la nature de l'objet retourné dans le prototype de fonction.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Function</title>
  </head>
  <body>
    <p</p>

    <script>
      function hello() {
        let now = new Date();
        let hours = now.getHours();
        if (hours<12) {
          return "Good morning";
        }
        else {
          return "Good afternoon";
        }
      }

      document.querySelector("p").textContent = hello();
    </script>
  </body>
</html>

Exercice

Modifier le code du programme de l'exercice 36 en utilisant une fonction afin que l'affichage de l'heure soit mis à jour toutes les secondes. Pour cela, consulter la documentation de la méthode window.setInterval().

Correction
<!doctype html>
<html lang="en">
  <head>
   <meta charset="utf-8">
    <title>Time is running</title>
  </head>
  <body>
  	<time></time>

    <script>
      function displayDate() {
        let now = new Date();

        let hours = now.getHours();
        if (hours<10) hours = "0" + hours;

        let minutes = now.getMinutes();
        if (minutes<10) minutes = "0" + minutes;

        let seconds = now.getSeconds();
        if (seconds<10) seconds = "0" + seconds;

        document.querySelector("time").textContent = hours + ":" + minutes + ":" + seconds;
      }

      // Appel de la fonction displayDate toutes les 1000 millisecondes
      window.setInterval(displayDate, 1000);
    </script>
  </body>
</html>

Programmation événementielle

Le principe de la programmation événementielle consiste à pouvoir déclencher un appel de fonction lors de la survenue d'un événément (click, keyup, change, etc). La méthode addEventListener() permet d'associer un événement à un appel de fonction.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Event</title>
  </head>
  <body>
    <button>Click me!</button>

    <script>
      let button = document.querySelector("button");

      button.addEventListener("click", (event) => {
        let red   = Math.floor(Math.random()*256);
        let green = Math.floor(Math.random()*256);
        let blue  = Math.floor(Math.random()*256);

        button.style.backgroundColor = "rgb(" + red + "," + green + "," + blue + ")";
      });
    </script>
  </body>
</html>

Exercice

Modifiez le fichier form.html afin d'exiger une double saisie du mot de passe. Une fonction JavaScript associée à l'événement submit du formulaire empêchera la transmission des informations au serveur si les mots de passe ne sont pas identiques.

Capture d'écran du formulaire stylisé avec W3.CSS

Info : cas particulier des formulaires

Au delà de la méthode querySelector(), Il est possible de sélectionner un formulaire grâce à la propriété forms de l'objet document.

La méthode document.forms.namedItem(name) permet de sélectionner un formulaire dont le nom est égal à name. De la même manière, la propriété elements de l'objet formulaire permet de sélectionner un élément par son nom.

Info

La méthode event.preventDefault() peut être utilisée pour empêcher la soumission du formulaire.

Correction
<!doctype html>
<html lang="fr">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
    <title>Inscription</title>
  </head>
  <body>
    <div class="w3-content">
      <h1>Inscription</h1>

      <form name="signin" method="POST" enctype="multipart/form-data" action="https://iut-info.univ-reims.fr/users/nocent/web/tools/http_request_analyzer.php">
        <label>Nom</label>
        <input class="w3-input" type="text" name="lastname"
               required pattern="[A-Za-z]+">

        <label>Prénom</label>
        <input class="w3-input" type="text" name="firstname"
               required pattern="[A-Za-z]+">

        <label>Date de naissance</label>
        <input class="w3-input" type="date" name="birthdate" required>

        <label>
          <input class="w3-radio" type="radio" name="gender" value="female" checked>
          Femme
        </label>

        <label>
          <input class="w3-radio" type="radio" name="gender" value="male">
          Homme
        </label>

        <br>

        <label>Pays</label>
        <select class="w3-select" name="country" required>
          <option value="" disabled selected>Choisissez...</option>
          <option value="1">Allemagne</option>
          <option value="2">Belgique</option>
          <option value="3">France</option> 
        </select>

        <label>Courriel</label>
        <input class="w3-input" type="email" name="email" required>

        <label>Mot de passe</label>
        <input class="w3-input" type="password" name="pswd1"
               required pattern="[A-Za-z]+[@#%][1-9][0-9]{3,}">

        <label>Confirmez le mot de passe</label>
        <input class="w3-input" type="password" name="pswd2"
               required pattern="[A-Za-z]+[@#%][1-9][0-9]{3,}">

        <div id="alert" class="w3-panel w3-red" style="display: none">
          <h2>Attention !</h2>
          <p>Les mots de passe saisis sont différents.</p>
        </div>

        <label>Photo</label>
        <input class="w3-input" type="file" name="photo" accept="image/jpeg, image/png">

        <button class="w3-button w3-blue" type="submit">Envoyer</button>
        <button class="w3-button w3-red" type="reset">Effacer</button>
      </form>
    </div>

    <script>
      let signin = document.forms.namedItem('signin');

      signin.addEventListener("submit", (event) => {
        let pswd1 = signin.elements.namedItem("pswd1").value;
        let pswd2 = signin.elements.namedItem("pswd2").value;

        if (pswd1 != pswd2) {
          event.preventDefault();
          document.querySelector("#alert").style.display = "block";
        }
      });
    </script>

  </body>
</html>

Exercice Cookie Clicker reboot

Complétez le code du fichier cookie.html afin de concevoir un idle game consistant à cliquer sur un cookie le plus grand nombre de fois en 15 secondes.

Page d'accueil
Page en cours de partie

Images à télécharger :

La fonction reset()

Elle remet le score à zéro et modifie l'interface du jeu : elle masque le bouton start et affiche la zone d'affichage du temps restant.

Info

La méthode toggle() de l'attribut classList d'un élément DOM permet d'ajouter ou de supprimer une classe CSS.

let element = document.querySelector("button");
// Ajoute/supprime la class hidden
element.classList.toggle("hidden");
Correction
function reset() {
  // Cache le bouton start
  startButton.classList.toggle("hidden");

  // Affiche le temps restant
  timeDisplay.classList.toggle("hidden");

  // Remise à zéro du score
  clickCount = 0;
  score.textContent = 0;

  // Réinitialisation du temps restant
  remainingTime = 15;
}
La fonction countdown()

Elle met à jour le compte à rebours et teste si la partie est terminée.

  • Si la partie est terminée, modifier la variable playing pour arrêter la partie et mettre à jour l'interface du jeu : afficher le bouton start et masquer la zone d'affichage du temps restant
  • Sinon, afficher le nombre de secondes restantes en rouge (si remainingTime est inférieure ou égale à 5), en orange (si remainingTime est inférieure ou égale à 10) ou en noir dans le cas contraire
  • Décrémenter la variable remaininTime
  • Enfin, appeler à nouveau la fonction countdown() dans une 1 seconde

    Info

    La méthode window.setTimeout(functionName, delay) permet d'appeler la fonction functionName au bout de delay millisecondes.

Correction
function countdown() {
  if (remainingTime == 0) { // Si le temps est écoulé
    // Fin de la partie
    playing = false;

    // Affiche le bouton start
    startButton.classList.toggle("hidden");

    // Cache le temps restant
    timeDisplay.classList.toggle("hidden");
  }
  else {
    // Mise à jour des secondes restantes
    time.textContent = remainingTime + " seconds left!";

    // Modification de la couleur du texte en fonction du temps
    if (remainingTime<=5)
      time.style.color = "red";
    else if (remainingTime<=10)
      time.style.color = "orange";
    else
      time.style.color = "white";

    // Mise à jour du temps restant
    remainingTime--;

    // Appel de la fonction countdown dans 1 seconde
    window.setTimeout(countdown, 1000);
  }
}
L'écouteur d'événement du cookie

Il consiste simplement à incrémenter le score lorsque la partie est en cours (booléen playing).

Correction
cookieImage.addEventListener("click", function(event) {
  if (playing) { // Si la partie est en cours

    // Mise à jour du score
    clickCount++;
    score.textContent = clickCount;
  }
});
L'écouteur d'événement du bouton start

Il démarre la partie (booléen playing), initialise la variable startTime et appelle les fonctions reset et countdown.

Correction
startButton.addEventListener("click", function(event) {
  // La partie est démarrée
  playing = true;

  // Réinitialisation de l'interface
  reset();

  // Démarrage du compte à rebours
  countdown();
});

Démo jouable de Cookie Clicker reboot

Exercice

Concevoir un programme pour jouer au jeu Rock, Paper, Scissors contre le navigateur :

  1. Le joueur fait son choix (rock, scissors ou paper) en cliquant sur une des 3 cases.
    Début de la partie
  2. Initialisation d'une variable entière player sachant que rock → 0, scissors → 1 et paper → 2.
  3. Initialisation d'une variable entière computer à l'aide d'un tirage aléatoire
    Indication : documentation des méthodes Math.random() et Math.floor().
  4. Comparaison des valeurs des variables player et computer pour afficher le résultat:
    La machine gagne
    Match nul
    L'humain gagne

Images à télécharger :

Correction
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Rock, Scissors and Paper</title>
    <style>
      .hidden {
        display: none;
      }
    </style>
  </head>
  <body>
    <h1>Rock, paper, scissors</h1>
    <figure id="game">
      <img src="img/rock.png" alt="rock">
      <img src="img/paper.png" alt="paper">
      <img src="img/scissors.png" alt="scissors">
      <figcaption>It's up to you, human</figcaption>
    </figure>

    <figure id="results" class="hidden">
    </figure>

    <script>
      // Correspondance numéro - libellé
      let label = ["rock", "paper", "scissors"];

      let game = document.querySelector("#game");
      let results = document.querySelector("#results");

      function play(humanChoice) {
        // Choix de l'ordinateur
        let computerChoice = Math.floor( Math.random()*1000 ) % 3;

        // Suppression du contenu de la figure #results
        // par la destruction de tous les éléments enfants
        while (results.firstChild) results.removeChild(results.firstChild);

        // Création de l'image correspondant au choix du joueur
        let humanImage = document.createElement("img");
        humanImage.setAttribute("src", "img/" + label[humanChoice] + ".png");
        humanImage.setAttribute("alt", label[humanChoice]);
        results.appendChild(humanImage);

        // Création de l'image correspondant au choix de l'ordinateur
        let computerImage = document.createElement("img");
        computerImage.setAttribute("src", "img/" + label[computerChoice] + ".png");
        computerImage.setAttribute("alt", label[computerChoice]);
        results.appendChild(computerImage);

        // Création du libellé du résultat
        let caption = document.createElement("figcaption");

        if (humanChoice == computerChoice)
          caption.textContent = "Draw";
        else
          if ( (humanChoice==0 && computerChoice==2) ||
               (humanChoice==2 && computerChoice==1) ||
               (humanChoice==1 && computerChoice==0) )
            caption.textContent = "Human wins!";
        else
          caption.textContent = "Computer wins!";

        results.appendChild(caption);

        // Bascule de l'affichage : suppression/ajout de la classe CSS hidden
        game.classList.toggle("hidden");
        results.classList.toggle("hidden");
      }

      // Création des écouteurs d'événement
      let rock = document.querySelector("#game [alt=rock]");
      rock.addEventListener("click", function() { play(0); });

      let paper = document.querySelector("#game [alt=paper]");
      paper.addEventListener("click", function() { play(1); });

      let scissors = document.querySelector("#game [alt=scissors]");
      scissors.addEventListener("click", function() { play(2); });
    </script>
  </body>
</html>

Exercice

Modifier le code de l'exercice précédent pour jouer au jeu Rock, Scissors, Paper, Lizard, Spock. popularisé par la sérié télévisée Big Bang Theory.