Tutoriel pour apprendre la programmation en JavaScript

Adrien Joly

Ce tutoriel est une introduction à la programmation en JavaScript.

JavaScript a été créé en 1995, pour le compte de la société Netscape (éditeur d'un des premiers navigateurs web, dont Mozilla Firefox est l'héritier), puis standardisé sous le nom d'ECMAScript (souvent réduit à ses initiales « ES »).

Utilisable sur de nombreuses plateformes, JavaScript est le seul langage de programmation inclus en standard dans les navigateurs web. On dit donc que c'est « le langage du web ».

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum. Commentez

- Titre : Tutoriel pour apprendre la programmation en JavaScript

- Auteur : Adrien Joly

- Parution : 22 mai 2017

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2015 Adrien Joly. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. TP 1 : Les bases du langage JavaScript

- I-A. Introduction à la Programmation et à JavaScript

- I-A-1. Langage de programmation

- I-A-2. Qu'est-ce qu'un programme ?

- I-A-3. Histoire du langage JavaScript

- I-A-4. Exécution de code JavaScript

- I-B. Manipulation de valeurs en JavaScript

- I-B-1. Types de valeurs

- I-B-1-a. Types simples

- I-B-1-b. Types avancés

- I-B-1-c. Exercice : Quel type ?

- I-B-2. Variables et opérateur d'affectation en JavaScript

- I-B-2-a. Variables et valeurs littérales

- I-B-2-b. Opérateur d'affectation

- I-B-2-c. Création de variables

- I-B-2-d. Exercice : Création de variables en JavaScript

- II. TP 2 : Conditions

- II-A. Comparaisons de valeurs et conditions en JavaScript

- II-A-1. Comparaison d'égalité : == et ===

- II-A-2. Opérateurs d'inégalité

- II-A-3. Conditions

- II-A-4. Condition avec multiples alternatives

- II-A-5. Instructions pour interagir avec l'utilisateur : prompt et alert

- II-A-6. Exercice 1 : Chatbot bête, mais poli

- II-A-7. Exercice 2 : Chatbot poli, mais pas sympa

- II-A-8. Exercice 3 : Votre propre chatbot

- II-A-9. Exercice bonus : Chatbot sur Messenger

- II-A-10. Combinaison d'expressions conditionnelles && et ||

- II-A-11. Indentation et autres conventions à respecter

- III. TP 3 : Algorithmes et Jeux

- III-A. Algorithmes

- III-A-1. Définition de Wikipédia

- III-A-2. Autre définition, plus concrète, de Gérard Berry

- III-B. Boucles for en JavaScript

- III-B-1. Qu'est-ce qu'une boucle ?

- III-B-2. À quoi servent les boucles ?

- III-B-3. Anatomie d'une boucle for en JavaScript

- III-B-4. Traçage de l'exécution d'une boucle for

- III-B-5. Interrompre l'exécution d'une boucle : break

- III-B-6. Application : FizzBuzz

- III-B-6-a. Algorithme à implémenter

- III-B-6-b. Trace d'exécution de l'algorithme

- III-B-6-c. Comment savoir si un nombre est multiple d'un autre ?

- III-B-7. Application  : Devine le nombre

- III-B-7-a. Fonctionnement du jeu à implémenter

- III-B-7-b. Exemple de déroulement d'une partie

- III-B-7-c. Comment obtenir un nombre aléatoire

- IV. TP 4 : Fonctions

- IV-A. Fonctions en JavaScript

- IV-A-1. Introduction

- IV-A-2. Définition et appel de fonction

- IV-A-3. Importance de return

- IV-B. Pratique  : Définition et appel de fonction en JavaScript

- IV-B-1. Bogues et tests unitaires : comment tester une fonction

- IV-B-2. Valeur et affectation d'une fonction

- IV-B-3. Exercice : Jeu ChiFouMi

- IV-B-3-a. Fonctionnement du jeu à implémenter

- IV-B-3-b. Exemple de déroulement d'une manche

- IV-B-4. Phase 1 : Implémentation d'une manche

- IV-B-5. Phase 2 : Partie en trois manches, et scores

- V. TP 5 : Tableaux

- V-A. Tableaux, introduction

- V-A-1. Caractéristiques

- V-A-2. Accéder aux éléments d'un tableau

- V-A-3. Modification d'un élément

- V-A-4. Ajout d'élément

- V-A-5. Retrait d'élément

- V-A-6. Pratique : Calendrier

- V-B. Tableaux, fonctions avancées

- V-B-1. Recherche d'élément par valeur

- V-B-2. Concaténation de tableaux

- V-B-3. Partitionnement de tableaux

- V-B-4. Altération de tableau

- V-B-5. Pratique  : Épicerie

- V-B-6. Exercice  : Jeu du Pendu

- VI. TP 6 : Objets

- VI-A. Objets JavaScript

- VI-A-1. Définition d'objet

- VI-A-2. Accès aux propriétés d'un objet

- VI-A-3. Modification de propriétés

- VI-A-4. Clés riches

- VI-A-5. Notes sur les types

- VI-A-6. Mise en pratique  : annuaire téléphonique

- VI-B. Objets, usage avancé

- VI-B-1. Hiérarchie d'objets

- VI-B-2. Énumérer les propriétés d'un objet

- VI-B-3. Énumérer les propriétés d'un objet, une autre manière

- VI-B-4. Suppression d'une propriété

- VI-B-5. Exercice : Répertoire téléphonique

- VII. TP 7 : Classes

- VII-A. Bases de la POO : classes JavaScript

- VII-A-1. Programmation Orientée Objet : classes, instances et this

- VII-A-2. Comment instancier une classe en JavaScript/ES6

- VII-A-3. Comment définir une classe en JavaScript/ES6

- VII-A-4. Usage de this

- VII-A-5. Exercice : Création de classe

- VIII. TP 8 : Manipuler le Web avec DOM

- VIII-A. JavaScript et le DOM

- VIII-A-1. Terminologie : quelques rappels sur le Web

- VIII-A-2. Associer un programme JavaScript à une page web

- VIII-A-3. Application : Dire bonjour au monde

- VIII-A-4. Accéder aux éléments de la page Web depuis JavaScript

- VIII-A-4-a. Application

- VIII-A-5. Récupérer la valeur d'un champ de saisie <input>

- VIII-A-6. Pratique : Récupération des valeurs du formulaire

- VIII-A-7. Réagir aux actions de l'utilisateur sur la page

- VIII-A-8. Exercice : Calculatrice

- VIII-A-9. Exercice Bonus : Calculatrice multiopérations

- IX. TP 9 : Manipuler les classes et styles CSS

- IX-A. Manipulation de classes CSS

- IX-A-1. Exercice : Afficher/cacher

- IX-B. Accéder à des éléments par classe

- IX-B-1. Exercice : Cacher les spoilers

- IX-C. Manipulation de styles CSS

- IX-C-1. Exemple de changement de style

- IX-C-2. Exercice : Surbrillance au clic

- IX-C-3. Exercice à rendre : Filtrage par catégorie

- IX-D. Accéder à des éléments par nom d'élément

- IX-D-1. Fonction DOM : getElementsByTagName()

- IX-E. Manipulation d'attributs

- IX-E-1. Fonctions DOM : getAttribute() et setAttribute()

- IX-F. Manipulation de contenu HTML d'un élément

- IX-F-1. Propriété innerHTML

- IX-G. Scope et génération de fonctions

- IX-G-1. « Scope » : portée des variables

- IX-G-2. Fonction génératrice de fonctions

- X. TP 10 : Manipulation DOM avancée

- X-A. Utilisation de la classe Event

- X-A-1. Exercice : détecter « Entrée » dans un champ texte

- X-B. Navigation dans le DOM

- X-B-1. Exercice : Changer la couleur du parent au clic

- X-C. Modification de la structure du DOM

- X-C-1. Créer et rattacher un nœud dans le DOM

- X-C-2. Supprimer un nœud du DOM

- X-C-3. Exercice : construire une page web en JavaScript

- X-C-4. Exercice : supprimer le champ de recherche de Google

- XI. TP 11 : Récupération de données avec AJAX

- XI-A. Effectuer une requête GET avec XMLHttpRequest

- XI-A-1. Introduction

- XI-A-2. Formats de données

- XI-A-3. Émettre une requête HTTP GET avec AJAX

- XI-A-4. Récupérer un document JSON avec AJAX

- XI-A-5. Exemples d'API interrogeables via AJAX

- XI-A-6. Exercice 1 : Récupérer l'adresse IP de l'utilisateur

- XI-A-7. Exercice 2 : Météo de la ville de l'utilisateur

- XI-A-8. Exercice 3 : Afficher les images demandées

- XI-A-9. Restrictions de protocole HTTP / HTTPS

- XI-A-10. Restrictions de domaines

- XI-A-11. Manipuler les paramètres d'une URL HTTP

- XI-A-12. Identification du développeur et/ou de l'application

- XII. TP 12 : Envoi de données avec AJAX

- XII-A. Effectuer une requête POST avec XMLHttpRequest

- XII-A-1. Envoi d'une chaine de caractères

- XII-A-2. Envoi d'un objet JavaScript / JSON

- XII-A-3. Conseils pratiques pour diagnostiquer le fonctionnement de vos requêtes

- XII-A-4. Exercice 4 : Tweeter en AJAX

- XII-A-4-a. Documentation de l'API fournie

- XII-A-4-b. Étapes proposées

- XII-A-4-c. Bonus : améliorations

- XIII. TP 13 : Composants Web

- XIII-A. Création d'un composant Web simple

- XIII-A-1. Généricité et instructions d'intégration

- XIII-A-2. Exercice 1 : développer et documenter un composant simple

- XIII-A-2-a. Composant : Carousel

- XIII-A-2-b. Composant : Accordéon

- XIII-A-2-c. Composant : Galerie vidéo

- XIV. TP 14 : Composants et POO

- XIV-A. Composant Web multiinstances et POO

- XIV-A-1. Défi : Plusieurs instances de Carousel sur une même page

- XIV-A-2. Solution 1 : Composant qui s'applique sur des groupes d'éléments

- XIV-A-2-a. Exemple de documentation d'un composant pour groupes d'éléments

- XIV-A-3. Solution 2 : Composant contrôlable par l'intégrateur

- XIV-A-3-a. Exemple de documentation d'un composant proposant une API

- XIV-A-4. Programmation Orientée Objet : classes, instances et this

- XIV-A-5. Comment définir et instancier une classe en JavaScript/ES6

- XIV-A-6. Usage de this

- XIV-A-7. Exercice 2 : Créer un composant Web instanciable plusieurs fois

- XIV-A-8. Exercice 3 : Création de classe

- XIV-A-9. Exercice 4 : Classe Accordeon

- XV. TP 15 : jQuery, Node.js et frameworks

- XV-A. Convertir un composant jQuery en JavaScript/DOM pur

- XV-A-1. Exercice 1 : convertir un composant accordéon

- XV-A-2. Exercice 2 : convertir un autre composant (au choix)

- XV-B. Node.js et npm

- XV-B-1. Introduction

- XV-B-2. Installer et tester Node.js sur sa machine

- XV-B-3. Exécuter un fichier .js avec Node.js

- XVI. Single Page Apps avec Augular et React

- XVI-A. Front-end VS back-end

- XVI-B. Frameworks et bibliothèques front-end

- XVI-C. Solutions hybrides

- XVII. TP 16 : Identification avec Google et Facebook

- XVII-A. Introduction

- XVII-B. Identification par plateforme tierce

- XVII-B-1. Ce que voit l'utilisateur

- XVII-B-2. Échanges entre l'application et la plateforme tierce

- XVII-C. Mise en œuvre

- XVII-C-1. Pratique : Intégration Google Sign-in

- XVII-C-2. Exercice : Login en production

- XVIII. TP 17 : Gestion d'erreurs et Géolocation API

- XVIII-A. Gestion d'erreurs en JavaScript

- XVIII-A-1. Erreurs synchrones et asynchrones

- XVIII-A-2. Erreurs de programmation

- XVIII-A-3. Erreurs opérationnelles

- XVIII-A-3-a. Cas synchrone

- XVIII-A-3-b. Cas asynchrone

- XVIII-B. Usage de Geolocation API

- XIX. Remerciements

I - TP 1 : Les bases du langage JavaScript

Conseil pratique : pour naviguer dans ce cours d’introduction au langage JavaScript , vous pouvez y rechercher un mot (ex. : « exercice ») en pressant Ctrl-F ou Cmd-F. Utilisez la table des matières pour passer d'un chapitre à l'autre.

I-A - Introduction à la Programmation et à JavaScript

I-A-1 - Langage de programmation

JavaScript est un langage qui permet de donner des ordres (appelés instructions) à une machine.

La machine en question peut être :

- un navigateur web ;

- un ordinateur ;

- un serveur sur Internet ;

- un objet intelligent (ex. : Arduino, Raspberry Pi…) ;

- voire une carte à puce ! (ex. : carte SIM, carte bleue…)

On va donc apprendre des commandes et structures pour expliquer ce qu'on attend de cette machine.

I-A-2 - Qu'est-ce qu'un programme ?

Le code source d'un programme est un peu comme une recette de cuisine : on y définit une liste d'actions (les instructions) qui permettent à quiconque de reproduire le plat, autant de fois qu'il le veut.

Comme dans une recette, si votre code source est trop vague ou contient des erreurs, le résultat risque d'être décevant, voire complètement à côté de la plaque.

Contrairement à une recette de cuisine, il faut garder en tête que ce n'est pas un humain qui va interpréter votre code source, mais une machine. Sachant que, contrairement à un humain, une machine n'est pas capable de raisonner intuitivement, il faut être extrêmement rigoureux dans le respect des règles de grammaire et d'orthographe du langage.

Si vous écrivez votre code de manière approximative, deux choses peuvent se passer :

- si vous avez de la chance, la machine vous avertira qu'elle n'a pas compris une de vos instructions => elle affichera un message d'erreur pour vous aider à la corriger ;

- soit, dans certains cas, il ne se passera rien de visible. Dans ce cas, ce sera à vous de relire votre code, et de vous mettre à la place de la machine pour essayer de comprendre comment elle l'interprète.

I-A-3 - Histoire du langage JavaScript

JavaScript a été créé en 1995, pour le compte de la société Netscape (éditeur d'un des premiers navigateurs web, dont Mozilla Firefox est l'héritier), puis standardisé sous le nom d'ECMAScript (souvent réduit à ses initiales « ES »).

Utilisable sur de nombreuses plateformes, JavaScript est le seul langage de programmation inclus en standard dans les navigateurs web. On dit donc que c'est « le langage du web ».

Même si le nom ressemble, Java est un langage différent de JavaScript. Si vous avez la flemme de prononcer « JavaScript », utilisez donc ses initiales « JS » plutôt que « Java ».

Malgré ses nombreuses évolutions, JavaScript est plutôt un langage de script qu'un langage de programmation, à proprement parler. En effet, il a été conçu comme une solution simple pour ajouter des interactions aux pages HTML du web, et non pour créer des logiciels à haut niveau de sûreté. Une des conséquences : JavaScript est un langage faiblement typé. Nous allons voir ça plus bas.

I-A-4 - Exécution de code JavaScript

Il y a deux façons d'exécuter nos instructions JavaScript :

1. De manière interactive : via une console ;

2. De manière programmative : en rédigeant un code source.

Dans le premier cas, chaque instruction sera exécutée immédiatement puis retournera un résultat, comme sur une calculatrice.

Dans le deuxième cas, on va écrire une liste d'instructions dans un fichier (appelé « code source »), pour que notre machine puisse exécuter ce fichier en une seule fois.

Pour tester JavaScript en mode interactif :

- ouvrez une fenêtre de navigation privée (mode incognito) depuis Google Chrome,

- ouvrez la console JavaScript en utilisant le raccourci clavier Cmd+Alt+J (sur Mac) ou Ctrl+Shift+J (sur PC/Window)

- tapez 1+1 et validez. => La console devrait afficher le résultat de cette opération. Ensuite tapez alert('bonjour'); et observez ce qui se passe.

Il existe de nombreuses consoles JavaScript plus ou moins évoluées : repl.it, glot.io, jsbin, jsfiddle, et codepen permettent d'éditer son code de manière un peu plus confortable, mais ne conservent pas d'historique de vos instructions, contrairement à la console de Google Chrome.

I-B - Manipulation de valeurs en JavaScript

I-B-1 - Types de valeurs

En langue française, il existe plusieurs types de mots : les noms, les verbes, les adjectifs, les prénoms, etc.

De la même façon, en maths, on peut manipuler plusieurs types de valeurs : nombres entiers, décimaux, rationnels (fractions), complexes, etc.

En JavaScript, c'est pareil. Il est possible d'exprimer et de manipuler des valeurs de différents types.

I-B-1-a - Types simples

- booléen (boolean) : true, false

- nombre (integer/float number) : 999, 0.12, -9.99

- chaine de caractères (string) : 'coucou'

- aucune valeur : null

I-B-1-b - Types avancés

- non défini : undefined

- objet (object) : { prop : 'valeur' }

- tableau (array) : [ 1, 2, 3 ]

- fonction (function) : function(){ /* ... */ }

I-B-1-c - Exercice : Quel type ?

1) Reconnaissez les types des valeurs ci-dessous. Notez que  :

- certaines lignes contiennent plusieurs valeurs ;

- quand une ligne contient une opération, devinez la valeur et le type du résultat de cette opération.

2) Vérifiez vos réponses en saisissant ces opérations dans la console, et en utilisant le mot-clé typeof. (voir plus bas)

1. 0

2. true

3. 2 - 1.2

4. 'hello'

5. 'bonjour' + 4

6. { a : 0.4 }

7. [ 'a', 'b', 'c' ]

Le mot-clé typeof renvoie le type de l'expression qui suit.

3) Expliquez le type de la dernière ligne.

Solution

typeof 0                    // => number
typeof true                 // => boolean
typeof 2 - 1.2              // => number
typeof 'hello'              // => string
typeof 'bonjour' + 4        // => string (car concatĂ©nation)
typeof { a: 0.4 }           // => object
typeof [ 'a', 'b', 'c' ]    // => object (car un tableau est une sorte d'objet)

I-B-2 - Variables et opérateur d'affectation en JavaScript

I-B-2-a - Variables et valeurs littérales

Dans l'exercice ci-dessus, nous avons manipulé des valeurs de manière littérale. C'est-à-dire qu'elles étaient explicitement affichées dans le code.

Dans un véritable programme, les valeurs dépendent souvent d'une saisie de l'utilisateur, ou d'autre chose. Du coup, elles sont rarement exprimées de manière littérale. On utilise pour ça une représentation symbolique : les variables.

En maths, on représente habituellement une variable sous forme d'une lettre minuscule. Par exemple « soit x=4 » veut dire qu'on définit une variable appelée x représente actuellement la valeur 4 (un nombre entier, en l'occurrence).

En JavaScript, une variable représentée par une suite de lettres (minuscules et/ou majuscules) pouvant contenir aussi des chiffres et le symbole underscore (_).

Généralement, on emploie une notation appelée camel case pour nommer les variables en JavaScript. Cette notation consiste à coller plusieurs mots, en mettant en majuscule seulement la première lettre de chaque mot, sauf celle du premier mot.

Exemple : nombreSaisiParUtilisateur est un bon nom pour une variable JavaScript.

Camel veut dire « chameau », en français. Avez-vous compris pourquoi ?

I-B-2-b - Opérateur d'affectation

Comme en maths, l'opérateur d'affectation = permet d'affecter une valeur (à droite) à une variable (à gauche).

Par exemple, si on tape monNombre = 1 dans une console JavaScript, puis qu'on y tape monNombre, la console nous répondra 1, car c'est la valeur actuelle de la variable monNombre.

Chaque usage de l'opérateur d'affectation sur une variable changera sa valeur actuelle, quitte à remplacer la valeur qu'elle portait précédemment.

Par exemple, si on tape monNombre = 1, monNombre = 2 puis monNombre, la console JavaScript nous répondra 2, car c'est la dernière valeur qui a été affectée à la variable monNombre.

La valeur affectée (à droite du =) peut être une valeur littérale, ou celle d'une autre variable.

Par exemple, si on tape autreNombre = monNombre, puis autreNombre, la console JavaScript nous répondra 2, car nous avons affecté la valeur de la variable monNombre (2, tel qu'affecté dans l'exemple précédent) à notre nouvelle variable autreNombre.

À noter que l'opérateur d'affectation se comporte différemment selon que la valeur affectée est de type simple (nombre, chaine de caractères…) ou avancé (objet, tableau, fonction…) :

- l'affectation d'une valeur de type simple dupliquera cette valeur dans la variable affectée ;

- alors qu'en affectant une valeur de type avancé, notre variable affectée sera en fait une référence à cette valeur.

Exemple d'affectation de valeur de type simple :

1. maValeurSimple = 1;

2. autreValeurSimple = maValeurSimple;

3. // à ce stade, nos deux variables valent 1

4. maValeurSimple = 2;

5. // => autreValeurSimple vaut toujours 1, alors que maValeurSimple vaut 2

Exemple d'affectation de valeur de type avancé  :

1. monTableau = [ 1, 2, 3 ];

2. autreVariable = monTableau;

3. // à ce stade, nos deux variables valent [ 1, 2, 3 ]

4. monTableau = [ 4, 5, 6 ];

5. // cette instruction a affecté un autre tableau à monTableau

6. // => autreValeurSimple vaut toujours [ 1, 2, 3 ], car il référence toujours le tableau qui lui avait été affecté

Exemple d'affectation de valeur de type avancé, avec modification :

1. monTableau = [ 9, 8, 7 ];

2. autreVariable = monTableau;

3. // à ce stade, nos deux variables valent [ 9, 8, 7 ]

4. monTableau.sort();

5. // cette instruction a rangé notre tableau dans l'ordre croissant

6. // => monTableau vaut désormais [ 7, 8, 9 ]

7. // => ... et autreVariable vaut aussi [ 7, 8, 9 ], car le tableau qu'il a référencé a été modifié.

I-B-2-c - Création de variables

Dans nos exemples, les variables ont été créées automatiquement par la console JavaScript lors de leur première affectation de valeur.

Mais nous allons désormais créer nos variables de manière explicite, à l'aide du mot-clé var.

En effet, l'usage de var permet de s'assurer qu'on a pas déjà créé une variable du même nom.

Il est possible d'affecter une valeur à notre variable lors de sa création avec var.

Il restera évidemment possible d'affecter d'autres valeurs à notre variable, à l'aide de l'opérateur d'affectation, mais sans utiliser var ensuite.

Exemple  :

1. // on va créer une variable de type chaine de caractères :

2. var maVariable = 'mon texte';

3. // => maVariable vaut 'mon texte'

4. maVariable = `mon nouveau texte`;

5. // => maVariable vaut désormais 'mon nouveau texte'

Vous remarquerez que :

- j'ai ajouté des espaces autour de l'opérateur d'affectation pour améliorer la lisibilité de mon code ;

- et j'ai terminé mes instructions par un point-virgule (;).

Afin d'améliorer la lisibilité de votre code, et d'éviter toute ambiguïté pouvant occasionner des erreurs, je compte sur vous pour appliquer ces règles.

I-B-2-d - Exercice : Création de variables en JavaScript

En utilisant la console JavaScript :

1) Créez les variables suivantes :

- la variable monNombre avec une valeur de 4 ;

- la variable maChaine contenant 'bonjour ! :-)' ;

- la variable monBooleen valant false ;

- et une variable sansValeur à laquelle vous n'aurez affecté aucune valeur.

2) Pour confirmer que vos variables sont correctes, vérifiez que chaque instruction ci-dessous répond true : (copiez-collez-les une par une dans la console)

1. monNombre === 4;

2. maChaine === 'bonjour !  :-)';

3. monBooleen === false;

4. sansValeur === undefined;

Solution

// solution:
var monNombre = 4;
var maChaine = 'bonjour ! :-)'; // string => apostrophes, et respecter chaque caractère
var monBooleen = false;         // boolean => pas d'apostrophes
var sansValeur;                 // pas de valeur => pas d'affectation
// vérification:
monNombre === 4;               // => true
maChaine === 'bonjour ! :-)';  // => true
monBooleen === false;          // => true
sansValeur === undefined;      // => true

II - TP 2 : Conditions

II-A - Comparaisons de valeurs et conditions en JavaScript

Un programme est censé pouvoir prendre des décisions de manière autonome, sur la base de conditions.

Pour exprimer ces conditions, on s'appuie généralement sur des comparaisons entre valeurs. Nous allons commencer par la comparaison la plus simple : l'égalité de valeurs.

II-A-1 - Comparaison d'égalité : == et ===

En JavaScript il existe deux opérateurs de comparaison d'égalité :

- == vérifie l'égalité de deux valeurs de manière laxiste ;

- === vérifie l'égalité de deux valeurs de manière stricte.

Une égalité laxiste consiste à dire que deux valeurs sont vues comme équivalentes, mais pas exactement égales.

Exemple : 1 (nombre) et "1" (chaine de caractères) représentent tous les deux le chiffre 1, mais sont de types différents => ils sont égaux seulement selon l'opérateur d'égalité laxiste.

L'égalité stricte vérifie en plus que le type des deux valeurs comparées est le même.

Quelques exemples :

1. 1 == '1'; // => true

2. 1 === `1`; // => false (car types différents)

3. 0 == false; // => true

4. 0 === false; // => false

5. null == undefined; // => true

6. null === undefined; // => false

La comparaison entre valeurs de types avancés fonctionne différemment. Ce ne sont pas les valeurs à proprement parler qui sont comparées, mais la référence vers cette valeur.

Explication :

1. [1,2] == [1,2]; // => false, car deux tableaux ont été créés

2. var monTab = [1,2];

3. monTab == monTab; // => true, car la variable monTab référence un seul et même tableau

4. monTab == [1,2]; // => false, car ce sont deux références de tableaux différents

5. var monTab2 = monTab;

6. monTab == monTab2; // => true, car monTab2 fait référence au même tableau que monTab

ATTENTION !

Notez qu’en JavaScript, les opérateurs de comparaison d'égalité == et === ne doivent pas être confondus avec l'opérateur d'affectation =.

Vous verrez plus tard que se tromper d'opérateur peut causer des erreurs silencieuses qui peuvent mettre des heures à être détectées et corrigées dans votre code !

Donc soyez attentifs à bien les différencier quand vous écrivez votre code.

II-A-2 - Opérateurs d'inégalité

Quand on sait que deux valeurs ne sont pas égales, notre programme peut s'adapter à différents cas de figure.

Nous avons vu que les opérateurs === et == permettaient d'évaluer l'égalité (stricte ou laxiste), en retournant une valeur true quand c'était le cas. Le langage JavaScript fournit aussi leurs opérateurs contraires : !== et != (strict, et laxiste, respectivement). Ceux-ci retournent une valeur true quand les valeurs comparées ne sont pas égales.

Exemples :

1. 1 == 1; // => true

2. 1 === 1; // => true

3. 1 == '1'; // => true

4. 1 === '1'; // => false

5. 1 != 1; // => false

6. 1 !== 1; // => false

7. 1 != '1'; // => false

8. 1 !== '1'; // => true

Le langage JavaScript fournit aussi les opérateurs de comparaison suivants :

- strictement inférieur : <,

- strictement supérieur : >,

- inférieur ou égal : <=,

- supérieur ou égal : >=.

Exemples :

1. 1 < 1; // => false

2. 1 > 1; // => false

3. 1 <= 1; // => true

4. 1 >= 1; // => true

5. 1 < 2; // => true

6. 1 > 2; // => false

7. 1 <= 2; // => true

8. 1 >= 2; // => false

II-A-3 - Conditions

Dans un programme (codé en langage JavaScript ou pas), les conditions sont une des instructions les plus incontournables.

C'est grâce aux conditions que votre programme peut prendre des décisions et donc d'effectuer des actions de manière autonome (ou automatique), en fonction des données qui lui sont fournies.

On peut représenter ces décisions sous forme d'un arbre :

Cet exemple d'arbre illustre un diagnostic médical, en fonction des symptômes.

Cet arbre pourrait être traduit en pseudocode de la manière suivante :

- si douleur à abdomen, alors appendicite

- sinon, si douleur à la gorge et

- si fièvre, alors rhume

- sinon, mal de gorge

- sinon, si…

Puis être implémenté en langage JavaScript de la manière suivante :

1. // supposons que les variables douleur et fievre soient fournies

2. var diag;

3. if (douleur === 'abdomen') {

4. diag = 'appendicite';

5. } else if (douleur === 'gorge') {

6. if (fievre === true) {

7. diag = 'rhume';

8. } else {

9. diag = 'mal de gorge';

10. }

11. }

En JavaScript, les conditions s'expriment à l'aide des mots-clés if et else. On les emploie de la manière suivante :

1. if (expression) {

2. // si expression == true, alors les instructions entre ces accolades vont s'exécuter

3. } else {

4. // sinon, ce sont les instructions entre ces accolades-là qui vont s'exécuter

5. }

Le mot-clé else (facultatif) permet d'exécuter une séquence d'instructions seulement si expression n'est pas vraie.

Vous pouvez utiliser une comparaison en guise d'expression.

Exemple :

1. var monNombre = 1, resultat;

2. if (monNombre === 1) {

3. resultat = 'monNombre vaut 1';

4. } else {

5. resultat = 'monNombre ne vaut pas 1';

6. }

7. resultat;

8. // => cette liste d'instructions va afficher 'monNombre vaut 1'

À retenir : dans un bloc if-else, soit les instructions entre la première paire d'accolades seront exécutées, soit celles de la deuxième paire d'accolades.

Par ailleurs, observez bien la manière d'agencer les accolades et les espaces.

II-A-4 - Condition avec multiples alternatives

Dans le cas où vous aimeriez définir plus de deux comportements alternatifs, vous pouvez employer ajouter des cas else if() entre votre bloc if{} et votre bloc else{}.

Exemple :

1. if (monNombre === 1) {

2. resultat = 'monNombre vaut 1';

3. } else if (monNombre > 1) {

4. resultat = 'monNombre est supérieur à 1';

5. } else {

6. resultat = 'monNombre n\'est ni égal à 1, ni supérieur à 1';

7. }

8. resultat;

Comme pour les blocs if-else, seules les instructions d'une paire d'accolades seront exécutées.

Par contre, si vous écrivez plusieurs blocs if-else à la suite les uns des autres, ceux-ci seront complètement indépendants.

II-A-5 - Instructions pour interagir avec l'utilisateur : prompt et alert

Maintenant qu'on a vu comment faire en sorte qu'un programme prenne des décisions, il faut qu'on soit capable d'interagir avec lui.

À ce stade, nous allons employer deux instructions pour cela :

- alert permet d'afficher un message à l'utilisateur ;

- et prompt permet de lui demander de saisir une chaine de caractères.

Par exemple, voici comment afficher Bonjour ! :

1. alert('Bonjour !');

Comme vous le voyez, il faut fournir le message à afficher entre parenthèses. Et, comme il s'agit ici d'une chaine de caractères littérale, il ne faut pas oublier de mettre le texte entre apostrophes.

Il est aussi possible d'afficher la valeur d'une variable :

1. var monMessage = 'Hello !';

2. alert(monMessage); // usage d'une variable => pas d'apostrophes

Maintenant, voici comment inviter l'utilisateur à saisir une chaine de caractères, puis l'afficher :

1. var sonPrenom = prompt('Quel est ton prénom ?');

2. alert('Bonjour, ' + sonPrenom + ' !  :-)');

Êtes-vous capable d'interpréter ce que signifie le code à l'intérieur des parenthèses du alert ? On appelle ça « la concaténation ». On verra ça plus en détail dans les cours à venir.

II-A-6 - Exercice 1 : Chatbot bête, mais poli

Écrivez et testez un programme qui va inviter l'utilisateur à saisir une phrase, et se comporter différemment selon sa saisie :

- si l'utilisateur saisit bonjour, afficher Bonjour à toi ! ;

- sinon, si l'utilisateur saisit tu vas bien ?, afficher Bien, et toi ? ;

- et sinon, afficher Désolé, je n'ai pas compris....

Solution

// on commence par poser une question avec prompt
var reponse = prompt('bonjour?');
// la réponse a été stockée dans la variable reponse
// maintenant, on définit le comportement du programme
// pour 3 réponses possibles (alternatives)
if (reponse === 'bonjour') {
alert('Bonjour à toi !');
} else if (reponse === 'tu vas bien ?') {
alert('Bien, et toi ?');
} else {
// cette partie sera executée par défaut, si aucune des
// conditions ci-dessus n'est vraie
alert('Désolé, je n\'ai pas compris...');
}

II-A-7 - Exercice 2 : Chatbot poli, mais pas sympa

Écrivez un autre programme sur la base du précédent.

Dans le cas où l'utilisateur aura saisi bonjour, le programme affichera Bonjour ! Ca va ?, invitera l'utilisateur à saisir une deuxième réponse puis se comportera différemment selon cette deuxième réponse :

- si l'utilisateur saisit oui, afficher Je suis content pour toi. ;

- sinon, afficher Désolé, il faut que je file..

Arbre de décision et Implémentation en JavaScript :

// ce programme est une évolution de chatbot1.js
// on commence par poser une question avec prompt
var reponse = prompt('bonjour?');
// la réponse a été stockée dans la variable reponse
// maintenant, on définit le comportement du programme
// pour 3 réponses possibles (alternatives)
if (reponse === 'bonjour') {
// nous sommes à l'intérieur des accolades => indentation de 2 espaces
// à ce stade, l'utilisateur a saisi bonjour,
// on va lui poser une deuxieme question avec prompt
var reponse2 = prompt('Bonjour ! Comment vas-tu ?');
// condition imbriquée dans notre 1ère condition:
if (reponse2 === 'oui') {
// on est à nouveau entre accolades => indentation supplémentaire
alert('Je suis content pour toi.');
} else {
alert('Désolé, il faut que je file.');
}
// nous allons sortir des accolades => fin de l'indentation de 2 espaces
} else if (reponse === 'tu vas bien ?') {
// observez bien l'expression ci-dessous, elle est liée à la 1ère
// question prompt, et non à la deuxième.
alert('Bien, et toi ?');
} else {
alert('Désolé, je n\'ai pas compris...');
}

II-A-8 - Exercice 3 : Votre propre chatbot

À vous de concevoir et implémenter votre propre chatbot !

Vous allez devoir produire et rendre deux fichiers :

1. Dessiner un arbre représentant un total de cinq questions qui pourront être posées par votre Chatbot, ainsi que les réponses associées pour chaque cas. L'arbre doit avoir au moins deux niveaux de profondeur (c.-à-d. degré 2).

2. Implémenter et tester les lignes de code JavaScript permettant d'interagir avec le Chatbot, tel que défini par votre arbre.

Vous devrez utiliser les mots-clés : var, if, else, =, ===, prompt et alert.

Solution: cf solution de l'exercice précédent.

II-A-9 - Exercice bonus : Chatbot sur Messenger

Si vous avez trouvé les exercices trop faciles, et que vous avez aidé vos camarades, je vous propose un défi un peu plus compliqué : faire de votre programme un véritable Chatbot joignable via Messenger.

Pour cela, je vous recommande d'utiliser une plateforme de développement de Chatbot.

II-A-10 - Combinaison d'expressions conditionnelles && et ||

Dans l'exemple précédent, on a vu que chaque alternative dépendait du résultat d'une seule expression de comparaison de valeurs.

1. if (maPremiereValeur === maDeuxiemeValeur) { /* ... */

2. } else if (maPremiereValeur > maDeuxiemeValeur) { /* ... */ }

Dans certains cas, une alternative est définie par la combinaison de plusieurs expressions.

Par exemple, imaginons un programme qui propose comment s'habiller en fonction du temps qu'il fait, en suivant les critères suivants :

- s'il fait beau et chaud, suggérer de porter un short ;

- s'il pleut ET qu'il fait chaud, suggérer de prendre un parapluie ;

- s'il pleut ET qu'il fait froid, suggérer de prendre un manteau à capuche.

Sachant qu'il est possible d'imbriquer des conditions, on pourrait l'implémenter de la manière suivante :

1. if (temps === 'beau') {

2. suggestion = 'short';

3. } else if (temps === 'pluie') {

4. if (temperature >= 20) {

5. suggestion = 'parapluie';

6. } else if (temperature < 20) {

7. suggestion = 'manteau à capuche';

8. }

9. }

Mais il est aussi possible d'exprimer nos critères de manière plus linéaire, en combinant les conditions à l'aide de l'opérateur && :

1. if (temps === 'beau') {

2. suggestion = 'short';

3. } else if (temps === 'pluie' && temperature >= 20) {

4. suggestion = 'parapluie';

5. } else if (temps === 'pluie' && temperature < 20) {

6. suggestion = 'manteau à capuche';

7. }

Dans cette implémentation, nous avons combiné les critères de temps et de temperature dans une même expression conditionnelle.

Lorsqu'une alternative if/else est définie par plusieurs expressions liées par l'opérateur && (appelé et), il faut que toutes ces expressions soient vraies afin que les instructions associées à cette alternative soient exécutées.

Il existe aussi un opérateur || (appelé ou) qui permet de définir des alternatives qui seront exécutées si au moins une des expressions est vraie.

Exemple :

1. if (rdvPrevu === true || envieDallerAuxToilettes === true) {

2. message = 'excusez-moi, je vais devoir vous laisser';

3. } else {

4. message = 'nous pouvons en discuter tout de suite, si vous voulez !';

5. }

… ce qui pourrait aussi s'écrire ainsi, sous sa forme développée :

1. if (rdvPrevu === true) {

2. message = 'excusez-moi, je vais devoir vous laisser';

3. } else if (envieDallerAuxToilettes === true) {

4. message = 'excusez-moi, je vais devoir vous laisser';

5. } else {

6. message = 'nous pouvons en discuter tout de suite, si vous voulez !';

7. }

Dans ce cas, nous voyons que la combinaison d’expression conditionnelle avec || permet de réduire la redondance de notre code, en ne définissant qu’une seule fois une même liste d’instructions correspondante à deux cas alternatifs.

II-A-11 - Indentation et autres conventions à respecter

Dans le cadre de ce cours, nous allons respecter un extrait des conventions du guide de style d'Airbnb. (C'est illustré de nombreux exemples très clairs qui aident à comprendre les bonnes et mauvaises manières d'écrire son code.)

Extrait des règles qui seront à respecter :

- mettre les chaines de caractères entre apostrophes, (cf. 6.1) ;

- usage et placement des accolades (braces, en anglais), (cf. 16.1) ;

- et indentation de deux espaces (cf. 18.1).

Par ailleurs, pensez à inclure systématiquement 'use strict'; en première ligne de tous vos programmes.

Pour vérifier et corriger l'indentation de votre code JavaScript, vous pouvez utiliser un outil en ligne comme jsbeautifier ou la fonction « prettier » de repl.it.

Si vous préférez utiliser votre éditeur de code préféré (ex. : sublime text) pour vous aider à corriger l'indentation et/ou les conventions ci-dessus, configurez-le de manière à ce qu'il respecte le guide de style d'Airbnb.

III - TP 3 : Algorithmes et Jeux

Objectifs :

- Création d'algorithmes à l'aide de boucles

- Savoir développer des jeux simples

III-A - Algorithmes

III-A-1 - Définition de Wikipédia

« Un algorithme est une suite finie et non ambiguë d'opérations ou d'instructions permettant de résoudre un problème ou d'obtenir un résultat. »

Une recette de cuisine peut être réduite à un algorithme, si on peut réduire sa spécification aux éléments constitutifs :

- des entrées (les ingrédients, le matériel utilisé) ;

- des instructions élémentaires simples, dont l'exécution amène au résultat voulu ;

- un résultat : le plat préparé.

Un casse-tête, tel le Rubik's Cube, peut être résolu de façon systématique par un algorithme qui mécanise sa résolution.

Illustration : algorithme de résolution de Rubik's Cube.

III-A-2 - Autre définition, plus concrète, de Gérard Berry

« Un algorithme, c'est tout simplement une façon de décrire dans ses moindres détails comment procéder pour faire quelque chose. […] Le but est d'évacuer la pensée du calcul, afin de le rendre exécutable par une machine numérique (ordinateur…). On ne travaille donc qu'avec un reflet numérique du système réel avec qui l'algorithme interagit. »

III-B - Boucles for en JavaScript

III-B-1 - Qu'est-ce qu'une boucle ?

Une boucle permet de répéter plusieurs fois une séquence d'instructions.

Pour afficher les nombres de 1 à 3 dans la console JavaScript, on pourrait utiliser les instructions suivantes :

1. console.log(1);

2. console.log(2);

3. console.log(3);

Et ça fonctionne très bien !

Par contre, le code deviendrait très fastidieux à écrire (et à lire) dans le cas où on voudrait afficher les nombres de 1 à 10000 !

Pour ce genre de répétition, le mot-clé for permet de définir une seule fois les instructions qui doivent être répétées, puis de spécifier combien de fois on souhaite qu'elles soient répétées.

Pour afficher les nombres de 1 à 10000, il suffit donc d'écrire le code suivant :

1. for ( var monNombre = 1; monNombre <= 10000; monNombre++ ) {

2. console.log( monNombre );

3. }

On pourrait traduire ce code de la manière suivante :

Pour chaque valeur de monNombre, croissant de 1 à 10000 (compris), afficher la valeur de monNombre dans la console.

III-B-2 - À quoi servent les boucles ?

Les boucles sont donc très utiles pour éviter les redondances dans un programme (ex. : jouer cinq fois le même son, mettre tous les champs d'un formulaire en majuscules…), mais elles sont surtout indispensables dans de nombreuses applications courantes :

- les jeux tour-par-tour consistent en une boucle qui se termine lorsqu'un joueur remporte la partie ;

- les jeux d'action utilisent une boucle permettant de mettre à jour l'affichage (frame par frame, pour utiliser la terminologie exacte) en fonction des actions du/des joueur(s) ;

- ainsi que les algorithmes de tri et de manipulation de données utilisés dans 99 % des logiciels.

JavaScript fournit quatre mots-clés pour définir des boucles : do, while, until et for. La forme de boucle la plus courante est for, car c'est la plus générique / adaptable. Nous allons donc seulement travailler avec des boucles for dans le cadre de ce cours.

III-B-3 - Anatomie d'une boucle for en JavaScript

Reprenons l'exemple de boucle que nous avons vu plus haut :

1. for ( var monNombre = 1 ; monNombre <= 10000 ; monNombre++ ) {

2. console.log( monNombre );

3. }

Cette boucle est définie par :

- l'usage du mot-clé for;

- une liste d'instructions (saisie entre accolades {}) à répéter tant que la condition est vraie : console.log( monNombre ); (dans notre exemple, il n'y a qu'une seule instruction, mais on peut en mettre une infinité) ;

- une condition (expression conditionnelle, comme dans une condition if) : monNombre <= 10000 ;

- une instruction d'itération qui sera exécutée après chaque itération de la boucle : monNombre++ (qui, ici, incrémente la valeur de monNombre, c'est-à-dire augmente sa valeur de 1) ;

- et une instruction d'initialisation qui ne sera exécutée qu'une seule fois : var monNombre = 1 (ici, on créée une variable monNombre et on lui affecte la valeur initiale 1).

On appelle itération chaque répétition de la boucle.

Pour synthétiser, voici la syntaxe à utiliser pour définir une boucle for en JavaScript :

1. for( /* initialisation */ ; /* condition */ ; /* incrémentation */ ) {

2. /* instructions à répéter */

3. }

À noter que, dans la plupart des cas, les boucles sont utilisées pour itérer :

- sur un intervalle (dans notre exemple : nombres entiers entre 1 et 10000),

- ou sur une énumération de valeurs (ex. : un tableau/Array, comme on le verra plus tard).

III-B-4 - Traçage de l'exécution d'une boucle for

Afin de mieux comprendre le fonctionnement de la boucle for et de la manière de saisir ces trois paramètres, nous allons interpréter une boucle comme le fait un navigateur web (ou tout autre interpréteur JavaScript).

Prenons la boucle for suivante :

1. console.log('on va boucler');

2. for ( var i = 0; i < 4; i++ ) {

3. console.log('i', i, i < 4);

4. }

5. console.log('on a fini de boucler');

Voici la manière dont elle va être interprétée et exécutée par la machine :

1. console.log('on va boucler');            // => affiche : on va boucler

2. // interprétation de la boucle => on commence par l'initialisation

3. var i = 0; // initialisation de la boucle, en affectant 0 à la variable i

4. // --- première itération de la boucle ---

5. i < 4 ?                       // condition vraie, car i vaut 0

6. console.log('i', i, i < 4);   // => affiche : i 0 true

7. i++;                          // incrémentation => i vaut maintenant 1

8. // --- seconde itération de la boucle ---

9. i < 4 ?                       // condition vraie, car 1 < 4

10. console.log('i', i, i < 4);   // => affiche : i 1 true

11. i++;                          // incrémentation => i vaut maintenant 2

12. // --- troisième itération de la boucle ---

13. i < 4 ?                       // condition vraie, car 2 < 4

14. console.log('i', i, i < 4);   // => affiche : i 2 true

15. i++;                          // incrémentation => i vaut maintenant 3

16. // --- quatrième itération de la boucle ---

17. i < 4 ?                       // condition vraie, car 3 < 4

18. console.log('i', i, i < 4);   // => affiche : i 3 true

19. i++;                          // incrémentation => i vaut maintenant 4

20. // --- cinquième itération de la boucle ---

21. i < 4 ?                       // condition fausse, car i==4 => fin de boucle

22. // boucle terminée => on interprète les instructions suivantes.

23. console.log('on a fini de boucler');     // => affiche : on a fini de boucler

Il est très pratique de décomposer une boucle de cette manière lorsqu'elle ne se comporte pas comme voulu. (débogage)

III-B-5 - Interrompre l'exécution d'une boucle : break

Dans certains cas, il est pratique d'interrompre l'exécution d'une boucle, pendant l'exécution d'une de ses itérations (telle que définie entre les accolades de définition de la boucle).

1. for (var i = 0; i < 10; i++) {

2. var commande = prompt('entrez une commande');

3. if (commande === 'quitter') {

4. break; // => on sort de la boucle for, la suite du programme continue de s'exécuter (après ses accolades du for)

5. }

6. }

Cependant, l'usage de break est non recommandé, car il rend la logique plus complexe à comprendre en lisant le code. Il est généralement possible et plus élégant d'intégrer la condition de sortie dans la condition de la boucle.

1. var commande;

2. for (var i = 0; i < 10 && commande !== 'quitter'; i++) {

3. commande = prompt('entrez une commande');

4. }

Ici, on a intégré la condition à l'aide de l'opérateur logique &&, donc la boucle continuera d'itérer tant que i < 10 ET que commande !== 'quitter'.

III-B-6 - Application : FizzBuzz

III-B-6-a - Algorithme à implémenter

À implémenter étape par étape :

1. Écrire un programme qui affiche les nombres de 1 à 199 (compris) dans la console.

2. Pour les multiples de 3, afficher Fizz au lieu du nombre.

3. Pour les multiples de 5, afficher Buzz au lieu du nombre.

4. Pour les nombres multiples de 3 et 5, afficher uniquement FizzBuzz.

Pour ne pas s'embrouiller, il est recommandé de  :

- commencer par écrire la logique d'une seule itération, avant de la faire se répéter à l'aide d'une boucle ;

- écrire sous forme de pseudocode (en langue française), avant de l'implémenter en JavaScript.

III-B-6-b - Trace d'exécution de l'algorithme

On devrait obtenir les lignes suivantes dans la console  :

1. 1

2. 2

3. Fizz

4. 4

5. Buzz

6. Fizz

7. 7

8. 8

9. Fizz

10. Buzz

11. 11

12. Fizz

13. 13

14. 14

15. FizzBuzz

… et ainsi de suite, jusqu'à 199.

III-B-6-c - Comment savoir si un nombre est multiple d'un autre ?

Pour savoir si un nombre est multiple de 3 et/ou de 5, nous allons utiliser deux fonctions fournies ci-dessous :

1. function estMultipleDeTrois(nombre) {

2. return nombre % 3 === 0;

3. }

4. function estMultipleDeCinq(nombre) {

5. return nombre % 5 === 0;

6. }

Après avoir copié-collé la définition de ces deux fonctions dans la console JavaScript, vous pouvez les utiliser de la manière suivante :

1. estMultipleDeTrois(2); // => retourne false, car 2 n'est pas multiple de 3

2. estMultipleDeTrois(6); // => retourne true, car 6 est un multiple de 3

3. estMultipleDeCinq(6); // => retourne false, car 6 n'est pas un multiple de 5

4. estMultipleDeCinq(15); // => retourne true, car 15 est un multiple de 5

Vous pouvez alors appeler ces fonctions dans les parenthèses de vos conditions if, car comme une expression de comparaison de valeurs, un appel de fonction retourne une valeur qui est vraie (true) ou fausse (false).

Exemple :

1. var monNombre = 5; // valeur fournie en guise d'exemple

2. if (estMultipleDeCinq(monNombre)) {

3. console.log('monNombre est multiple de 5');

4. } else {

5. console.log('monNombre n\'est pas multiple de 5');

6. }

Solution

// fonctions fournies dans l'énoncé
function estMultipleDeTrois(nombre) {
return nombre % 3 === 0;
}
function estMultipleDeCinq(nombre) {
return nombre % 5 === 0;
}
// algorithme "fizzbuzz":
for(var i = 1; i < 200; i++) {
if (estMultipleDeCinq(i) && estMultipleDeTrois(i)) {
console.log('FizzBuzz');
} else if (estMultipleDeTrois(i)) {
console.log('Fizz');
} else if (estMultipleDeCinq(i)) {
console.log('Buzz');
} else {
console.log(i);
}
}

III-B-7 - Application  : Devine le nombre

III-B-7-a - Fonctionnement du jeu à implémenter

- En début de partie, l'ordinateur va choisir un nombre aléatoire entre 0 et 100.

- Le joueur a droit à 10 tentatives pour deviner ce nombre.

- À chaque tentative :

- La partie continue jusqu'à ce que le joueur gagne ou épuise ses 10 tentatives.

- si le nombre saisi est inférieur à celui de l'ordinateur, afficher Plus grand ;

- si le nombre saisi est supérieur à celui de l'ordinateur, afficher Plus petit ;

- si le joueur a réussi à deviner le bon nombre, afficher Bravo !

Pour implémenter ce jeu :

- vous allez avoir besoin d'interagir avec l'utilisateur, et donc d'utiliser les mots-clés prompt et alert ;

- il est recommandé de commencer par écrire le code d'une seule itération (sans utiliser de boucle), et de le tester en donnant des valeurs arbitraires à vos variables, pour simuler chaque cas.

III-B-7-b - Exemple de déroulement d'une partie

1. Nombre saisi par le joueur : 10

2. Réponse de l'ordinateur : Plus grand

3. Nombre saisi par le joueur : 20

4. Réponse de l'ordinateur : Plus petit

5. Nombre saisi par le joueur : 16

6. Réponse de l'ordinateur : Bravo !

III-B-7-c - Comment obtenir un nombre aléatoire

Pour obtenir un nombre aléatoire entier entre 0 et 100 :

Math.round(Math.random() * 100)

Libre à vous de stocker cette valeur dans une variable, si vous avez besoin de la comparer à d'autres valeurs, par exemple.

var nbTours = 10;
var aleatoire = Math.round(Math.random() * 100);
console.log('nombre à trouver:', aleatoire);
for (var i = 0; i < nbTours; ++i) {
var joueur = prompt('devinez le nombre');
if (joueur > aleatoire) {
alert('Plus petit');
} else if (joueur < aleatoire) {
alert('Plus grand');
} else if (joueur == aleatoire) {
alert('Bravo !');
break; // sort de la boucle avant la fin
}
}

IV - TP 4 : Fonctions

Objectifs :

- Savoir développer le jeu ChiFouMi (pierre feuille ciseaux)

- Création d'algorithmes à l'aide de boucles, et de fonctions

IV-A - Fonctions en JavaScript

IV-A-1 - Introduction

Comme en mathématiques, une fonction transforme des paramètres (en « entrée ») en une valeur de résultat (la « sortie »), lorsqu'elle est appelée. Avant de pouvoir l'appeler, on la définit par une suite d'instructions qui déterminera cette valeur de résultat, en fonction des paramètres qui lui seront passés.

Définir une fonction permet de regrouper des instructions JavaScript, afin qu'elles puissent être exécutées à différentes occasions, sans avoir à dupliquer le code correspondant.

Par exemple, sur le web, les fonctions JavaScript sont utilisées par le développeur pour définir le comportement que doit suivre le navigateur lorsque l'utilisateur effectue certaines actions (ex. : saisie dans un champ, clic sur un bouton, soumission d'un formulaire).

IV-A-2 - Définition et appel de fonction

On définit une fonction de la manière suivante :

1. function nomDeLaFonction (parametre1, parametre2, parametre3 ...) {

2. // instructions javascript

3. // pouvant utiliser les paramètres parametre1, parametre2 et parametre3

4. return resultat;

5. }

Par exemple :

1. function multiplierParDeux (nombre) {

2. return nombre * 2;

3. }

Pour exécuter une fonction, il faut l'appeler en citant son nom, et en lui fournissant des valeurs pour chacun des paramètres entre parenthèses.

Par exemple :

1. var resultat = multiplierParDeux(3); // => le paramètre nombre vaut 3 => la variable resultat vaudra 6

Comme pour une variable, l'appel à une fonction sera remplacé la valeur qu'elle renvoie, au moment de l'exécution du programme. Contrairement aux variables, cette valeur dépendra de la valeur des paramètres passés à la fonction.

Ainsi, il est possible de passer le résultat de l'appel d'une fonction en paramètre d'une fonction.

Exemple de substitution d'un appel de fonction par sa valeur de retour :  :

1. resultat = multiplierParDeux(multiplierParDeux(3)); // équivaut à :

2. resultat = multiplierParDeux(3 * 2); // qui équivaut à :

3. resultat = (3 * 2) * 2; // qui vaut finalement :

4. resultat = 12;

Et, avec une autre valeur passée en paramètre :

1. var resultat = multiplierParDeux(multiplierParDeux(4)); // équivaut à :

2. var resultat = multiplierParDeux(4 * 2); // qui équivaut à :

3. var resultat = (4 * 2) * 2; // qui vaut finalement :

4. var resultat = 16;

IV-A-3 - Importance de return

Quand on exécute une fonction depuis une console JavaScript, la valeur retournée par cette fonction est affichée dans la console. Il ne faut pas pour autant confondre le mot-clé return et la fonction console.log.

En effet :

- console.log peut être appelée plusieurs fois depuis une même définition de fonction, mais chaque appel de fonction ne peut résulter qu'en une seule valeur de retour spécifiée par return ;

- l'usage de return permet à l'appelant d'une fonction de disposer de la valeur résultante comme il le souhaite : il peut par exemple décider d'afficher cette valeur dans la console, ou dans un alert, ou même de la stocker dans une variable. Or, si la définition de cette fonction affiche la valeur résultante dans la console au lieu d'utiliser return, il sera impossible pour l'appelant de récupérer cette valeur résultante dans son programme.

IV-B - Pratique  : Définition et appel de fonction en JavaScript

Dans cette partie de mise en pratique, nous allons définir ensemble plusieurs fonctions, et les tester en les appelant.

Développer :

- une fonction diviserParDeux qui retourne la moitié de la valeur passée en paramètre. Tests  :

- diviserParDeux(2) === 1;

- diviserParDeux(4) === 2;

- var n = Math.random(); diviserParDeux(n) === n / 2;

- une fonction somme qui retourne la somme des deux paramètres qui lui seront passés. Tests  :

- somme(1, 1) === 2;

- somme(1, 2) === 3;

- somme(2, 1) === 3;

- var n = Math.random(); somme(n, 1) === n + 1;

- une fonction signe qui retourne la chaine de caractères positif, négatif ou nul, selon le signe de la valeur passée en paramètre. Tests  :

- signe(-1) === 'négatif';

- signe(0) === 'nul';

- signe(Math.random()) === 'positif';

- une fonction factorielle qui retourne le produit de tous les entiers consécutifs entre 1 et l'entier passé en paramètre (compris). Exemple : factorielle(3) retourne le résultat de 1 * 2 * 3, soit 6. Tests  :

- factorielle(0) === 1;

- factorielle(1) === 1;

- factorielle(3) === 6;

- factorielle(4) === 24;

Solution

function diviserParDeux (nombre) {
return nombre / 2;
}
function somme (nombre1, nombre2) {
return nombre1 + nombre2;
}
function signe (nombre) {
if (nombre === 0) {
return 'nul';
} else if (nombre > 0) {
return 'positif';
} else if (nombre < 0) {
return 'négatif';
}
}
function factorielle (nombre) {
var res = 1;
for(var i = 1; i <= nombre; i++) {
res = i * res;
}
return res;
}

IV-B-1 - Bogues et tests unitaires : comment tester une fonction

Appeler une fonction ajoute de l'incertitude et parfois de l'imprévisibilité au comportement du code, car cela revient à déléguer une fonctionnalité à une autre partie du code (la définition de la fonction appelée).

Afin de se rassurer sur le bon fonctionnement d'une fonction et éviter les bogues, il est important de tester les fonctions qu'on utilise.

Un bogue est un comportement imprévu causant des anomalies et/ou l'interruption de l'exécution du programme. Il est généralement causé par une erreur d'implémentation ou une réalisation trop naïve (c.-à-d. ne couvrant pas certains cas qui peuvent se produire).

Exemple d'implémentation naïve pouvant causer un bogue :

1. function multiplierParDix (nombre) {

2. return nombre + '0'; // on ajoute un zéro à la fin du nombre

3. }

4. multiplierParDix(2);   // => 20 => OK

5. multiplierParDix(3);   // => 30 => OK

6. multiplierParDix(0.5); // => 0.50 => Bogue! on voulait obtenir 5 dans ce cas

Dans l'exemple ci-dessus, nous avons effectué trois tests d'appel de notre fonction multiplierParDix, et l'un d'eux nous a permis de détecter un bogue dans notre fonction.

Afin de réduire le nombre de bogues potentiels d'une fonction, et donc de se rassurer sur son bon fonctionnement, il est important d'écrire et exécuter plusieurs tests unitaires, et penser intelligemment aux cas limites, les cas qui pourraient le plus probablement causer un bogue.

Écrire un test unitaire pour une fonction consiste à :

- décrire un exemple d'usage de cette fonction, en précisant la valeur résultante attendue pour certaines valeurs de paramètres,

- implémenter l'appel de cette fonction, et comparer la valeur résultante à celle qui est attendue.

Lors de l'exécution du test unitaire, si la valeur de la comparaison détermine si la fonction fonctionne comme prévu sur l'exemple de ce test.

Par exemple, on pourrait définir les trois tests unitaires suivants pour valider notre fonction multiplierParDix :

1. multiplierParDix(2) === 20;  // => false (car '20' différent de 20)

2. multiplierParDix(3) === 30;  // => false (car '30' différent de 30)

3. multiplierParDix(0.5) === 5; // => false (car '0.50' différent de 0.5)

Avec la définition de la fonction multiplierParDix fournie plus haut, aucun de ces tests ne passe. C'est-à-dire que chaque test d'égalité sera false.

En revanche, les tests unitaires passeront avec la définition suivante de cette même fonction :

1. function multiplierParDix (nombre) {

2. return nombre * 10;

3. }

4. multiplierParDix(2) === 20;  // => true => OK

5. multiplierParDix(3) === 30;  // => true => OK

6. multiplierParDix(0.5) === 5; // => true => OK

À retenir : un test unitaire est un exemple d'appel permettant de vérifier qu'une fonction se comporte comme prévu dans un cas donné, en comparant le résultat effectivement retourné au résultat qui devrait être retourné.

IV-B-2 - Valeur et affectation d'une fonction

Nous avons vu que l'appel d'une fonction consiste à mentionner son nom, suivi de paramètres exprimés entre parenthèses. Et que cet appel est remplacé par la valeur retournée par son exécution.

1. // définition de la fonction multiplierParDeux()

2. function multiplierParDeux (nombre) {

3. return nombre * 2;

4. }

5. // appel de la fonction multiplierParDeux(), en passant le nombre 3 en paramètre

6. var resultat = multiplierParDeux(3);

7. // la valeur retournée par l'appel de la fonction (6) est affectée à résultat

Ainsi, multiplierParDeux(3) est remplacé par sa valeur de retour : 6, après l'exécution de la fonction multiplierParDeux à laquelle on a passé la valeur littérale 3 comme valeur du paramètre appelé nombre.

Pour rappel, une variable JavaScript est remplacée par la dernière valeur qui lui a été affectée. Ainsi, si la valeur 6 a été affectée à la variable maVariable à l'aide de l'instruction maVariable = 6;, les mentions suivantes de maVariable seront remplacées par sa valeur 6.

En JavaScript, une fonction est une valeur, au même titre qu'un nombre ou une chaine de caractères. Elle peut donc aussi être attribuée à une variable.

Ainsi, il est possible d'affecter la fonction multiplierParDeux à la variable maVariable :

1. maVariable = multiplierParDeux;

… et de l'appeler de la manière suivante :

1. maVariable(3); // => retourne la valeur 6;

Il est donc aussi possible d'affecter une fonction anonyme à une variable :

1. var multiplierParTrois = function (nombre) {

2. return nombre * 3;

3. };

… ce qui est équivalent à écrire :

1. function multiplierParTrois (nombre) {

2. return nombre * 3;

3. }

IV-B-3 - Exercice : Jeu ChiFouMi

IV-B-3-a - Fonctionnement du jeu à implémenter

À chaque manche, l'ordinateur et le joueur choisissent chacun un élément parmi pierre, feuille ou ciseaux.

Un point est donné à celui qui a choisi l'élément le plus fort, sachant que :

- ciseaux > feuille (les ciseaux coupent la feuille)

- pierre > ciseaux (la pierre casse les ciseaux)

- feuille > pierre (la feuille enveloppe la pierre)

Si l'ordinateur et le joueur ont choisi le même élément, aucun d'eux n'emporte de point.

IV-B-3-b - Exemple de déroulement d'une manche

- l'ordinateur choisit secrètement pierre (parmi les trois valeurs d'éléments possibles) ;

- le joueur est invité à saisir son choix => il tape feuille ;

- l'ordinateur affiche feuille, car c'est l'élément qui l'emporte (la feuille enveloppe la pierre).

IV-B-4 - Phase 1 : Implémentation d'une manche

Pour implémenter le code d'une manche, nous allons :

- définir une fonction comparer(choix1, choix2) qui renvoie le nom de l'élément gagnant, entre les deux passés en paramètres ;

- appeler cette fonction, en passant les choix de l'ordinateur et du joueur en paramètres, afin de savoir lequel des deux a remporté la manche.

Voici une proposition d'étapes à suivre :

1. Dessiner l'arbre de décision d'une manche : nom de l'élément gagnant en fonction de deux éléments choisis ;

2. Transformer l'arbre de décision en conditions if imbriquées, en fonction de la valeur de deux variables : choix1 et choix2 ;

3. Chaque condition de dernier niveau va afficher dans la console le nom de l'élément qui remporte la manche ;

4. Transférer ces conditions dans la définition d'une fonction comparer(choix1, choix2) qui retourne le nom de l'élément gagnant à l'aide de return (au lieu de l'afficher dans la console), parmi les deux passés en paramètres ; Si les deux sont identiques, retourner égalité.

5. Tester cette fonction en lui passant chaque combinaison possible de valeurs du jeu en paramètres ;

6. En dehors de la définition de la fonction, créer les variables choixOrdi et choixUtilisateur ;

7. Faire en sorte que choixOrdi ait pour valeur un des trois éléments, choisi de manière aléatoire, et que choixUtilisateur soit saisi par l'utilisateur à l'aide de prompt() ;

8. Appeler la fonction comparer(), puis afficher dans la console la valeur de son résultat (l'élément qui remporte la manche), à partir des choix de l'ordinateur et du joueur.

IV-B-5 - Phase 2 : Partie en trois manches, et scores

Après avoir implémenté une manche à l'aide de la fonction comparer(), faites en sorte que le joueur puisse jouer trois manches d'affilée et que le score final du joueur et de l'ordinateur soient affichés dans la console en fin de partie.

Pour cela :

1. Créer les variables scoreOrdi et scoreJoueur ;

2. Après l'affichage du résultat de l'appel à comparer() dans la console, incrémenter une de ces variables, en fonction de qui a remporté la manche ;

3. Mettre le code correspondant à une manche dans une boucle for, de manière à ce qu'il s'exécute 3 fois d'affilée ;

4. En fin de partie, afficher qui a remporté la partie : 'ordi', 'joueur' ou 'aucun', en fonction des scores.

Solution de la phase 1

// definition de la fonction comparer
// qui renvoie le nom de l'élément gagnant, entre les deux passés en paramètres
function comparer(choix1, choix2) {
if (choix1 === choix2) {
return 'Egalité !';
} else if (choix1 === 'pierre') {
if (choix2 === 'ciseaux') {
return 'pierre';
} else if (choix2 === 'feuille') {
return 'feuille';
}
} else if (choix1 === 'feuille') {
if (choix2 === 'pierre') {
return 'feuille';
} else if (choix2 === 'ciseaux') {
return 'ciseaux';
}
} else if (choix1 === 'ciseaux') {
if (choix2 === 'pierre') {
return 'pierre';
} else if (choix2 === 'feuille') {
return 'ciseaux';
}
}
}
// début de partie
var choixUtilisateur = prompt('Choisissez-vous pierre, feuille, ou ciseaux ?');
var choixOrdi = Math.random();
if (choixOrdi < 0.34) {
choixOrdi = 'pierre';
} else if(choixOrdi <= 0.67) {
choixOrdi = 'feuille';
} else {
choixOrdi = 'ciseaux';
}
console.log('Ordinateur:', choixOrdi);
var resultat = comparer(choixUtilisateur, choixOrdi);
console.log('=> Gagnant:', resultat);

Solution de la phase 2

// definition de la fonction comparer
// qui renvoie le nom de l'élément gagnant, entre les deux passés en paramètres
function comparer(choix1, choix2) {
if (choix1 === choix2) {
return 'Egalité !';
} else if (choix1 === 'pierre') {
if (choix2 === 'ciseaux') {
return 'pierre';
} else if (choix2 === 'feuille') {
return 'feuille';
}
} else if (choix1 === 'feuille') {
if (choix2 === 'pierre') {
return 'feuille';
} else if (choix2 === 'ciseaux') {
return 'ciseaux';
}
} else if (choix1 === 'ciseaux') {
if (choix2 === 'pierre') {
return 'pierre';
} else if (choix2 === 'feuille') {
return 'ciseaux';
}
}
}
// début de partie
var scoreOrdi = 0;
var scoreJoueur = 0;
for (var manche = 0; manche < 3; manche++) {
// choix aléatoire de l'ordinateur
var choixOrdi = Math.random();
if (choixOrdi < 0.34) {
choixOrdi = 'pierre';
} else if(choixOrdi <= 0.67) {
choixOrdi = 'feuille';
} else {
choixOrdi = 'ciseaux';
}
console.log('Ordinateur:', choixOrdi);
// choix du joueur
var choixUtilisateur = prompt('Choisissez-vous pierre, feuille, ou ciseaux ?');
// déterminer qui remporte la manche
var resultat = comparer(choixUtilisateur, choixOrdi);
// afficher les résultats de la manche, et mettre à jour les scores
if (resultat === 'Egalité !') {
console.log('=> Egalité !');
} else if (resultat === choixUtilisateur) {
console.log('=> Gagnant de la manche:', resultat, '(joueur)');
scoreJoueur++;
} else {
console.log('=> Gagnant de la manche:', resultat, '(ordi)');
scoreOrdi++;
}
}
// afficher les résultats du jeu
var message;
if (scoreOrdi === scoreJoueur) {
message = 'Égalité !';
} else if (scoreOrdi > scoreJoueur) {
message = 'Perdu !';
} else {
message = 'Gagné !';
}
alert(message + ' Votre score: ' + scoreJoueur + ', ordi: ' + scoreOrdi);

V - TP 5 : Tableaux

Objectifs  :

- Savoir développer le Jeu du Pendu

- Utilisation de tableaux

V-A - Tableaux, introduction

Un tableau (appelé array en anglais) est un type avancé de valeurs. C'est une liste ordonnée de valeurs.

Exemple :  :

var fruits = [ 'Mangue', 'Raisin', 'Figue' ];

V-A-1 - Caractéristiques

Comme tous les autres types de valeur, un tableau peut être stocké dans une variable.

En revanche, en JavaScript, les tableaux sont considérés comme une forme particulière du type object. Autrement dit, typeof [] retourne "object".

Comme toute variable en JavaScript, les valeurs stockées dans un tableau peuvent être de n'importe quel type (y compris un tableau), et n'ont pas besoin d'être tous du même type :

var fourreTout = [ null, true, 'bonjour', 1.2, fruits, undefined ];

Tout tableau possède une propriété length qui vaut le nombre de valeurs qu'il contient :

fourreTout.length; // => retourne 6, car il y a 6 valeurs dans notre tableau

V-A-2 - Accéder aux éléments d'un tableau

Chaque élément d'un tableau est numéroté par ce qu'on appelle un « indice » (ou « index » en anglais).

À noter que cette numérotation commence par 0 (zéro), et non par 1.

Du coup, l'indice du premier élément est 0, et l'indice du dernier élément est length - 1 (length étant le nombre d'éléments du tableau).

On peut accéder à un élément de tableau en précisant son indice entre crochets :

1. var fruits = [ 'Mangue', 'Raisin', 'Figue' ];

2. fruits[0]; // vaut 'Mangue'

3. fruits[1]; // vaut 'Raisin'

4. fruits[2]; // vaut 'Figue'

5. fruits[3]; // undefined

V-A-3 - Modification d'un élément

Pour modifier la valeur d'un élément, il suffit de l'adresser par indice (comme vu juste avant) puis de lui affecter une valeur, comme on le ferait pour une variable :

1. var fruits = [ 'Mangue', 'Raisin', 'Figue' ];

2. fruits[1] = 'Kiwi';

3. fruits; // => vaut [ 'Mangue', 'Kiwi', 'Figue' ]

V-A-4 - Ajout d'élément

La méthode push() permet d'ajouter un élément à la fin du tableau. La valeur de cet élément est à passer en paramètre (entre parenthèses) de la méthode, quand on l'appelle :

1. var fruits = [ 'Mangue', 'Raisin', 'Figue' ];

2. fruits.push('Banane'); // appel de la méthode push() sur le tableau fruits, avec `Banane` en paramètre

3. fruits; // => [ 'Mangue', 'Raisin', 'Figue', 'Banane' ]

À chaque fois qu'on ajoute un élément à un tableau en appelant la méthode push(), sa longueur length est incrémentée (c.-à-d. sa valeur augmente de 1) :

1. fruits.length; // => 4, désormais

V-A-5 - Retrait d'élément

La méthode pop() retourne la dernière valeur du tableau puis la retire de ce tableau :

1. var fruits = [ 'Mangue', 'Raisin', 'Figue' ];

2. var f = fruits.pop();

3. f;      // => `Figue`

4. fruits; // => [ 'Mangue', 'Raisin' ];

À chaque fois qu'on retire un élément d'un tableau en appelant la méthode pop(), sa longueur length est décrémentée (c.-à-d. sa valeur décroit de 1) :

fruits.length; // => 2, désormais

V-A-6 - Pratique : Calendrier

1. var jours = [ 'lun', '007', 'mer', 'jeu', 'ven', 'sam', 'BUG' ];

Quelles instructions JavaScript faut-il exécuter pour effectuer les opérations suivantes ?

1. Retirer la dernière valeur du tableau jours

2. Afficher les valeurs du tableau dans la console

3. Ajouter la valeur 'dim' à la fin du tableau

4. Remplacer la valeur '007' par 'mar'

5. Afficher le nombre de valeurs du tableau dans la console

6. Afficher la troisième valeur du tableau dans la console

Quelle est la valeur finale du tableau, après avoir effectué toutes ces opérations ?

Solution

var jours = [ 'lun', '007', 'mer', 'jeu', 'ven', 'sam', 'BUG' ];
jours.pop();
console.log(jours);
jours.push('dim');
jours[1] = 'mar';
console.log(jours.length);
console.log(jours[2]);

V-B - Tableaux, fonctions avancées

V-B-1 - Recherche d'élément par valeur

La méthode indexOf() retourne l'indice du premier élément d'un tableau, pour une valeur donnée. La valeur recherchée est à passer en paramètre (entre parenthèses) de la méthode, quand on l'appelle  :

1. var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Raisin' ];

2. fruits.indexOf('Raisin'); // => 1

Si aucun élément du tableau ne contient la valeur cherchée, l'appel à indexOf() retourne -1 :

1. fruits.indexOf('Pomme'); // => -1, car il n'y a pas de valeur 'Pomme' dans fruits

V-B-2 - Concaténation de tableaux

Comme pour les chaines de caractères, il est possible de concaténer des tableaux.

La concaténation de deux tableaux consiste à créer un nouveau tableau contenant les éléments de ces deux tableaux.

La méthode concat() retourne un nouveau tableau contenant les éléments du tableau sur lequel elle est appelée, et d'un autre tableau passé en paramètre  :

1. var fruits1 = [ 'Mangue', 'Raisin' ];

2. var fruits2 = [ 'Figue', 'Kiwi' ];

3. fruits1.concat(fruits2); // => [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ]

Il est bien entendu possible d'utiliser des tableaux littéraux, au lieu de variables  :

1. [ 'Mangue', 'Raisin' ].concat([ 'Figue', 'Kiwi' ]); // => [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ]

V-B-3 - Partitionnement de tableaux

La méthode slice() retourne un nouveau tableau contenant un extrait du tableau sur lequel elle est appelée.

À chaque appel de cette méthode, il faut fournir deux paramètres : l'indice de l'élément où commence cet extrait, et l'indice de l'élément où se termine l'extrait (non compris) :

1. var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];

2. fruits.slice(1, 3); // => [ 'Raisin', 'Figue' ]

V-B-4 - Altération de tableau

La méthode splice() (à ne pas confondre avec slice()) permet à la fois de supprimer et d'insérer des éléments dans un tableau, en fournissant leur indice.

Syntaxe d'appel de la méthode : tableau.splice(i, c, v1, v2, ...), avec

- paramètre i : indice à partir duquel on va effectuer la modification

- paramètre c : nombre d'éléments à supprimer depuis l'indice i

- paramètre(s) suivant(s) : valeur(s) d'élément(s) à insérer à partir de l'indice i

Voici un exemple utilisant les paramètres i et c : (suppression seulement)

1. var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];

2. fruits.splice(1, 2); // depuis l'indice 1, supprimer 2 valeurs

3. fruits; // => [ 'Mangue', 'Kiwi' ]

Voici un exemple utilisant tous les paramètres : (insertion seulement)

1. var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];

2. fruits.splice(1, 0, 'Pomme'); // depuis l'indice 1, supprimer 0 valeurs, puis y insérer 'Pomme'

3. fruits; // => [ 'Mangue', 'Pomme', 'Raisin', 'Figue', 'Kiwi' ]

Contrairement aux méthodes concat() et slice(), splice() ne retourne pas un nouveau tableau, mais modifie le tableau sur lequel elle est appelée.

V-B-5 - Pratique  : Épicerie

1. var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];

Écrire un programme qui :

1. Affiche la liste de fruits disponibles ;

2. Demande au client quel fruit il désire acheter :

- s'il est présent dans le tableau fruits : le retirer du tableau, et afficher 'OK!',

- sinon, afficher 'indisponible...'.

3. Affiche à nouveau la liste de fruits disponibles.

Solution

var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];
console.log(fruits);
var reponse = prompt('quel fruit desirez vous');
var indice = fruits.indexOf(reponse);
if (indice !== -1) {
fruits.splice(indice, 1);
console.log('ok!');
} else {
alert('indisponible...');
}
console.log(fruits);

V-B-6 - Exercice  : Jeu du Pendu

Principe du jeu : Le joueur doit deviner un mot, lettre par lettre. Il perd après 10 mauvaises tentatives.

Nous allons utiliser les variables suivantes :

1. var mauvaisesLettres = [ ]; // sera complété pendant le jeu

2. var lettresAttendues = [ 's', 'u', 'p', 'e', 'r' ];

À chaque tentative, le joueur va proposer une lettre, puis :

- si la lettre fait partie du mot à deviner (dont les lettres sont stockées dans le tableau lettresAttendues), cette lettre sera supprimée du tableau ;

- sinon, elle sera ajoutée à la fin du tableau mauvaisesLettres.

Le joueur gagne la partie quand il a deviné toutes les lettres attendues du mot. (c.-à-d. le tableau lettresAttendues ne contient plus aucun élément)

Le joueur perd la partie dès que le tableau mauvaisesLettres contient 10 éléments. (c.-à-d. le nombre de tentatives autorisées)

Exercice  : implémenter le jeu du pendu en JavaScript.

Mots-clés à utiliser : alert, prompt, if, else, =, ===, indexOf, length, push, splice, et for

Conseil : avant de créer une boucle for, implémenter l'algorithme qui sera exécuté à chaque itération de cette boucle. (une itération = une tentative du joueur)

Étapes proposées :

1. Afficher le nombre de lettres à trouver, et de tentatives restantes.

2. Si mauvaisesLettres a une longueur de 10, afficher perdu!.

3. Si le nombre de lettresAttendues restantes à trouver est nul, afficher bravo!.

4. Demander une lettre au joueur, en utilisant prompt().

5. Si la lettre fait partie du tableau lettresAttendues, la supprimer du tableau. Sinon l'ajouter au tableau mauvaisesLettres. Afficher un message au joueur dans chaque cas.

6. Créer une boucle for permettant de jouer jusqu'à la fin de partie (perdu ou bravo).

BONUS : Dans le cas où le joueur devine une lettre qui apparaît plus d'une fois dans le mot, retirer toutes les occurrences de cette lettre en une seule fois.

Solution

'use strict';
var MAX_TENTATIVES = 10;
var mauvaisesLettres = [ ]; // sera complété pendant le jeu
var lettresAttendues = [ 's', 'u', 'p', 'e', 'r' ];
for (; mauvaisesLettres.length < MAX_TENTATIVES && lettresAttendues.length > 0; ) {
console.log('lettres à trouver:', lettresAttendues);
alert('Lettres à trouver: ' + lettresAttendues.length + '. '
+ 'Tentatives restantes: ' + (MAX_TENTATIVES - mauvaisesLettres.length));
var lettre = prompt('Devinez une lettre');
var indice = lettresAttendues.indexOf(lettre);
if (indice != -1) {
lettresAttendues.splice(indice, 1); // retire la 1ère occurrence de cette lettre trouvée dans le tableau
alert('Bonne pioche! Continuez!');
} else {
mauvaisesLettres.push(lettre);
alert('Le mot à trouver ne contient pas la lettre ' + lettre + ', désolé...');
}
}
if (lettresAttendues.length == 0) {
alert('Bravo, vous avez trouvé le mot !');
} else if (mauvaisesLettres.length == MAX_TENTATIVES) {
alert('Perdu... Faites une autre partie !');
}

BONUS

'use strict';
var MAX_TENTATIVES = 10;
var mauvaisesLettres = [ ]; // sera complété pendant le jeu
var lettresAttendues = [ 'a', 'n', 'n', 'a' ];
for (; mauvaisesLettres.length < MAX_TENTATIVES && lettresAttendues.length > 0; ) {
console.log('lettres à trouver:', lettresAttendues);
alert('Lettres à trouver: ' + lettresAttendues.length + '. '
+ 'Tentatives restantes: ' + (MAX_TENTATIVES - mauvaisesLettres.length));
var lettre = prompt('Devinez une lettre');
// tant qu'on trouve des occurrences, on continue de les supprimer une à une
var trouvees = 0;
for (var indice = 0; indice != -1 ;) {
indice = lettresAttendues.indexOf(lettre); // cherche la prochaine occurrence de la lettre
if (indice != -1) {
lettresAttendues.splice(indice, 1); // retire cette occurrence de la lettre trouvée dans le tableau
trouvees++;
}
}
if (trouvees > 0) {
alert('Bonne pioche ! Il y avait bien ' + trouvees + ' fois la lettre '
+ lettre + ' dans le mot à trouver !');
} else {
mauvaisesLettres.push(lettre);
alert('Le mot à trouver ne contient pas la lettre ' + lettre + ', désolé...');
}
}
if (lettresAttendues.length == 0) {
alert('Bravo, vous avez trouvé le mot !');
} else if (mauvaisesLettres.length == MAX_TENTATIVES) {
alert('Perdu... Faites une autre partie !');
}

VI - TP 6 : Objets

Objectifs :

- Savoir développer un répertoire téléphonique

- Utilisation d'objets

VI-A - Objets JavaScript

Dans la première moitié de ce chapitre, nous avons vu que les tableaux permettaient de stocker une liste ordonnée d'éléments (ayant chacun une valeur de n'importe quel type). Et que ces éléments étaient indexés par des numéros.

Exemple  :

1. var tableau = ['a', 'b', 4];

2. // => Correspondance entre numéros d'éléments et leur valeur

3. //    0 : 'a'

4. //    1 : 'b'

5. //    2 : 4

Les objets permettent aussi de stocker des valeurs.

Alors que chaque valeur d'un tableau est stockée dans un élément, chaque valeur d'un objet est stockée dans ce qu'on appelle une propriété.

Alors que chaque élément d'un tableau est indexé par un numéro (indice), chaque propriété d'un objet est indexée par une chaine de caractères.

VI-A-1 - Définition d'objet

Contrairement à la manière de définir un tableau, chaque valeur de propriété stockée dans un objet doit être précédée du nom de cette propriété (aussi appelé clé, ou key en anglais), et suivi par : (deux-points).

1. var objet = {

2. prop1 : 'a',

3. prop2 : 'b',

4. prop3 : 4,

5. };

6. // => Correspondance entre noms de propriétés et leur valeur

7. //    'prop1' : 'a'

8. //    'prop2' : 'b'

9. //    'prop3' : 4

À noter que chaque définition de propriété doit être ponctuée d'une virgule.

Les règles d'indentation s'appliquent aussi à la définition d'objets sur plusieurs lignes, car les propriétés sont définies entre accolades.

Le format JSON (JavaScript Object Notation, utilisé très couramment par les API de sites web, et exports structurés de données depuis le web) correspond à cette manière de définir des objets. Nous allons y revenir plus tard dans ce chapitre.

VI-A-2 - Accès aux propriétés d'un objet

Il existe deux manières d'accéder à la valeur d'une propriété d'objet :

1. La notation pointée, ex. : objet.prop

2. L'usage des crochets, ex. : objet[prop]

Comme pour les tableaux, l'adressage par crochets a un avantage intéressant : il permet d'accéder à un élément dont la clé est stockée dans une variable.

Exemple :

1. var objet = {

2. prop1 : 'a',

3. prop2 : 'b',

4. prop3 : 4,

5. };

6. // adressage littéral par notation pointée :

7. objet.prop2;        // => 'b'

8. // adressage littéral par crochets :

9. objet['prop2'];     // => 'b'

10. // adressage symbolique par crochets :

11. var cle = 'prop2';

12. objet[cle];         // => 'b'

La notation pointée est plus concise et lisible, mais impose de mentionner la clé « en dur » (littéralement) dans le code :

1. // adressage littéral, avec notation pointée :

2. objet.prop2; // => 'b'

3. // adressage symbolique, impossible avec notation pointée :

4. var cle = 'prop2';

5. objet.cle;   // => undefined, car il n'y a pas de propriété appelée 'cle' dans l'objet

VI-A-3 - Modification de propriétés

La modification d'une propriété d'objet fonctionne similairement à la modification d'un élément de tableau : il suffit d'effectuer une affectation après avoir adressé la valeur à modifier.

1. var tableau = ['a', 'b', 4];

2. tableau[1] = 3; // => tableau = ['a', 3, 4]

3. var objet = {

4. prop1 : 'a',

5. prop2 : 'b',

6. prop3 : 4,

7. };

8. objet.prop2 = 3; // => objet : { prop1 : 'a', prop2 : 3, prop3 : 4 }

Il est bien sûr possible d'utiliser indifféremment l'adressage par notation pointée ou par crochets, pour une affectation.

VI-A-4 - Clés riches

Contrairement aux restrictions imposées pour nommer les variables en JavaScript, il est possible d'inclure des espaces et des caractères spéciaux dans les noms de propriétés.

Par contre, il faut dans ce cas utiliser des apostrophes (ou guillemets) pour définir et adresser ces propriétés :

1. var mesAmis = {

2. 'Luke Skywalker' : true,

3. 'Dark Vador' : false,

4. };

5. mesAmis.LukeSkywalker;     // => undefined

6. mesAmis.Luke Skywalker;    // => /!\ syntax error

7. mesAmis['Luke Skywalker']; // => true

VI-A-5 - Notes sur les types

Comme pour les tableaux, la valeur d'une propriété d'objet peut être de n'importe quel type : string, number, boolean, null, undefined, object ou function.

À noter que, comme les objets, les tableaux sont aussi de type object.

1. typeof { prop : 'a' };  // => "object"

2. typeof [ 'a', 3, 4 ];  // => "object"

Mais les tableaux ont des caractéristiques particulières que n'ont pas les objets. Notamment l'indexation numérique, des fonctions spécifiques comme slice, etc.

VI-A-6 - Mise en pratique  : annuaire téléphonique

But  : développer un programme qui permet d'afficher le numéro de téléphone d'un ami (à l'aide de alert) dont le nom a été saisi par l'utilisateur (à l'aide de prompt).

Afin que l'annuaire soit extensible à l'avenir, aucune condition if ne doit être utilisée. Nous allons donc stocker les noms et numéros de téléphone dans un objet, et utiliser l'adressage par crochets pour trouver un numéro à partir d'un nom.

1. var annuaire = {

2. patricia : '06 66 66 66 66',

3. david : '07 77 77 77 77',

4. };

1. Afficher dans la console le numéro de téléphone de patricia, en utilisant la notation pointée sur l'objet annuaire;

2. Demander à l'utilisateur de saisir un prénom, puis afficher le numéro de téléphone associé à ce prénom.

Solution

var annuaire = {
patricia: '06 66 66 66 66',
david: '07 77 77 77 77',
};
// 1. Afficher dans la console le numéro de téléphone de `patricia`, en utilisant la notation pointée sur l'objet `annuaire`;
console.log(annuaire.patricia);
// 2. Demander à l'utilisateur de saisir un prénom, puis afficher le numéro de téléphone associé à ce prénom.
var nom = prompt('donner le nom d\'un ami');
alert('numéro de téléphone de ' + nom + ': ' + annuaire[nom]);

VI-B - Objets, usage avancé

VI-B-1 - Hiérarchie d'objets

Comme il est possible de stocker un objet comme valeur d'une propriété d'objet, cela veut dire que nous pouvons définir des hiérarchies / arbres d'objets.

Exemple :

1. var compteFacebook = {

2. amis : {

3. 'Luke Skywalker' : true,

4. 'Dark Vador' : false,

5. },

6. groupes : {

7. maitresJedi : {

8. titre : 'Groupe des maîtres Jedi',

9. membres : [ 'Yoda', 'Obi Wan' ],

10. },

11. lolcats : {

12. titre : 'Vive les chats !',

13. membres : [ 'Patrick' ],

14. },

15. },

16. };

Dans l'exemple ci-dessus, nous avons jusqu'à trois niveaux d'imbrication d'objets (voire quatre, si on compte les tableaux comme des objets) : l'objet compteFacebook contient une propriété groupes (de type objet) contenant une propriété maitresJedi (aussi de type objet) contenant deux propriétés (de type chaine de caractères et tableau).

Cet objet peut être représenté visuellement sous forme d'un arbre.

Pour accéder au premier élément du tableau membres de la sous-propriété maitresJedi, on doit écrire le cheminement à suivre, de propriété à sous-propriétés.

Exemple :

1. // adressage par notation pointée :

2. compteFacebook.groupes.maitresJedi.membres[0]; // => 'Yoda'

3. // adressage par crochets :

4. compteFacebook['groupes']['maitresJedi']['membres'][0]; // (idem)

Dans cet exemple aussi, on peut utiliser indifféremment la notation pointée ou les crochets, pour accéder à la propriété d'un objet.

VI-B-2 - Énumérer les propriétés d'un objet

Il existe deux manières d'énumérer les propriétés d'un objet :

1. Utiliser une boucle for-in ;

2. Itérer sur le tableau des clés de l'objet, à l'aide de la fonction Object.keys().

Une boucle for-in s'utilise de cette façon :

1. var mesAmis = {

2. 'Luke Skywalker' : true,

3. 'Dark Vador' : false,

4. };

5. for (var cle in mesAmis) {

6. console.log(cle, '->', mesAmis[cle]);

7. }

8. // => lignes affichées dans la console :

9. //    Luke Skywalker -> true

10. //    Dark Vador -> false

Contrairement à une boucle for classique (dans laquelle on définit une instruction d'initialisation, une expression conditionnelle et une instruction d'incrémentation, séparés par des points-virgules, pour rappel), la boucle for-in va répéter la liste d'instructions entre accolades pour chaque clé de l'objet.

Dans notre exemple de code ci-dessus, la variable cle prendra donc la valeur d'une clé de l'objet mesAmis, pour chacune de ses propriétés.

À noter que cela fonctionne aussi sur les tableaux, sauf que l'indice de chaque élément du tableau sera fourni (au lieu de la clé de chaque propriété de l'objet).

VI-B-3 - Énumérer les propriétés d'un objet, une autre manière

Lorsqu'elle est appelée en passant un objet en paramètre, la fonction Object.keys() retourne un tableau contenant les clés/noms de chaque propriété de cet objet.

1. var mesAmis = {

2. 'Luke Skywalker' : true,

3. 'Dark Vador' : false,

4. };

5. var cles = Object.keys(mesAmis); // => [ 'Luke Skywalker', 'Dark Vador' ]

On peut alors utiliser une boucle for plus classique pour itérer sur les valeurs de ce tableau :

1. for (var i = 0; i < cles.length; i++) {

2. var cle = cles[i];

3. console.log(i, ' :', cle, '->', mesAmis[cle]);

4. }

5. // => lignes affichées dans la console :

6. //    0, Luke Skywalker -> true

7. //    1, Dark Vador -> false

VI-B-4 - Suppression d'une propriété

Pour supprimer une propriété d'un objet, il faut utiliser le mot-clé delete de la manière suivante :

1. var objet = {

2. prop1 : 'a',

3. prop2 : 'b',

4. prop3 : 4,

5. };

6. delete objet.prop2;

7. // => objet === { prop1 : 'a', prop3 : 4 }

VI-B-5 - Exercice : Répertoire téléphonique

Sur la base de l'annuaire téléphonique réalisé plus haut, développer un programme qui propose les fonctionnalités suivantes :

- recherche et affichage d'un numéro de téléphone en saisissant un nom ;

- liste des contacts de l'annuaire (nom + numéro de téléphone, à afficher dans la console à l'aide d'une boucle for..in) ;

- ajout d'un contact (nom + numéro de téléphone) dans l'annuaire ;

- suppression d'un contact.

Pour permettre à l'utilisateur de choisir la fonctionnalité qu'il souhaite utiliser, lui demander de saisir la première lettre de la fonction :

- 'r' pour rechercher,

- 'l' pour afficher la liste,

- 'a' pour ajouter,

- et 's' pour supprimer.

Afin que l'utilisateur puisse utiliser plusieurs fonctionnalités d'affilée, placer le code du programme dans une boucle qui ne s'arrêtera que lorsque l'utilisateur aura tapé q au lieu de choisir une fonctionnalité.

Solution

var repertoire = {
'luke' : '0666666666',
'yoda' : '0688888888',
};
// non demandé dans l'énoncé, juste pour vérifier le bon fonctionnement dans la console:
console.log('[debug] repertoire:', repertoire);
// l'utilisateur peut effectuer plusieurs opération, jusqu'à ce qu'il presse `q`
for (var choix; choix != 'q';) {
choix = prompt('fonction: (r)echerche, (l)iste, (a)jout, (s)uppression, ou (q)uitter ?');
if (choix === 'r') {
var nom = prompt('donner le nom d\'un ami');
alert('numéro associé: ' + repertoire[nom]);
} else if (choix === 'l') {
for (var nom in repertoire) {
console.log(nom, ':', repertoire[nom]);
}
} else if (choix === 'a') {
var nom = prompt('donner le nom de l\'ami à ajouter');
var num = prompt('donner son numéro de téléphone');
repertoire[nom] = num;
} else if (choix === 's') {
var nom = prompt('donner le nom de l\'ami à supprimer du répertoire');
delete repertoire[nom];
}
// non demandé dans l'énoncé, juste pour vérifier le bon fonctionnement dans la console:
console.log('[debug] repertoire:', repertoire);
}

VII - TP 7 : Classes

VII-A - Bases de la POO : classes JavaScript

VII-A-1 - Programmation Orientée Objet : classes, instances et this

Une classe est un modèle d'objet. Elle peut être instanciée, c'est-à-dire qu'on crée un objet (appelé instance) selon ce modèle.

Le modèle d'une classe consiste à assurer que chaque objet instance de cette classe aura les mêmes :

- propriétés ; (cf chapitre sur les types avancés) ;

- méthodes : des fonctions qui s'appliquent à une instance donnée.

À noter que :

- chaque instance d'une classe aura les mêmes propriétés, mais la valeur de celles-ci pourra être différente pour chaque instance ;

- chaque instance d'une classe aura les mêmes méthodes, mais l'exécution de la fonction correspondante ne s'appliquera qu'à l'instance sur laquelle elle aura été appelée.

Comme nous allons le voir dans la suite du cours, les classes sont très utilisées pour manipuler la structure de pages Web. Notamment pour intégrer plusieurs instances d'un même composant sur une même page.

VII-A-2 - Comment instancier une classe en JavaScript/ES6

En guise d'exemple, supposons qu'on veuille intégrer un composant permettant d'afficher une galerie d'images sur notre page Web.

Supposons que ce composant soit défini par une classe nommée Galerie.

Comme pour toute classe, on peut instancier une Galerie en appelant son constructeur avec le mot-clé new. Le constructeur de cette classe prend un paramètre : conteneur, une portion de la page Web dans lequel la galerie s'intègrera.

Enfin, supposons que la classe Galerie fournisse des méthodes qui seront rattachées à chaque instance de cette classe :

- ajouterImage() permet de spécifier l'URL d'une image qui sera à afficher dans cette galerie ;

- et regenerer() permet de mettre à jour l'affichage de la galerie, après y avoir ajouté des images.

Voici un exemple d'instanciation de cette classe :

1. // supposons que conteneur référence un <div> de la page

2. var maGalerie = new Galerie(conteneur);

3. maGalerie.ajouterImage('img7.jpg');

4. maGalerie.regenerer();

Le mot-clé new permet d'instancier notre classe, et donc d'exécuter son constructeur en fournissant une valeur pour le paramètre conteneur. Comme pour une fonction, l'appel au constructeur retourne l'instance de Galerie fraichement créée.

VII-A-3 - Comment définir une classe en JavaScript/ES6

Afin de permettre l'instanciation de la classe Galerie, le créateur du composant a dû la définir de la manière suivante :

1. class Galerie {

2. // définition du constructeur de la classe Galerie

3. constructor(conteneur) {

4. this.conteneur = conteneur;

5. this.urlImages = [];

6. }

7. // cette méthode permet d'ajouter une image à cette galerie

8. ajouterImage(url) {

9. this.urlImages.push(url);

10. }

11. // cette méthode permet de générer et d'afficher cette galerie dans la page

12. regenerer() {

13. var html = '';

14. // génération des éléments <img> dans le conteneur

15. for (var i = 0; i < this.urlImages.length; i++) {

16. html = html + '<img src="' + this.urlImages[i] + '" class="hidden">;';

17. }

18. this.conteneur.innerHTML == html;

19. }

20. }

À noter  :

- le paramètre conteneur du constructeur de la classe a été affecté comme propriétés d'un certain objet this. (nous allons expliquer ça plus bas)

VII-A-4 - Usage de this

Quand on mentionne this dans la définition d'une méthode, ce mot-clé représente l'instance depuis laquelle la méthode a été appelée.

Par exemple :

1. class Article {

2. constructor(titre) {

3. this.titre = titre;

4. }

5. getTitre() {

6. return this.titre; // this === article1 ou article2, dans notre exemple

7. }

8. }

9. var article1 = new Article('Trump élu président');

10. var article2 = new Article('Macron se présente');

11. article1.getTitre(); // => retourne 'Trump élu président'

12. article2.getTitre(); // => retourne 'Macron se présente'

À noter qu'en JavaScript, this est en fait utilisable depuis toute fonction, qu'elle soit ou pas définie dans une classe. Il faut retenir que l'usage de classes permet à l'interpréteur JavaScript d'affecter automatiquement à this l'instance sur laquelle s'exécute chaque méthode.

VII-A-5 - Exercice : Création de classe

--> http://marijnhaverbeke.nl/talks/es6_falsyvalues2015/exercises/#Point

VIII - TP 8 : Manipuler le Web avec DOM

Objectifs :

- Exécuter du code JavaScript depuis une page HTML

- Manipuler une page HTML depuis un programme JavaScript

- Introduction aux concepts d'API et d'événements

- Développer une calculatrice dans le navigateur web

Références :

- Référence JavaScript du Mozilla Developer Network (en français)

- JavaScript Kit (en Anglais, moins à jour, mais plus concis et avec des exemples plus simples)

Aide-mémoire :

- OverAPI's JavaScript Cheatsheet (liste des propriétés et fonctions JavaScript/DOM, classées par type)

- JavaScript Cheat Sheet by DaveChild (liste des principales fonctions et propriétés JavaScript/DOM)

VIII-A - JavaScript et le DOM

Dans les chapitres précédents, nous avons exécuté des programmes JavaScript simples de manière interactive, depuis la console de notre navigateur.

Dans ce chapitre, nous allons voir :

- comment associer un programme JavaScript à une page web,

- et comment modifier cette page dynamiquement depuis notre programme.

VIII-A-1 - Terminologie : quelques rappels sur le Web

- WWW : World Wide Web, c'est le nom donné à l'ensemble des pages liées entre elles sur Internet via le protocole HTTP (nous en reparlerons dans le chapitre suivant). La plupart de ces pages sont décrites en langage HTML.

- HTML : HyperText Markup Language, c'est un langage qui permet de décrire la structure et le contenu d'une page web, en utilisant des éléments (balises).

- CSS : Cascading Style Sheets, c'est un langage qui permet de mettre en page et styliser les éléments HTML d'une page web en leur appliquant des propriétés.

- JavaScript : c'est le seul langage qui permet de donner des instructions exécutables depuis une page web au format HTML.

- Navigateur Web / Web Browser : c'est un logiciel d'affichage de pages web (généralement composées de fichiers HTML, CSS et JavaScript), et permettant à l'utilisateur d'interagir avec celles-ci.

- DOM : Document Object Model, c'est à la fois le nom qu'on donne à l'ensemble des éléments qui constituent une page Web ouverte dans le navigateur (telle qu'elle est structurée en mémoire), et à l'API qui permet de manipuler ces éléments.

- API : Application Programming Interface, est un ensemble de fonctions fournies par un logiciel (par exemple : un navigateur Web, ou un serveur Web) qui permettent à d'autres programmes d'interagir / échanger des informations avec lui.

VIII-A-2 - Associer un programme JavaScript à une page web

Vous devez savoir qu'une page web peut être associée à une feuille de style CSS. Pour cela, le code source HTML de cette page doit contenir un élément <link> donnant l'URL du fichier CSS correspondant.

De la même façon, pour associer un programme JavaScript à une page web, il suffit d'ajouter un élément <script> donnant l'URL du programme en question (dont le fichier porte généralement l'extension .js).

Exemple de code source HTML d'une page web :

1. <!DOCTYPE html>

2. <html>

3. <head>

4. <link rel="stylesheet" href="style.css" />

5. </head>

6. <body>

7. <h1>Bonjour !</h1>

8. <script src="script.js"></script>

9. </body>

10. </html>

Trois choses importantes à remarquer :

- l'élément <script> peut être défini dans le <head> ou dans le <body>, mais il est généralement recommandé de l'insérer juste avant la fin du <body> ;

- l'URL du script doit être fournie via l'attribut src ;

- mais surtout, contrairement aux éléments <link>, les éléments <script> ne doivent pas être exprimés sous forme d'une balise autofermante (finissant par />) => il est impératif d'utiliser une balise fermante </script> après chaque balise ouvrante <script>.

Les scripts ainsi intégrés dans le <body> de la page seront exécutés au fur et à mesure qu'ils sont découverts et chargés par le navigateur.

Remarque, il est aussi possible d'intégrer directement notre programme JavaScript entre les balises <script> et </script>, pour éviter de le stocker dans un fichier séparé. Cette méthode n'est pas recommandée, car elle peut causer des erreurs de syntaxe.

VIII-A-3 - Application : Dire bonjour au monde

Vous allez créer une page web qui affiche Bonjour le monde ! dans une fenêtre à l'aide de la fonction alert().

Pour cela :

1. Créez un fichier bonjour.html définissant une page web minimale, mais valide, contenant seulement un titre de type <h1>.

2. Ouvrez cette page dans votre navigateur, pour vérifier qu'elle s'affiche correctement.

3. Créez un fichier bonjour.js contenant un programme JavaScript permettant d'afficher le message demandé.

4. Testez votre programme JavaScript en le copiant dans la console du navigateur, depuis n'importe quelle page.

5. Associez le fichier bonjour.js à la page bonjour.html, à l'aide d'un élément <script>.

6. Rafraichissez la page bonjour.html dans votre navigateur, pour vérifier que le message demandé s'affiche bien dès l'affichage de la page.

7. En cas de problème, vérifiez la présence d'erreurs dans la console JavaScript du navigateur, corrigez-les, et réessayez.

bonjour.html

<!DOCTYPE html>
<html>
<body>
<h1>Bonjour !</h1>
<script src="bonjour.js"></script>
</body>
</html>

VIII-A-4 - Accéder aux éléments de la page Web depuis JavaScript

Les navigateurs Web (tels que Google Chrome) donnent accès à une API (voir définition plus haut) permettant à nos programmes JavaScript d'interagir avec le DOM de la page Web à laquelle ils sont liés.

C'est-à-dire qu'un script intégré dans une page peut utiliser des fonctions permettant de manipuler le contenu de cette page. Par exemple : pour récupérer des informations saisies par l'utilisateur dans des champs de la page, ou encore modifier le contenu et/ou l'affichage de la page.

Pour accéder à un élément de la page, il faut identifier précisément (c.-à-d. sans ambiguïté) cet élément auprès du navigateur. Par exemple : en l'adressant par son identifiant unique (attribut id de l'élément).

Pour cela, l'API du DOM met à notre disposition un objet (cf. chapitre précédent) appelé document, et cet objet contient plusieurs fonctions. Nous allons d'abord nous intéresser à la fonction getElementById() qui permet d'accéder à l'objet représentant un élément de la page, en fonction de son identifiant unique.

Supposons que notre page Web contienne les éléments suivants :

1. <body>

2. <p id="premier-paragraphe">Bonjour</p>

3. <p id="deuxieme-paragraphe">le monde</p>

4. </body>

Nous pouvons alors accéder au premier paragraphe en JavaScript (ex. : depuis un script rattaché à cette page, ou depuis la console du navigateur) de la manière suivante :

1. document.getElementById('premier-paragraphe');

2. // => retourne un objet qui représente l'élément HTML correspondant

En exécutant cet appel de fonction dans la console, on voit s'afficher ce qu'elle retourne : un objet JavaScript qui représente l'élément HTML ayant premier-paragraphe comme identifiant.

Nous allons voir plus bas que l'objet document fourni par l'API du DOM contient d'autre fonctions permettant d'accéder à des éléments, comme getElementsByClassName() ou encore querySelector().

VIII-A-4-a - Application

1. Allez sur le site web de votre choix ;

2. Utilisez l'onglet « Éléments » de Chrome Dev Tools (barre latérale dans laquelle se trouve aussi la console JavaScript) ;

3. Repérez un élément qui possède un attribut id ;

4. Dans la console, utilisez getElementById() de manière à afficher l'objet JavaScript représentant cet élément.

Astuce : pour accéder plus rapidement à la partie du DOM qui représente un élément d'une page Web, effectuez un clic droit sur cet élément de la page, puis cliquez sur « Inspecter ». Vous vous retrouverez immédiatement dans l'onglet « Éléments » de la page, à l'endroit où est défini cet élément.

VIII-A-5 - Récupérer la valeur d'un champ de saisie <input>

Maintenant que nous savons accéder à l'objet JavaScript correspondant à un élément HTML de la page, nous allons voir comment récupérer des données de cet élément.

Pour cela, nous allons utiliser la propriété value de l'objet JavaScript représentant un élément HTML.

Imaginons une page HTML contenant le formulaire suivant :

1. <body>

2. <form>

3. <label for="nom">Nom :</label>

4. <input id="nom" value="Michel" />

5. <label for="prenom">Prénom :</label>

6. <input id="prenom" value="Jean" />

7. </form>

8. </body>

Pour accéder à l'objet JavaScript représentant le champ portant l'identifiant nom, nous allons utiliser la fonction getElementById() de cette manière :

1. document.getElementById('nom');

Enfin, pour récupérer la valeur actuelle de ce champ, il suffit d'utiliser la propriété value de cet objet :

1. document.getElementById('nom').value;

Dans notre exemple, l'exécution de cette instruction JavaScript retournera la valeur du champ correspondant : "Michel".

Si on exécute à nouveau cette instruction après que l'utilisateur a modifié la valeur du champ, la valeur retournée correspondra à la valeur actuelle du champ (après modification).

VIII-A-6 - Pratique : Récupération des valeurs du formulaire

Afin de mettre en pratique l'association d'un programme JavaScript à une page HTML, l'accès à un élément en JavaScript, et la récupération de la valeur d'un champ de saisie, vous allez :

1. Créer un dossier JS-FORM-1 ;

2. Dans ce dossier, créer un fichier index.html valide contenant deux champs de saisie, tels que définis dans l'exemple ci-dessus (avec identifiants nom et prenom);

3. Toujours dans ce dossier, créer un fichier index.js contenant un programme JavaScript qui affichera avec alert() la valeur du premier champ de la page, puis celle du deuxième champ ;

4. Associez votre programme JavaScript (fichier index.js) à votre page HTML (fichier index.html), de manière à ce qu'il soit exécuté quand on ouvre la page, et vérifiez que les deux valeurs sont bien affichées dans des alert lors du chargement de la page dans votre navigateur ;

5. En cas de malfonctionnement, corrigez les erreurs dans vos fichiers, en vous aidant de la console JavaScript.

index.html

<!DOCTYPE html>
<html>
<body>
<h1>Bonjour !</h1>
<input id="nom" value="Michel" />
<input id="prenom" value="Jean" />
<script src="index.js"></script>
</body>
</html>

index.js

alert(document.getElementById('nom').value);
alert(document.getElementById('prenom').value);

VIII-A-7 - Réagir aux actions de l'utilisateur sur la page

Maintenant que nous savons accéder aux données d'une page HTML depuis un programme JavaScript, et faire en sorte que ce programme s'exécute au chargement de la page, nous allons voir comment exécuter des instructions JavaScript en réponse à une action de l'utilisateur sur la page.

À chaque fois que l'utilisateur interagit avec une page Web, le navigateur déclenche des événements. Ces événements sont mis à disposition par l'API du DOM, afin qu'un programme JavaScript puisse les intercepter et réagir à certains d'entre eux.

Quelques exemples d'événements :

- click : l'utilisateur a cliqué sur un élément ;

- change : l'utilisateur a changé la valeur d'un champ de saisie ;

- mouseover : l'utilisateur a survolé un élément à l'aide de la souris.

Vous trouverez la liste des événements standard sur cette page : Event reference - MDN.

On peut définir le comportement (la réaction) que doit adopter le navigateur lorsqu'un événement survient, en y associant une fonction JavaScript.

Par exemple, nous pourrions définir une fonction direBonjour() contenant alert('bonjour !'), puis demander au navigateur d'appeler cette fonction à chaque fois que l'utilisateur clique sur un bouton.

Il existe plusieurs moyens d'intercepter des événements en y attachant une fonction :

- la fonction addEventListener() (que nous verrons peut-être plus tard) ;

- les propriétés on* associées à chaque nom d'événement, ex. : onclick, onchange, onmouseover…

Pour l'instant, nous allons employer la méthode la plus simple : affecter une fonction à la propriété d'un élément correspondant à l'événement choisi.

Imaginons une page HTML contenant un bouton :

1. <body>

2. <button id="mon-bouton">Mon Beau Bouton</button>

3. </body>

Pour afficher un alert à chaque fois que l'utilisateur cliquera sur ce bouton (événement click), nous devons affecter une fonction à la propriété onclick de l'objet JavaScript représentant ce bouton :

1. document.getElementById('mon-bouton').onclick = function direBonjour() {

2. alert('bonjour !');

3. };

VIII-A-8 - Exercice : Calculatrice

Afin de pratiquer la récupération de valeurs d'un formulaire et l'exécution de code JavaScript lorsque l'utilisateur clique, nous allons développer une calculatrice simple en HTML + JavaScript.

La page HTML à développer doit contenir :

- deux champs <input> portant les valeurs d'id : « premier-nombre » et « deuxieme-nombre » ;

- un troisième champ portant l'id : « resultat »;

- et un bouton portant l'id : « mon-bouton ».

Ensuite, y associer un fichier JavaScript qui permettra à l'utilisateur, à chaque fois qu'il cliquera sur le bouton mon-bouton, d'obtenir le résultat de l'addition des nombres qu'il aura saisis dans les champs premier-nombre et deuxieme-nombre, dans le champ resultat.

Vous aurez probablement besoin d'utiliser la fonction parseInt() ou parseFloat() pour convertir des chaines de caractères en véritables nombres.

Solution :

index.html

<!DOCTYPE html>
<html lang="fr">
<head>
<title>Calculatrice</title>
<meta http-equiv="Content-type" content="text/html; charset=ISO-8859-1"/>
</head>
<body>
<h1>Calculatrice</h1>
<p>premier nombre:</p>
<input id="premier-nombre" />
<p>deuxième nombre:</p>
<input id="deuxieme-nombre" />
<button id="mon-bouton">calculer l'addition</button>
<p>résultat:</p>
<input id="resultat" />
<script src="index.js"></script>
</body>
</html>

index.js

document.getElementById('mon-bouton').onclick = function() {
var nombre1 = parseInt(document.getElementById('premier-nombre').value);
var nombre2 = parseInt(document.getElementById('deuxieme-nombre').value);
document.getElementById('resultat').value = nombre1 + nombre2;
};

Solution Bonus :

index.html

<!DOCTYPE html>
<html lang="fr">
<head>
<title>Calculatrice</title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>Calculatrice</h1>
<p>premier nombre:</p>
<input id="premier-nombre" />
<p>opération:</p>
<select id="operation">
<option>+</option>
<option>-</option>
<option>*</option>
<option>/</option>
</select>
<p>deuxième nombre:</p>
<input id="deuxieme-nombre" />
<button id="mon-bouton">calculer</button>
<p>résultat:</p>
<input id="resultat" />
<script src="index.js"></script>
</body>
</html>

index.js

document.getElementById('mon-bouton').onclick = function() {
var nombre1 = parseInt(document.getElementById('premier-nombre').value);
var nombre2 = parseInt(document.getElementById('deuxieme-nombre').value);
var operation = document.getElementById('operation').value;
var result;
if (operation === '-') {
result = nombre1 - nombre2;
} else if (operation === '*') {
result = nombre1 * nombre2;
} else if (operation === '/') {
result = nombre1 / nombre2;
} else {
result = nombre1 + nombre2;
}
document.getElementById('resultat').value = result;
};

Par ailleurs, pour éviter que votre bouton ne soumette votre formulaire quand vous cliquerez dessus, vous aurez peut-être besoin d’ajouter l’attribut type="button" à cet élément.

VIII-A-9 - Exercice Bonus : Calculatrice multiopérations

Sur la base de la calculatrice ci-dessus, ajouter un composant de sélection de l'opération qui sera effectuée entre les deux nombres lorsque l'utilisateur cliquera sur le bouton : addition, soustraction, multiplication et division.

IX - TP 9 : Manipuler les classes et styles CSS

Objectifs :

- Utiliser JavaScript pour manipuler le contenu et l'apparence de pages Web

- Développer une liste avec filtrage dynamique par catégorie

- Comprendre la portée des variables en JavaScript

Plan du cours :

1. Manipulation de classes CSS

- Application : Afficher/cacher

2. Accéder à des éléments par classe

- Application : Cacher les spoilers

3. Manipulation de styles CSS

- Application : Surbrillance au clic

- Exercice : Filtrage par catégorie

4. Accéder à des éléments par nom d'élément

5. Manipulation d'attributs

6. Manipulation de contenu HTML d'un élément

7. Scope et génération de fonctions

IX-A - Manipulation de classes CSS

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 :

1. <p id="premier">texte visible</p>

2. <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 :

1. .hidden {

2. display : none;

3. }

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 :

1. var element = document.getElementById('un-element');

2. element.classList.add('hidden'); // => la classe hidden va être associée à l'élément

IX-A-1 - Exercice : Afficher/cacher

- Créer une page web valide contenant les deux paragraphes <p> tels que définis plus haut.

- Y associer un fichier CSS contenant la règle .hidden telle que définie plus haut.

- Ouvrir la page, vérifier que seul le premier paragraphe est visible.

- Quel code JavaScript faut-il exécuter pour retirer la classe hidden du deuxième paragraphe ?

<p id="premier">texte visible</p>
<p id="second" class="hidden">ce texte devrait être caché</p>

.hidden {
display: none;
}

document.getElementById('second').classList.remove('hidden');

Solution : Codepen

IX-B - Accéder à des éléments par classe

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 :

1. <p>C'est l'histoire d'un garçon troublé</p>

2. <p class="spoiler">,car il s'avère que son ours en peluche a disparu</p>

3. <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 :

1. 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  :

1. for (var i = 0; i < elements.length; i++) {

2. var element = elements[i]; // i-ème élément du tableau d'éléments

3. }

IX-B-1 - Exercice : Cacher les spoilers

- Créer une page web valide contenant les trois paragraphes <p> tels que définis dans l'exemple ci-dessus.

- Y associer un fichier CSS contenant la règle .hidden telle que définie plus haut.

- Ouvrir la page, vérifier que les trois paragraphes sont visibles.

- Quel code JavaScript faut-il exécuter pour ajouter la classe hidden aux paragraphes qui portent la classe spoiler ?

- Ajouter un bouton à la page HTML, et y intégrer le code JS nécessaire pour que les spoilers soient cachés seulement une fois que l'utilisateur aura cliqué sur le bouton.

Attention, aucun code JavaScript ne doit apparaître dans votre fichier HTML => faire un fichier .js à part.

<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>
<button id="mon-bouton">cacher spoilers</button>

.hidden {
display: none;
}

var tab = document.getElementsByClassName('spoiler');
document.getElementById('mon-bouton').onclick = function() {
for (var i=0; i<tab.length; i++) {
tab[i].classList.add('hidden');
}
};

Solution : Codepen

IX-C - Manipulation de styles CSS

Nous savons à présent :

- accéder à des éléments HTML, à partir de leur id ou class ;

- modifier la liste des classes associées à un élément HTML.

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 (chaine 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 :

1. #mon-element {

2. border : 1 solid black;

3. background-color : red;

4. }

… et l'objet JavaScript correspondant, tel qu'associé à la propriété style de l'élément :

1. {

2. border : '1 solid black',

3. backgroundColor : 'red'

4. }

Il y a deux différences importantes à noter :

- la propriété style de l'élément doit respecter la syntaxe d'objet JavaScript (JSON), donc les valeurs de propriétés sont des chaines de caractères (entre apostrophes), et les propriétés doivent être séparées par des virgules (au lieu des points-virgules de la notation CSS) ;

- enfin, les noms de propriétés contenant des tirets doivent être écrits en respectant la notation « camel case » (pour rappel : mots collés, avec majuscule en début de chaque mot sauf le premier).

IX-C-1 - Exemple de changement de style

Prenons le fichier HTML suivant :

1. <p id="premier">texte important</p>

2. <p id="second">autre texte</p>

Pour modifier la couleur de fond du premier paragraphe, il faut exécuter le code JavaScript suivant :

1. var element = document.getElementById('premier');

2. element.style.backgroundColor = 'red';

IX-C-2 - Exercice : Surbrillance au clic

- Créer une page web valide contenant les deux paragraphes <p> tels que définis dans l'exemple ci-dessus.

- Ouvrir la page, vérifier que les deux paragraphes sont visibles.

- Intégrer le code JavaScript nécessaire pour que la couleur de fond du deuxième paragraphe devienne jaune quand l'utilisateur clique dessus.

- Bonus : intégrer le code JavaScript nécessaire pour que la couleur de fond de n'importe quel paragraphe de la page devienne jaune quand l'utilisateur clique dessus. (indice : il va falloir utiliser le paramètre fourni à l'appel de votre fonction onclick, nous ne l'avons pas encore vu au cours).

Solution : Codepen

<p id="premier">texte important</p>
<p id="second">autre texte (cliquable)</p>

Bonus)

var element = document.getElementById('second');
element.onclick = function() {
element.style.backgroundColor = 'yellow';
};

IX-C-3 - Exercice à rendre : Filtrage par catégorie

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 :

- incluez au minimum dix produits et trois catégories ;

- chaque produit doit être représenté par un élément HTML ;

- les catégories de chaque produit doivent être associées aux éléments correspondants à l'aide de l'attribut class ;

- le filtrage par catégories doit s'appuyer sur ces classes ;

- l'utilisateur doit pouvoir passer rapidement d'une catégorie à une autre, puis afficher la liste complète (non filtrée) des produits ;

- au moins deux de vos produits doivent être présents dans plusieurs catégories.

Exemple de liste de produits en HTML :

1. <article class="ludique pratique">iPad</article>

2. <article class="ludique sportif">Batte de baseball</article>

3. <article class="pratique">Ventilateur</article>

Vous serez évalué(e) selon les critères suivants :

- bon fonctionnement du filtrage ;

- concision et qualité du code ;

- bonus : esthétique du rendu et simplicité d'usage (ergonomie).

Les solutions les plus élégantes seront présentées au cours.

Solution : Codepen

<h1>Filtrage par catégorie</h1>
<button id="tous">tous</button>
<button id="ludique">ludique</button>
<button id="sportif">sportif</button>
<button id="pratique">pratique</button>
<article class="article ludique pratique">iPad</article>
<article class="article ludique sportif">Batte de baseball</article>
<article class="article pratique">Ventilateur</article>
<article class="article ludique">XBox</article>
<article class="article sportif">Gants de golf</article>
<article class="article pratique">Porte-clés</article>
<article class="article pratique">Carnet</article>
<article class="article ludique sportif">Balle de baseball</article>
<article class="article sportif ludique">Boules de pétanque</article>
<article class="article ludique">Yoyo</article>

function changerDisplay(classe, valeur) {
var articles = document.getElementsByClassName(classe);
for (var i=0; i<articles.length; ++i) {
articles[i].style.display = valeur;
}
}
document.getElementById('tous').onclick = function() {
changerDisplay('article', 'block'); // afficher tous les articles
};
document.getElementById('ludique').onclick = function() {
changerDisplay('article', 'none'); // cacher tous les articles
changerDisplay('ludique', 'block'); // afficher articles ludiques
};
document.getElementById('pratique').onclick = function() {
changerDisplay('article', 'none'); // cacher tous les articles
changerDisplay('pratique', 'block'); // afficher articles pratiques
};
document.getElementById('sportif').onclick = function() {
changerDisplay('article', 'none'); // cacher tous les articles
changerDisplay('sportif', 'block'); // afficher articles sportifs
};

IX-D - Accéder à des éléments par nom d'élément

IX-D-1 - Fonction DOM : 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 ;

- et 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 :

1. var images = document.getElementsByTagName('img');

2. // => images est un tableau contenant tous les éléments <img> de la page

À noter que, comme getElementsByClassName(), getElementsByTagName() retourne un tableau d'éléments.

IX-E - Manipulation d'attributs

IX-E-1 - Fonctions DOM : 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 standard des éléments HTML.

En guise d'exemple, imaginons l'élément HTML suivant :

1. <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 :

1. document.getElementById('mon-bouton').getAttribute('data-numero');

2. // => cet appel de fonction retournera 5

Pour modifier la valeur de cet attribut, utilisons la fonction setAttribute() de la manière suivante :

1. document.getElementById('mon-bouton').setAttribute('data-numero', 7);

2. // => l'attribut va prendre la valeur 7, au lieu de 5

IX-F - Manipulation de contenu HTML d'un élément

IX-F-1 - Propriété 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 :

1. <!-- Avant : -->

2. <p id="mon-parag">texte initial</p>

1. document.getElementById('mon-parag').innerHTML = 'image : <img src="image.jpg">';

Cette affectation modifiera le DOM de la page web de la manière suivante :

1. <!-- Après : -->

2. <p id="mon-parag">image : <img src="image.jpg"></p>

IX-G - Scope et génération de fonctions

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 :

1. // supposons que elements soit un tableau d'éléments HTML

2. for (var i = 0; i < elements.length; i++) {

3. elements[i].classList.add('bordure-rouge');

4. }

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 :

1. // supposons que elements soit un tableau d'éléments HTML

2. for (var i = 0; i < elements.length; i++) {

3. elements[i].onclick = function() {

4. alert('vous avez cliqué sur l\'élément n&#186;' + i);

5. }

6. }

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).

IX-G-1 - « Scope » : portée des variables

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 :

1. var variableGlobale = 4;

2. function maFonction() {

3. var variableLocale = 5;

4. console.log('(maFonction) variableGlobale :', variableGlobale); // => 4

5. console.log('(maFonction) variableLocale :', variableLocale);   // => 5

6. }

7. maFonction();

8. console.log('variableGlobale :', variableGlobale); // => 4

9. 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 a été définie 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 :

1. La closure : définir une fonction anonyme et l'appeler dans la foulée ;

2. Appeler une fonction qu'on aura définie à l'extérieur de notre boucle.

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.

IX-G-2 - Fonction génératrice de fonctions

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 :

1. function mere() {

2. console.log('fonction mere appelée');

3. return function fille() {

4. console.log('fonction fille appelée');

5. };

6. }

7. var fct = mere(); // => affiche 'fonction mere appelée' puis retourne la fonction fille

8. // => la fonction fille est affectée à la variable fct

9. 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 :

1. function mere(i) {

2. // i est *promue* comme variable locale à la fonction mere

3. // => à chaque appel de la fonction qu'elle retourne, elle aura conservé sa valeur

4. return function fille() {

5. alert('vous avez cliqué sur l\'élément n&#186;' + i);

6. // i fait référence à la valeur qui avait été passée en paramètre

7. // de l'appel à la fonction mere(), à chaque itération de boucle.

8. };

9. }

10. // supposons que elements soit un tableau d'éléments HTML

11. for (var i = 0; i < elements.length; i++) {

12. elements[i].onclick = mere(i);

13. // => la fonction retournée par mere va être affectée à fille

14. }

X - TP 10 : Manipulation DOM avancée

Objectifs :

- Inspecter la source d'un événement du DOM

- Manipuler la structure du DOM en JavaScript

Plan du cours :

1. Utilisation de la classe Event

- Exercice : détecter « Entrée » dans un champ texte

2. Navigation dans le DOM

- Exercice : changer la couleur du parent au clic

3. Modification de la structure du DOM

- Exercice : construire une page web en JavaScript

- Exercice : supprimer le champ de recherche de Google

Dans les chapitres précédents, nous avons :

- utilisé l'API fournie par le navigateur pour accéder aux éléments du DOM d'une page web/HTML ;

- modifié les classes et le rendu d'éléments ;

- défini une réaction aux événements déclenchés par l'utilisateur ;

- et introduit les concepts de Programmation Orientée Objet, et de classes.

Dans ce chapitre, nous allons voir :

- comment en savoir plus sur un événement qui a été déclenché par l'utilisateur ;

- comment naviguer dans le DOM d'une page web/HTML, de nœud en nœud ;

- comment en modifier la structure.

X-A - Utilisation de la classe Event

Reprenons un exemple de page web avec gestion d'événement click sur un élément HTML :

1. <button id="mon-bouton">Mon Beau Bouton</button>

Pour afficher un alert à chaque fois que l'utilisateur cliquera sur ce bouton, nous avons vu qu'il fallait affecter une fonction à la propriété onclick de l'objet JavaScript représentant ce bouton :

1. document.getElementById('mon-bouton').onclick = function() {

2. alert('bonjour !');

3. };

En effet, les propriétés d'événements (onclick, onchange, ou autres) d'un élément permettent d'indiquer au navigateur quelle fonction appeler lorsque l'événement correspondant est déclenché par l'utilisateur.

Ce que nous n'avons pas encore vu, en revanche, c'est que le navigateur passe systématiquement un paramètre lorsqu'il appelle cette fonction : une instance de la classe Event.

Affichons la valeur de ce paramètre event (objet-instance de la classe Event) dans la console, afin d'en découvrir les propriétés :

1. document.getElementById('mon-bouton').onclick = function(event) {

2. console.log(event);

3. };

La propriété la plus couramment utilisée est event.currentTarget. En effet, elle a pour valeur l'élément HTML sur lequel l'événement a été intercepté par notre fonction.

Cette propriété est particulièrement utile dans le cas où nous voulons affecter une même fonction de gestion d'événement sur plusieurs éléments, mais que cette fonction a besoin de savoir sur lequel de ces éléments l'événement a été déclenché.

Par exemple :

1. <button>Mon 1er Bouton</button>

2. <button>Mon 2e Bouton</button>

3. <button>Mon 3e Bouton</button>

1. var boutons = document.getElementsByTagName('button');

2. for (var i = 0; i < boutons.length; i++) {

3. boutons[i].onclick = function(event) {

4. alert('bouton clické : ' + event.currentTarget.innerHTML);

5. // => le contenu du bouton cliqué va être affiché dans l'alert

6. };

7. }

À noter que, selon le type d'événement auquel est associée une fonction, le paramètre event peut être une instance de sous-classes contenant des propriétés et méthodes supplémentaires.

Par exemple :

- le paramètre event d'une fonction liée à l'événement mousemouse (appelée à chaque mouvement de la souris) est une instance de la classe MouseEvent qui possède les propriétés clientX et clientY, afin de connaitre la position de la souris sur l'écran ;

- le paramètre event d'une fonction liée à l'événement keydown (appelée quand l'utilisateur tape un caractère dans un champ) est une instance de la classe KeyboardEvent qui possède les propriétés key et keyCode, permettant de connaitre quelle touche a été tapée par l'utilisateur.

Par ailleurs, la classe Event et ses sous-classes définissent une méthode preventDefault() qui permet d'annuler le traitement habituel d'un événement qui a été intercepté par une fonction. Cette méthode est par exemple utile pour empêcher l'ouverture d'un hyperlien <a>, la soumission d'un formulaire, ou l'ajout de caractères dans un champ.

X-A-1 - Exercice : détecter « Entrée » dans un champ texte

Créer une page HTML simple contenant un champ <input> dont la valeur est effacée quand l'utilisateur presse la touche « Entrée ».

<input id="champ">

var champ = document.getElementById('champ');
champ.onkeydown = function(event) {
if (event.keyCode === 13) {
champ.value = '';
}
};
// il était aussi possible d'utiliser onchange

Solution : jsfiddle

X-B - Navigation dans le DOM

Toute page HTML est structurée sous forme hiérarchique (arbre) : chaque élément HTML a un élément parent, et peut avoir plusieurs éléments enfants.

Reprenons par exemple notre page web ne contenant qu'un bouton :

1. <body>

2. <button id="mon-bouton">Mon Beau Bouton</button>

3. </body>

Dans cette page, l'élément <button> a l'élément <body> comme parent, et un nœud texte (le contenu textuel de l'élément) comme enfant. On peut aussi dire que <button> est un nœud enfant de <body>.

Ici, le seul enfant de notre élément <button> est un nœud textuel, mais il serait possible que ce même élément ait d'autres éléments comme enfants.

Supposons que notre bouton soit référencé par la variable element :

1. var element = document.getElementById('mon-bouton');

Comme tout élément HTML représenté en JavaScript (et donc instance de la classe Element), notre variable element possède trois propriétés utiles pour nous aider à naviguer dans ses nœuds parent et enfants :

- parentNode est le nœud/élément HTML parent de l'élément ;

- children est un tableau d'éléments, enfants directs de l'élément ;

- childNodes est un tableau de nœuds HTML (éléments et/ou nœuds textuels), enfants directs de l'élément.

Dans notre exemple :

- element.parentNode est l'équivalent de document.body (l'élément <body> de notre page) ;

- element.children est un tableau vide, car il n'y a pas d'élément à l'intérieur du bouton ;

- element.childNotes est un tableau qui ne contient que le nœud textuel ayant pour valeur « Mon Beau Bouton ».

X-B-1 - Exercice : Changer la couleur du parent au clic

Soit la page HTML suivante :

1. <div style="padding : 10px;">

2. <button>Mon 1er Bouton</button>

3. </div>

4. <div style="padding : 10px;">

5. <button>Mon 2e Bouton</button>

6. </div>

7. <div style="padding : 10px;">

8. <button>Mon 3e Bouton</button>

9. </div>

Écrire le code JavaScript permettant de colorier le fond du <div> parent en jaune lorsqu'un bouton est cliqué par l'utilisateur.

Pour cela, utiliser une boucle for, une seule fonction onclick, son paramètre event, les propriétés parentNode et style.

Solution : jsfiddle

<div style="padding: 10px;">
<button>Mon 1er Bouton</button>
</div>
<div style="padding: 10px;">
<button>Mon 2ème Bouton</button>
</div>
<div style="padding: 10px;">
<button>Mon 3ème Bouton</button>
</div>

var boutons = document.getElementsByTagName('button');
for (var i = 0; i< boutons.length; i++) {
boutons[i].onclick = function(event) {
var divParent = event.currentTarget.parentNode;
divParent.style.background = 'yellow';
};
}

X-C - Modification de la structure du DOM

À ce stade, nous savons accéder à des éléments du DOM, modifier leur contenu et rendu, et accéder à leurs parent et enfants.

Dans cette partie, nous allons voir comment créer, rattacher et supprimer des nœuds du DOM.

X-C-1 - Créer et rattacher un nœud dans le DOM

Pour ajouter un élément HTML dans le DOM d'une page web, le navigateur fournit les méthodes suivantes :

- document.createElement(nom) pour créer un élément ;

- document.createTextNode(texte) pour créer un nœud textuel ;

- conteneur.appendChild(element) pour ajouter un élément comme enfant d'un autre élément.

Donc, pour ajouter un bouton au <body> d'une page HTML, il faudra exécuter les instructions JavaScript suivantes :

1. // 1. créer l'élément

2. var bouton = document.createElement('button');

3. // 2. créer le contenu du bouton (nœud textuel)

4. var texteDuBouton = document.createTextNode('Mon beau bouton');

5. // 3. ajouter le contenu au bouton

6. bouton.appendChild(texteDuBouton);

7. // 4. ajouter le bouton au body de la page

8. document.body.appendChild(bouton);

X-C-2 - Supprimer un nœud du DOM

Le retrait d'un nœud du DOM est l'opération inverse de celle d'ajout (appendChild()) : elle consiste à appeler la méthode conteneur.removeChild(noeud), où conteneur est le nœud parent duquel on souhaite retirer noeud.

Ainsi, pour supprimer le bouton que nous avons ajouté à la page de l'exemple précédent, il faudra exécuter :

1. document.body.removeChild(bouton);

… ou, de manière plus générale :

1. bouton.parentNode.removeChild(bouton);

X-C-3 - Exercice : construire une page web en JavaScript

En partant d'une page HTML vierge, développer le code JavaScript permettant d'ajouter à la page :

- un champ <input> et un bouton qui vide la valeur du champ quand on clique dessus.

Pour cela, utiliser : createElement(), createTextNode(), appendChild(), document.body, onclick, et value.

Solution : jsfiddle

var champ = document.createElement('input');
var bouton = document.createElement('button');
bouton.appendChild(document.createTextNode('effacer'));
document.body.appendChild(champ);
document.body.appendChild(bouton);
bouton.onclick = function() {
champ.value = '';
};

X-C-4 - Exercice : supprimer le champ de recherche de Google

En utilisant l'inspecteur et la console de Chrome Dev Tools depuis la page google.com, trouver l'instruction JavaScript permettant de supprimer le champ de recherche.

Solution : jsfiddle

<!--
Marche à suivre:
1. Aller sur google.com
2. Faire un clic droit sur le champ de recherche
3. Cliquer sur "inspecter"
4. Dans l'inspecteur repérer un élément parent contenant tout le bloc de recherche et ayant un id
5. Tapez le code JavaScript suivant dans la console:
-->

var champ = document.getElementById('sfdiv');
// remplacer l'id si besoin
champ.parentNode.removeChild(champ);

XI - TP 11 : Récupération de données avec AJAX

Objectifs :

- Interroger une API du Web en JavaScript (ex. : météo)

- Effectuer une requête GET avec XMLHttpRequest

XI-A - Effectuer une requête GET avec XMLHttpRequest

XI-A-1 - Introduction

Créé en 1995, le procédé AJAX (Asynchronous Javascript and XML) a été intégré aux navigateurs pour permettre aux sites Web d'être plus dynamiques.

Cette technique permet à une application JavaScript de :

- échanger des données avec des serveurs Web ;

- charger des données sur une page sans la recharger ;

- accéder à des API (Application Programming Interface) du Web.

Une requête AJAX consiste à effectuer une requête HTTP depuis du code JavaScript.

Pour rappel, HTTP est le protocole employé par les navigateurs pour communiquer avec les serveurs Web.

Par exemple, quand nous allons sur Google, nous tapons http://google.com dans la barre du navigateur, ce qui a pour effet de demander la page HTML de recherche au serveur HTTP de google.com. Ceci est une requête de type GET, car elle permet seulement de récupérer des données depuis ce serveur, contrairement aux requêtes POST permettant d'envoyer des données à un serveur.

À noter que le chargement d'une page HTML déclenche généralement d'autres requêtes HTTP, afin de récupérer toutes les ressources associées à cette page Web : scripts, feuilles de style CSS, images, etc.

XI-A-2 - Formats de données

Une requête HTTP permet de récupérer des données de formats variés.

Par exemple :

- le format HTML, pour les pages Web ;

- le format CSS, pour les feuilles de style ;

- le format JS, pour les scripts en JavaScript ;

- le format JPG, pour les images, etc.

Nous allons nous intéresser en particulier aux formats représentant des données structurées.

Initialement, AJAX a été créé pour récupérer des données au format XML. Un format proche du HTML, et très en vogue à l'époque.

Exemple de document XML :

1. <inbox>

2. <email from="amazon">votre colis a été envoyé</email>

3. </inbox>

Comme en HTML, un document XML est composé d'éléments, d'attributs, et de nœuds textuels.

Désormais, la majorité des sites Web emploient le format JSON pour échanger des données structurées.

Exemple de document JSON :

1. [

2. {

3. "from" : "amazon",

4. "subject" : "votre colis a été envoyé"

5. }

6. ]

Pour rappel, JSON (JavaScript Object Notation) est en fait la syntaxe utilisée pour définir des objets en JavaScript.

En supposant que cet objet JSON soit stocké dans une variable objet, le langage JavaScript nous permet d'accéder facilement aux données qui y sont stockées.

Par exemple, pour afficher la valeur de la propriété subject de la propriété email, il suffit de saisir :

1. console.log(objet.email.subject);

XI-A-3 - Émettre une requête HTTP GET avec AJAX

Pour récupérer des données depuis une URL HTTP, le navigateur met à notre disposition la classe XMLHttpRequest. Malgré son nom, il est possible de l'utiliser pour récupérer des données exprimées dans d'autres formats que XML.

Voici un exemple de requête HTTP GET simple, en JavaScript :

1. var xhr = new XMLHttpRequest();

2. xhr.open('GET', 'https://jsonplaceholder.typicode.com/users/1');

3. xhr.onreadystatechange = function() {

4. if (xhr.readyState === 4) {

5. alert(xhr.responseText);

6. }

7. };

8. xhr.send();

Cette requête permet de récupérer le texte d'un document disponible à l'URL https://jsonplaceholder.typicode.com/users/1, puis de l'afficher dans un alert() une fois qu'il a été téléchargé intégralement.

Effectuer une requête avec XMLHttpRequest consiste en quatre étapes :

1. Instancier la classe, avec new ;

2. Spécifier la méthode (GET) et l'URL du document à récupérer, en appelant la méthode open() ;

3. Définir ce qu'on souhaite faire de la réponse, une fois reçue intégralement, en affectant une fonction à la propriété onreadystatechange de notre instance ;

4. Puis envoyer la requête, en appelant la méthode send() de notre instance.

La propriété readyState permet de savoir où en est le téléchargement du document. Lorsque celui-ci est complet, readyState vaut 4, et la fonction affectée à onreadystatechange est appelée à nouveau par le navigateur. À ce moment-là, nous pouvons accéder au contenu du document téléchargé, stocké dans la propriété responseText.

À noter que, même si le document en question est au format JSON (par exemple), responseText contiendra la version sérialisée du document, c'est-à-dire qu'il sera de type string, et non object. À ce stade, il n'est donc pas possible d'accéder à une propriété spécifique de cet objet.

Conseil  : avant d’effectuer une requête AJAX GET, coller l’URL de destination dans un onglet de votre navigateur, il vous affichera la réponse à cette requête (c.-à-d. la valeur de responseText).

Dans la prochaine partie, nous allons voir comment convertir cette chaine de caractères en véritable objet.

XI-A-4 - Récupérer un document JSON avec AJAX

Dans le cas où notre requête retourne un document JSON, il est possible de convertir sa représentation textuelle (fournie dans la propriété responseText) en véritable objet JavaScript.

Pour cela, le navigateur fournit la fonction JSON.parse().

Cette fonction prend en paramètre une chaine de caractères (type : string) contenant la version sérialisée d'un objet, et retourne l'objet correspondant (type : object).

Par exemple, imaginons l'objet sérialisé suivant :

1. var chaine = '{"message" :"bonjour!"}';

2. console.log(chaine.message); // => undefined

Comme cet objet est sérialisé sous forme de chaine de caractères, il n'est pas possible d'accéder directement à ses propriétés.

Pour permettre cela, nous allons le passer en paramètre de la fonction JSON.parse() :

1. var chaine = '{"message" :"bonjour!"}';

2. var objet = JSON.parse(chaine);

3. console.log(objet.message); // => "bonjour!"

Donc, pour récupérer un objet JavaScript depuis la réponse JSON de notre requête AJAX, il faut utiliser le code suivant :

1. var xhr = new XMLHttpRequest();

2. xhr.open('GET', 'https://jsonplaceholder.typicode.com/users/1');

3. xhr.onreadystatechange = function() {

4. if (xhr.readyState === 4) {

5. var reponse = JSON.parse(xhr.responseText);

6. alert(reponse.name);

7. }

8. };

9. xhr.send();

Bien entendu, n'oubliez pas de remplacer l'URL de cet exemple par celle à laquelle se trouve le document que vous souhaitez récupérer !

N'oubliez pas non plus de consulter la console de votre navigateur ainsi que la partie « Réseau » (ou « Network », en anglais) de Chrome Dev Tools, afin de diagnostiquer les éventuelles erreurs.

XI-A-5 - Exemples d'API interrogeables via AJAX

API simples :

- Adresse IP : httpbin.org/ip

- Utilisateur : jsonplaceholder.typicode.com/users/1

API plus complexes :

- Animations : Giphy

- Photos : Flickr

- Chatons : puppygifs.tumblr.com/api/read/json (JSON var with CORS)

Annuaire d'API : Programmable Web

Note : Certaines API nécessitent l'utilisation d'une clé API. Donc :

- lisez la documentation de l'API ;

- créez un compte développeur sur le site de l'API, si besoin ;

- lisez les parties suivantes pour intégrer l'éventuelle clé API dans l'URL de votre requête AJAX.

XI-A-6 - Exercice 1 : Récupérer l'adresse IP de l'utilisateur

Effectuer une requête AJAX vers l'API httpbin.org/ip qui affichera seulement l'heure fournie en réponse dans un alert().

Solution :

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://httpbin.org/ip');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
console.log('réponse du serveur:', xhr.responseText);
var objet = JSON.parse(xhr.responseText);
alert(objet.origin);
}
};
xhr.send();

XI-A-7 - Exercice 2 : Météo de la ville de l'utilisateur

Réaliser une page Web permettant d'afficher la météo d'une ville saisie par l'utilisateur. Pour récupérer la météo, utiliser une requête AJAX vers l'API de openweathermap.

Étapes :

1. Effectuer une requête vers l’API fournie, après en avoir consulté la documentation ;

2. Réaliser une page HTML contenant un champ de saisie et un bouton ;

3. Ajouter un code JavaScript qui affiche le contenu du champ quand on clique sur le bouton ;

4. Intégrer la requête AJAX correspondant à la première étape de cet exercice, de manière à ce que la réponse de requête soit affichée dans la console ;

5. Faire en sorte que l'appel AJAX s'adapte en fonction de la saisie, à chaque clic sur le bouton.

Solution :

<html>
<head>
</head>
<body>
<h1>Météo</h1>
<input id="ville" placeholder="ville">
<button id="bouton">OK</button>
<div id="meteo"></div>
<script src="ajax-get-weather.js"></script>
</body>
</html>

Notes :

Pour accéder à cette API, vous aurez besoin de vous inscrire et de demander une clé API (API Key, en anglais).

Pour pouvoir exécuter votre requête AJAX vers cette API, vous aurez peut-être besoin de changer son URL de manière à utiliser le protocole HTTPS au lieu du protocole HTTP (cf. explication plus bas).

XI-A-8 - Exercice 3 : Afficher les images demandées

Réaliser une page Web permettant d'afficher des images ou animations en rapport avec les mots-clés saisis par l'utilisateur. Pour récupérer l'URL des images, utiliser une requête AJAX vers l'API de Flickr ou de Giphy.

Les images doivent être affichées directement dans la page à l'aide d'éléments <img>, après la saisie de l'utilisateur. Pour cela, vous pouvez utiliser innerHTML, ou les méthodes présentées dans le chapitre sur la manipulation avancée du DOM.

Solution : flickr

<h1>Recherche d'images</h1>
<input id="tags">
<button id="bouton">OK</button>
<ul id="resultats"></ul>

var resultats = document.getElementById('resultats');
document.getElementById('bouton').onclick = function() {
var tags = document.getElementById('tags').value;
var url = 'https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=74a9b070d21072ccac3a7b5f44f09efa&tags=' + encodeURIComponent(tags) + '&format=json&nojsoncallback=1';
console.log('ajax request url:', url);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var reponse = JSON.parse(xhr.responseText);
var photos = reponse.photos.photo;
resultats.innerHTML = ''; // effacer les résultats précédents
for (var i = 0; i < photos.length; i++) {
var photo = photos[i];
// cf https://www.flickr.com/services/api/misc.urls.html
var photoUrl = 'https://farm' + photo.farm + '.staticflickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '.jpg';
var img = document.createElement('img');
img.src = photoUrl;
resultats.appendChild(img);
}
}
};
xhr.send();
};

Solution : giphy

<h1>Recherche de GIF</h1>
<input id="tags">
<button id="bouton">OK</button>
<ul id="resultats"></ul>

var resultats = document.getElementById('resultats');
document.getElementById('bouton').onclick = function() {
var tags = document.getElementById('tags').value;
var url = 'https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=' + encodeURIComponent(tags);
console.log('ajax request url:', url);
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var reponse = JSON.parse(xhr.responseText);
var photos = reponse.data;
console.log(photos);
resultats.innerHTML = ''; // effacer les résultats précédents
for (var i = 0; i < photos.length; i++) {
var gifUrl = photos[i].images.downsized.url;
console.log(gifUrl);
var img = document.createElement('img');
img.src = gifUrl;
resultats.appendChild(img);
}
}
};
xhr.send();
};

ajax-get-images-flickr.html
ajax-get-images-giphy.html

XI-A-9 - Restrictions de protocole HTTP / HTTPS

Comme vous le savez probablement, le protocole HTTP existe aussi en version sécurisée (HTTPS).

Pour des raisons de sécurité, les navigateurs empêchent le protocole HTTP d'être employé depuis une page Web affichée via HTTPS. Cette contrainte a des implications importantes sur le fonctionnement des requêtes AJAX.

Notamment :

- une page ouverte en HTTPS (ex. : jsfiddle) ne peut pas effectuer de requête AJAX vers des URL HTTP (non sécurisées), seulement vers des URL HTTPS ;

- si vous exécutez une requête AJAX depuis la console d'une page HTTPS, les mêmes contraintes s'appliquent.

Donc, dans la mesure du possible, assurez-vous d'utiliser le protocole HTTPS dans l'URL de vos requêtes AJAX.

XI-A-10 - Restrictions de domaines

En plus des restrictions de sécurité HTTPS, certaines API ne permettent pas l'exécution de requêtes AJAX depuis certains domaines (ex. : jsfiddle).

D'autres refusent carrément de répondre aux requêtes émises depuis d'autres domaines que le leur.

Dans ce cas, vous pourrez seulement exécuter des requêtes AJAX depuis une page de leur site Web, via la console.

XI-A-11 - Manipuler les paramètres d'une URL HTTP

Dans les exemples d'API « complexes » fournis plus haut, vous remarquerez que certaines URL contiennent des paramètres, séparés par les caractères ? puis &.

Exemple :

1. https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=74a9b070d21072ccac3a7b5f44f09efa&tags=soccer&format=json&nojsoncallback=1

Comme dans la définition de propriétés d'un objet JavaScript, chaque paramètre est composé d'un nom (aussi appelé clé) et d'une valeur. Dans une URL, le nom et la valeur de chaque paramètre sont séparés par un =.

Dans l'URL fournie ci-dessus en exemple, quatre paramètres sont transmis :

- le paramètre nommé method vaut flickr.photos.search ;

- le paramètre nommé api_key vaut 74a9b070d21072ccac3a7b5f44f09efa ;

- le paramètre nommé tags vaut soccer ;

- le paramètre nommé format vaut json ;

- et le paramètre nommé nojsoncallback vaut 1.

Au cas où vous voudriez passer la valeur d'une variable de votre programme JavaScript en paramètre de votre URL, vous pouvez utiliser la concaténation.

Exemple :

1. var tags = 'soccer';

2. var url = 'https://api.com/?tags=' + tags + '&format=json';

Attention : ceci fonctionne bien quand la valeur de votre variable ne contient aucun caractère spécial. Par contre que se passerait-il si elle contenait un espace, ou un & ? L'URL deviendrait alors invalide…

Pour éviter que les caractères spéciaux éventuels d'une chaine de caractères ne puissent invalider votre URL, utilisez la fonction encodeURIComponent() de la manière suivante :

1. var tags = 'soccer';

2. var url = 'https://api.com/?tags=' + encodeURIComponent(tags) + '&format=json';

XI-A-12 - Identification du développeur et/ou de l'application

Vous aurez remarqué que certaines URL contiennent un paramètre api_key, ou appid.

Exemple :

1. http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=44db6a862fba0b067b1930da0d769e98

Ce genre de paramètre permet d'identifier l'application utilisant l'API, ou le développeur de cette application. En effet, les fournisseurs d'API souhaitent connaitre cette information, afin de contrôler l'accès et l'usage de leur API.

Pour obtenir une clé, rendez-vous sur le site Web de cette API, puis intégrez la clé fournie dans l'URL de votre/vos requête(s) AJAX.

XII - TP 12 : Envoi de données avec AJAX

Objectifs :

- Envoyer des informations (ex. : un tweet) à une API du Web

- Effectuer une requête POST avec XMLHttpRequest

XII-A - Effectuer une requête POST avec XMLHttpRequest

Dans la partie précédente, nous avons utilisé la classe XMLHttpRequest pour envoyer des requêtes de type HTTP GET. Pour rappel, ce type de requête permet de récupérer des informations depuis un serveur.

Dans cette partie, nous allons voir comment utiliser cette même classe pour envoyer des requêtes de type HTTP POST. Même s'il permet aussi de recevoir une réponse du serveur à ces requêtes, ce type de requête permet d'envoyer des informations.

XII-A-1 - Envoi d'une chaine de caractères

Voici un exemple de requête POST effectuée en JavaScript/AJAX :

1. var xhr = new XMLHttpRequest();

2. xhr.open('POST', 'https://httpbin.org/post');

3. xhr.onreadystatechange = function() {

4. if (xhr.readyState === 4) {

5. alert(xhr.responseText);

6. }

7. };

8. // envoi d'une chaine de caractères :

9. xhr.send('ceci est un exemple de données envoyées');

Le principe de fonctionnement est exactement le même que celui d'une requête GET, sauf que nous avons cette fois-ci envoyé une chaine de caractères via notre requête à l'adresse serveur https://httpbin.org/post.

Pour cela, nous avons :

- remplacé le paramètre GET par POST, dans l'appel à la méthode open() ;

- passé une chaine de caractères en paramètre de l'appel à la méthode send().

XII-A-2 - Envoi d'un objet JavaScript / JSON

Pour envoyer un objet JavaScript / JSON dans une requête POST, il faut d'abord sérialiser l'objet (c'est-à-dire : le convertir) sous forme de chaine de caractères, à l'aide de la fonction JSON.stringify().

Il suffit donc de modifier le paramètre passé à la méthode send(), tel que dans l'exemple suivant :

1. // ... ou envoi d'un objet JSON :

2. xhr.send(JSON.stringify({ message : 'bonjour!' }));

En effet, JSON.stringify() est la fonction inverse de JSON.parse() :

- alors que JSON.parse() permet de convertir une chaine de caractères en objet ;

- JSON.stringify() permet de convertir un objet en chaine de caractères.

Attention : veillez à ne pas appeler la méthode send() d'une même instance (ex. : xhr, dans notre exemple) plus d'une seule fois !

XII-A-3 - Conseils pratiques pour diagnostiquer le fonctionnement de vos requêtes

- Comme d'habitude, n'oubliez pas de consulter la console de votre navigateur pour vérifier la présence éventuelle d'erreurs dans votre code et/ou dans vos requêtes AJAX (ex. : accès non autorisé à une API).

- Utilisez l'onglet « Réseau » (ou Network, en anglais) de Chrome Dev Tools pour suivre l'exécution de vos requêtes, et consulter à la fois leur contenu et celui de la réponse du serveur.

- Utilisez l'API https://httpbin.org/post pour tester le bon fonctionnement de vos requêtes POST. Cette API vous envoie en réponse le contenu que le serveur a reçu suite à votre requête.

XII-A-4 - Exercice 4 : Tweeter en AJAX

Un serveur est mis à votre disposition à l'URL https://js-ajax-twitter.herokuapp.com. Comme Twitter, l'application Web exécutée sur ce serveur permet aux utilisateurs de publier des messages publics en temps réel.

Le but de cet exercice est de publier vos messages en utilisant une requête AJAX.

Sont fournis :

- une page web affichant en temps réel le flux des derniers messages publiés sur le serveur ;

- une API HTTP permettant de publier des messages sur le serveur, accessible depuis l'adresse /tweet ;

- et un client simple permettant de publier des messages à l'aide d'un formulaire HTML.

XII-A-4-a - Documentation de l'API fournie

Pour publier un message sur ce serveur, il faut envoyer une requête HTTP POST à l'adresse /tweet (accessible depuis la racine du serveur), en transmettant un objet JSON.

L'objet JSON à envoyer comme contenu de la requête doit contenir deux propriétés :

- message : le texte à publier (type : string) ;

- token : le jeton fourni après identification de l'utilisateur (type : string).

Note : La valeur de la propriété token de l'objet à envoyer est générée automatiquement par le client simple fourni, dans la variable globale window.token.

À chaque requête HTTP valide reçue, le serveur répondra par un objet JSON ayant :

- soit une propriété error contenant un message d'erreur (type : string) ;

- soit une propriété OK, dans le cas où le message a été publié sans erreur.

Remarque importante : le bouton d'identification de Google n'a été activé que depuis les domaines jsbin.com et js-ajax-twitter.herokuapp.com. Vous ne pourrez donc pas exécuter le client simple fourni (ni une version dérivée) depuis un autre domaine ni depuis le système de fichiers de votre machine (protocole file://).

XII-A-4-b - Étapes proposées

1. Tester le client fourni : https://js-ajax-twitter.herokuapp.com/client.html (après login) ;

2. Cloner le client fourni : https://jsbin.com/bucilir/edit?html,js,output ;

3. Remplacer le formulaire par l'envoi d'une requête HTTP POST à chaque fois que l'utilisateur pressera ENTRÉE dans le champ de saisie ;

4. Dans le cas où la publication du message a fonctionné sans erreur, effacer le champ de saisie, afin de permettre la saisie immédiate d'un nouveau message ;

5. Dans le cas contraire, afficher une description de l'erreur dans un alert().

Conseil : pensez à utiliser la console JavaScript et l'onglet « Réseau » de Chrome Dev Tools pour diagnostiquer.

XII-A-4-c - Bonus : améliorations

Quand vous aurez terminé toutes les étapes proposées ci-dessus, vous pourrez apporter les améliorations suivantes à votre client :

- intégrer le flux de tous les messages sur votre page ;

- améliorer le design et l'expérience utilisateur de votre client.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<!-- initialize google login api, for user authentication -->
<meta name="google-signin-client_id" content="247219641427-ifeq88p7rgor9al5ksduds7ug0ba7djr.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>
</head>
<body>
<p>Connexion:
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<a href="#" onclick="gapi.auth2.getAuthInstance().signOut(); return false;">Sign out</a>
</p>
<p>Twittez:</p>
<!-- Solution de l'exercice: -->
<input id="message" placeholder="Votre message">
<button id="bouton">Envoyer</button>
<!-- fin de la solution -->
</body>
</html>

// --- code fourni ---
function onSignIn(googleUser) {
window.token = googleUser.getAuthResponse().id_token;
(document.getElementById('tokenInput') || {}).value = token;
}
// --- fin du code fourni ---
// Solution de l'exercice:
var message = document.getElementById('message');
function sendMessage() {
// pas la peine d'envoyer le message s'il est vide
if (message.value.length === 0) {
return;
}
// initialisation de la requête AJAX POST au serveur
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://js-ajax-twitter.herokuapp.com/tweet');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
console.log('Réponse finale du serveur:', xhr.responseText);
var responseObject = JSON.parse(xhr.responseText);
if (responseObject.error) {
alert('Erreur: ' + responseObject.error);
} else {
console.log('Message envoyé avec succès :-)');
message.value = '';
}
}
};
// envoi de la requête avec les propriétés message et token (comme spécifié dans la documentation de l'API)
xhr.send(JSON.stringify({
message: message.value,
token: window.token, // fourni dans le code HTML
}));
}
// l'évènement "click" est émis par le bouton quand l'utilisateur clique dessus
document.getElementById('bouton').onclick = function() {
console.log('L\'utilisateur a cliqué sur le bouton');
sendMessage();
};
// l'évènement "change" est émis par l'input quand l'utilisateur valide
document.getElementById('message').onchange = function() {
console.log('L\'utilisateur a pressé ENTRÉE');
sendMessage();
};

Solution : jsbin

ajax-post-twitter.html

XIII - TP 13 : Composants Web

Objectifs :

- Développer un composant web simple : Accordéon / Carousel / Galerie vidéo

XIII-A - Création d'un composant Web simple

Sur le Web, un composant est un programme qui permet de fournir une interface intégrable, conçue pour un usage précis, mais personnalisable par chaque développeur.

Par exemple, si je développe et diffuse un composant de galerie d'images, d'autres développeurs pourront intégrer simplement mon composant sur leur site, et y ajouter les images de leur choix.

Les composants permettent donc d'enrichir le contenu, l'esthétique, et/ou les interactions proposés par un site Web, en réutilisant du code qui n'a pas été écrit spécifiquement pour ce site.

Il existe de très nombreux composants publiés sur Internet et utilisables gratuitement. Pour la plupart, ils sont basés sur la bibliothèque « jQuery » (dont nous parlerons plus tard dans ce cours). Mais nombreux ont été conçus en JavaScript/DOM natif (aussi appelé « Vanilla JavaScript »), et peuvent donc fonctionner sans jQuery.

Note : dans le cadre de ce cours, l'usage (direct ou pas) de jQuery ne sera pas accepté.

Voici quelques exemples de composants natifs intégrables librement :

- Sweet Alert

- Dialog Modal

- Animate on scroll

- Tranglify

Vous pourrez trouver d'autres composants natifs sur le site Vanilla List.

À ce stade, nous allons apprendre à réaliser un composant simple, dans le sens où celui-ci ne sera pas suffisamment modulaire pour être intégré plusieurs fois sur une même page. Nous verrons plus tard comment faire cela.

XIII-A-1 - Généricité et instructions d'intégration

Afin que notre composant puisse être intégré de manière personnalisée par chaque développeur, quels que soient le site Web en question et son contenu, il est important de définir quelques règles et abstractions.

D'abord, un composant doit être simple à intégrer, et un utilisateur de composant ne devrait jamais avoir à consulter ni modifier le code source du composant. Il va falloir donc que le composant soit suffisamment générique et configurable depuis le site Web qui l'intégrera.

Par exemple, un composant de galerie d'images doit s'adapter à une liste d'images fournie par un développeur désirant l'intégrer, et l'intégrateur ne devrait en aucun cas avoir à modifier la liste d'images dans le code source du composant.

Pour cela, un composant :

- ne doit pas contenir de valeurs et références littérales ; (ex. : nombre d'images et/ou URL des images stockés « en dur » dans le code du composant) ;

- doit définir et documenter les règles et contraintes éventuelles que devront respecter les intégrateurs du composant.

Exemples d'instructions d'intégration fournies par la documentation d'un composant :

- toutes les images à afficher dans la galerie doivent être des balises <img> portant la classe carousel-img ;

- ou encore : appeler la fonction creerGallerie() (définie par le composant) en passant en paramètres l'identifiant du DIV devant contenir la galerie, et un tableau d'URL d'image.

Prenez le temps d'analyser les instructions d'intégration des composants listés plus haut.

XIII-A-2 - Exercice 1 : développer et documenter un composant simple

Pour mettre en pratique cette partie du chapitre, développer un des trois composants suivants, et fournir la page de documentation associée.

La page de documentation doit contenir :

- une description concise du composant : à quoi il sert, quelles sont ses fonctionnalités et avantages éventuels ;

- une démonstration du composant, pour le tester depuis la page ;

- les instructions permettant d'intégrer simplement ce composant à son site ;

- la description des fonctions et/ou paramètres éventuellement fournis par le composant ;

- BONUS : la liste des navigateurs (et leur version) sur lesquels le composant fonctionne ;

- BONUS : le composant et sa documentation publiés sur GitHub.

S'inspirer de la documentation des composants fournis plus haut en exemple.

Conseil pratique : pour afficher du code source sur une page HTML sans que celui-ci ne soit interprété par le navigateur, utiliser la balise <xmp>.

XIII-A-2-a - Composant : Carousel

Un « carousel » est un composant de galerie d'images permettant à l'utilisateur de passer d'une image à l'autre, et éventuellement d'agrandir une image en plein écran.

Certains carousels passent automatiquement d'une image à l'autre, à l'aide d'un minuteur (setTimeout() ou setInterval()).

Pour développer un carousel, il faut maîtriser :

- les sélecteurs DOM (getElementById(), getElementsByClassName() et/ou getElementsByTagName()) ;

- la manipulation de styles et/ou classes CSS en JavaScript ;

- la capture d'événements onclick ;

- et l'opérateur modulo (%).

Solution : Jsfiddle

<button id="next">next</button>
<div id="carousel">
<img class="carousel-img" src="https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcTeiCMhoMrQyPFOsY9cX1tUZm39RAVvWGI3KTt5j3MhYAbYtmqm">
<img class="carousel-img" src="data:image/jpeg;base64,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">
<img class="carousel-img" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxISEhUSEBMVFhUVFRUVFRUVFRYVFRUVFRUXFxUVFhUYHSggGBolHRUVITEhJSkrLi4uFyAzODMtNygtLisBCgoKDg0OGhAQGislHR0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIALYBFQMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAEBQMGAAIHAQj/xAA6EAABAwMDAgQEAwcEAwEBAAABAAIRAwQhBRIxQVEGImFxEzKBkaGx0RQjQlJywfAHU2KSM4LhFhX/xAAYAQADAQEAAAAAAAAAAAAAAAABAgMABP/EACMRAAICAgICAgMBAAAAAAAAAAABAhEhMQMSE0FRYQQygSL/2gAMAwEAAhEDEQA/AOy70s1GrgosvSbVq2CiKyh+Ka8u+qXW1fAyvPEFWXoGjUXRDRzyLn4b1MMdBK6DZag0gQVxRtQoy31Gq35XuH1WlGwxlR29lcHqt94XGGa5cf7rvupm+Irn/ed+H6JPEP5fo7DvCzeFx7/9Dc/7zvwW3/6S5/3nfh+i3hYfI/g67UrAAmeAT9lyev4qun1C9tVzRJ2tB8oHQR1RWg6xWqVtr3ucHNcOJ5B7KuNpGPUcqXIujo6OD/VtnQvDPjL4kMuYDiYD+AT2d291cgVwksIz/eCrbpPiauaXw2H94wSAQCHsAyAT/EPxHsli7dB5IVlHSl4SuYO8bXP8zf8AqELW8X3R/jH/AFCt4mc/k+jq/wAQLPiBcgb4quhn4n3AUx8Z3X8zf+qz4jeT6Op171rBJVdvfHNGm7aGudHYhc7vtfuKvzvMdhhKzUKSUH6HjJezq9Lx1Qd/C4fb9VHdeLWkeRcrFUhTUa9RxDWyScADJJ9AjGL9gk4+i06lrJdJJSCjczUmeq8uqbKWLh5NT/apkFw/reZa32En2Qbtba3LbamAB1c8u9y6Yn1hZ8sVgC4ZSydF0WtgK0W9TCo/hy7bVptqswCSCD0I6eyttrUwkuwpVgYl6X39XBUrqiU6nWwVrGqyuXVfzH3Q7q6AvrqHFBm9XL2OhQGxrrR1dKTerR14Fuxug0dXUTq6WOvPVROuwjYOoydXWJSbpYtZup2t78Kv63VwU5qOwq3rTsFdFnMyg6qZcVDSamF1bSVJRs1eMkSaBGNUrWplTslM2xTd0L1FQatm0ieid0dO9EfR070TKSDVFZFq7ss/ZXdirnT04dlMNOHZNYMlX0BrmV2EjE5UNamBWqNJiKjvqCSQrkzTgDMKqeMqYpXAc7h7Qcdxg/kFzfkemdX4zdtENayOdpB9yZS7fUpua9oLXNIIzOR2TW1u6LuH7TwQ44PqD0QWpM25Dp6zAP3XOjpYdc2orsFxQAziqwcsf1MdAc+yWm1d1BQWn62+hWbEeYkOAwCDyCPYSCj6XiF/xHB4D24DXQRO6I+sGcrohzVhnLPhvKIDQd2K0NE9k3uNbptY55GA7biCZ9B9VE3XbciZAHqIV+0X7I9JfArNI9lr8Mp3WvKW3diNpdP/ABHJiMoOjfUXmGuBjLjEQO6SU4r2FccmA0rdziGtEk8BEVdRZRBpWzpqERUricd2Uccf8uvRD6nroeTQtmljZLaj+XuiZE9Bxj1W+nWrG+Z0yekfoufk5O2EdHHxVlglvaEyY5PqZK3ezkZ/sjbm8a3hmOpcSOO8Rj6IS3uP2iGMBBc7YB3JMT+a55YOlMtnhsbLek3+aahz/McD7D8Vb7R+FWGfPtHysDWNHYNEKw2bsKyxg43lthznJJq78FNXuwkmrOwUJPA0dnO9auiHFJ6l+Udr/wAxVauasLmStnZhIZf/ANErU6iUhddLU3SfoxOyHp1BaG/SM3S0N0j0YOyHv7esSH9qWLdWbsj6jqHCQak2U6ecJZcslUkzloQfsklFUbNHsooqnRQ7MPUBZaKZtomDKSlbTW7M1AtK1RlK2UrGqdqvFiNGjKK3FJSNcF7uVLBRp8NUT/UrL6DQ2ZbUJPaCyPzKv25c+/1UrbRTI5hzfvB/sk5f1KcX7opzaoB2uBP9MfkmNrTpkOBcWxnacY6pfoxaBuc07/UYj9Edf/vIdUDQRhpb82QfKTx6fVclnWxdfXDHEFjWgsgEmTLZ8pH0MH6KC4vSxtQtZLi0be3HaOwHPMjsjaDfMQ1g67twiGmR9ABg+oQmo3FQB3w4lgJgjozAyOp2zPGVREhDYXFQ03tqAx03TJJ4aPrJlTPqNO5oxLoH/qC4/QkR9UZUqNrWlGqT+8DqjuTJcG5mOkn8kLolkH0/jPMNpzUdnJJHy+uBPrhU7AoFttUq1LgxJbt+HzgNA5CeW+mudScxlQS+J/mjlvugdNtqT3GrSBh7A6Bh2HEEQekxkd493VpQ3eai5sA4LeBI6dm5/JJJhRFp1o6iJeNxnPucn3PKs2mGnUB8kEdztn2IQLmOfRawg7gXTEy4A9xwCfyQt5qD6G3Ac2YIjd9iRE++DKTtY1DXUbCk5pJp+UZw8H8I/VeeGbGXPrBpDW+Vk9zgn7fml72trHczaOpDTE9Zjp/T07lXHQG7ramdu2RMfXn7JZMzwje0ownltwgqdJHURhaMiDRvUKSaq7BTisUi1R2CmkxoLJzvX/mKqd91Vr1z5iqtehThs6JaEVUmVHJRNRmVpsXUc1kQle7SpQxehqBrINpXiJ2LFjWfULnYQVUqd78IN7lGTFRKxEUyhGORFNyVMYKaVJuQ7XL19RGzEpqqJ91CBr3MJTeaiB1VYSFaH5v/AFWjtS9VS6mtCeVC7Vp4K6ETaLsdW9VTP9U79r6VOkP/ACOdj0nEyi9Iu5cS6DtG77Kq6hdG4qmtVI7MaBgR/ZT5ZpIpxQd2b6cwsZsc8GGw4nd15gxzhaXVxJa9pY4TByG7h8pDmgzyBmI7oQX8bmOHlOXDgmOeOnsg7jxTUZBp0G7f+Uif/Vpj6qEEdEh7Y0TUo1XAkPZvBBONjvNif7nr7JZ/p+/4taoXZhjQBPYuiPXyj6AInQvF1Cpup12fBc+Yc0ywk/wkYgfqjvClg21+IWwd7ztMR5ckf56oylVoEY2Vl1I0XXFEnFN73s9N7TB+5AWjadSr8KypSC9rX1COQ0jbHtAb9wn/AIgtGhz3SDvDA89CN4Jjt1/JSeErQU3uecueIk9A0bRz3mfoh3SVh6iy9e23q/BEDawB0AE5a47e/Yx3CONHa9rm+WQ07SdodnAJmI+X3LTzlLXWFS41Co44YwyXZjjgE8nlMNa31KgFNpY1vkhgl5AMGXY2z65PbqTa0LTGlletLjUmXMGYPlPfPUYPTr6p0KdOqzaQA6CTj6/dVjS7UghvwyCASGjaTBnEBog5Oev4I2huYXF5IkENkHGeJk55UpKh0xPZODbja2MujaDJAJjJXXLWiGMa0QIaBj2XG9KLaVdxrNL5dJPJHsfsuwWNdr2NczgjErSVCSlYUAp2IYFSsKERGeVykWqOwU5uHJBqbsFOxobKHrZyVWroKx6yclIa7UIbLS0KX01oaaNdTWpprpOagTYvNiJdTUZCxqItq8W5WIGPoyo9Buet6r0I6ouaTMgtjlOxyAY9EMelCGtctKtTCjD1FXesFC3UqmCqNrlbnKteqVeVRtZqSVTjDQrLj3K2k4yfuofiI7TWCo5rSYMiJ4PoVcxaNMc6nZVH7HebG7qAcEqvWtqbh5bLg1scD/7yrV4v8Q/BoigGeXaOB+aqui3m9u5uSHZHGD6/3UORspBDqvojWUqjwS8tbJ6kjr6qv+Ma3wblgptZsYBslssc0jqDyCCP7Kx6ZrLqbyKg3MOCIl0dJHPE5hQalYUKpDadWk+mOGVDDqX/ABBGdozAPHskg6dmmrqhZ4YoNrW1wKrKe01Gua4tl4gP3NY7+XzNweyZ+HLV/wCy1mvcd1KQ3rwJ+vJ+yKOn020QBcNaAYa2m2GzzBJBkfZK9N1MbmsZUAg/vHAggiDAHPvP+FrcrbBFUwuhZVKtMHhwh31BkDjtKL8NUHOqVS/DWeYNIIkZmP8AOqYW91TnyOPu4Db9h7qG71XZU+GWNbIJLhGYkeUz2Jx9kNj6K1X1W6rGq21aGspucHvIHSR7TH4QkVq+8dWG2vucab6o84ALGsdUfG4RMNdiJJEequtiwNbWYWkU6oxUA8oeP5iOA4H5iIwJ5VFt9Aq/FNN7QGCdzyRt28bhB5jhUjJJtEpN+y36XqteRSuRkAEPpywkZw4cSDz0Prgpg+43uO3HBJkbuxyJ64z3R3hm0FwalQt/ctZsBP8AER1E9u6U6vple13VqJFWnwWvEPaOfKRg8dQlbsKTrIJrFoabyQIkyPZX3wlWLrZhJnp7eio97ftq0qTgRLpxPHp6Kx+BqjoqNPAgj68pfVGki3blK0qA49/yXrXJUTPLhyQam/BTm5eq/qTsFM2PAperuyUlqlNtYdkpFWchHZZ6NScrChzUXhqq9kaJnqFy1NVRuqI2Y2KxQl6xGwUd7rVEJ8RZXqIUVFzSFQfTeiqbktpvRVN6UNBzXKK5cvGOUdw7CwyEWqFUvVhyrlqblUNSEqsDMTspprotsTVZ/UFBQpJ1obCKgIk5Ex091SxWR+OdRaa20yAwbT9ki0bU2NdFMHzYIj8YCc+ObVvxy5z2MBAn5nOdjs0HPukr9LpEZqvY3qxtPJ/q8+6f6gFNxTbssnjBYoEgvpkEjptyOkhzgl76G95AcCAcjd5hj/jIafSVJbanQYz4Y+OQREnYPscwn3hulTPytc0EET8Km+oSeS0in9+VNPqNVi64024qhrWteKTR8xc0HJEgN5P+YASvX9Cq2jWVmU3Ooj5iyRtJ5J5wup2dlSAzTc50SHPd154AAHA6IDVNV2tc1zQWEEFpMtI4IJAgcx0TLs2n6BhJlCutRYynPmOQIaPOS7gfgm1no9evTFVzajACNm6A5wI4LZ9BykngV1MXb3Pl4Y4im0xHZpM4LgIGV3Fr6VdmwnaciHNwY5wqdZJYFcot3WDmel3tanuYQdohuWgtHOeJA456cSgbm4oNcPiW3Jzk7CQegjb04XQb7wu3dvFMSf46JOZ5LmZH1VF1nT30q20NquZOHNktaezh8M7fqcdUn8yHYU3X6lSm1tKm1lOQNoB3TGZxtj05+iO0dhdTqGo4QMbQAIjkEdDyltnpzamXucwCJ84MxyDAGMdIWmt+IqLKZZRzTA8zhBP0MyQluwvBU7u5eC5opeXcdjh7+iungCpkgzu25J/sqhY+IA7lhg8EhWjwnWJrgn+JpJ9J7prt0I1gvW9bNeoHVF4HqZMy5ekGouwm9y9ItQcmHiVHVzlI66fam2Slb6C0dlXoTVCo5KZPt1F+zK9kQAleI82qjNssgZBC1YjBQWJsAOuXD1A168uHqFrlzMAfTciqbkvpORlMpQhrHKO4cvGFR3DlhkJtSKrF6FY9QKQVTme3Hv0/X6KkQmW1ENjdk9G8f9v0/Lq1saD3uaIMSCAB5R7AY+qFsa/Ulwn+QNafq5PdPuvRxJ/nfugf9U4jBPH9t/43tgGNpcSGkD06/bKpDrXbnf5e8BrP+7+vpBVw169L6gaQI44mB3Jdge6XOsS04O6MmsZcW+jAevr9oiUk3kvDQDplJjHS4FxA3beDH8znP/8AG3PzEMPYFWm0vi75YazrAIB+/meccu7TAglVWrpzxIbhk7oyfd9Q8veZwPXoFMK9QD5TjpPy9YPc8EnuAMAAKc38DRXyW52ubZ3bzGOeT2gKr69evuwRSlrGuh3QmIx3U1ncB0Ndg9B/dFigGl2MGCqcc/Q8IJyyUy1tKlB4dTOZ+UjDu4Pouq+FtccWgFzWEQIORMdjx9FU6NCXkROR9MKwWmnMkOcYMQM9Oqr2QebijGP2XWjqDHzLZI603Q7A6A9f87qo68xlyTVkkAQXghr2Rx8VsEj+oBwMLS+r/Cl4ceJxzjk+4Kr7PEPxS6pSO2q0S/DSHMMfvAOrSYDh0JB4JicpKRzxVGt9Z3YbFN/xWH+d2/2hri5jvb5vQIKnpYMGs0evwgWj6sJLT9AERRvy8uNIBjyIcyCGPnsI59IPsZJE9uw1xtqHY6BEncHDoN3UHoencjAR2hti9lm0vDmQWNyTxt/qb09xhWXTKQZFUuB3GAOwSkU9p2N8u3npnqD+hUla8DyATG2IaMAx1CaOrFl8F1FSRhYHoG1qHaJ7KUPSEza5ekt85M7h6T3jlh4iK9blCGmjbtQtCyKvQIbdefs6O2rwNT2ToXvoKN1FMqrEOQmixZAJoLEXC9TilwruUbSvK5WjCosVBtEo2mUvolG0ilCFNKiuDhbNKiuCgMhPflILg9PVPL8pFX5TxCFWYTm3q/8AFv4/qlNi1MmuhOChBrd0XVRT6yIAwPsm+lXY/wDHMjAkZJPokPiCiQ8vHPE+/P4Y+q2sdSZRDSPmP39SpPZZaLtUsW4DR6ye/f1j/OUM3TQTx5R9ye6Astbc44G49zgDsnFvcBzm0mmXnJPQDqfZCggj9KBMxnoiqdBzMOg/onLNu4MpiTB3O6CMfqjWWU5gEcZS00zdrK4LotHkZJiJ4Qt5We6C0EHggYVuq2zAOAThD19ObOMSf7KmWhLKYLGqXEugjgAiPb+6Fo6CRU+KAQRJG3jIIIjqDJBHGSr9b0WkT6x9QvKNMbo29UFE3Yq1t4eEgjh38LgSAe0+n5QntDw1jzEZ6d//AL6/eU5r3dFg84j+3ql2ran5P3TmuHo6CD0VVBexXJvRV/FbNnDCIME9fQn7fgqwa1R7mtcIAyCP7pvrmourSHmXRBxtOMie6XMtwG73EyegStqxknRcLR52DM4Uwelulv8A3YRQflIybJLhyVXbkfXcll05YaIquio2ra6Kia5YqyUlRiZXu5eb01k6N3FDVFu96ie5NEEkQlyxQvesVidMuVYrRpWVjlaAqNChtFyNplL6CY0GJWMicFQ3BRdOksqW8pR0Vy+SSsMq4XFlKAdpknhPFGFds6AvX3UZlMbuwgcKs1w7dGUwTS/uZcZzhLC+DLh7Dv7lF3IMoKux3Ljj8VO8lFoZMvzthgDZ5IRVjXqMB+GTvfG53p0A9EutqeAMZ/JGUbjYIb8xwtbs1Fq0TVPhAtOScvcfwaFcrXUGup79wj+EcD3hcyoMApvc90uj6N7k/Ra2mqS7YdwphgAJ5c4dI5gphKOis1FjZe5zYEkmQZPol+q6o8Gk9ny5f7z3+irtr4UvKlM1qdN+0tkDyzxjyEzx0UlWjefs4rfAeWMaAcZhsgkjkfZZIDaHNj4rphzw9hZnIOQT1LSja+usJENkHII7d1R6FYPiZATGhcRGeOoyChdDJFl1C+ZUZsfmeHDBBVXdS2ugkx0Ix7SjqjXOEtiUHc1eJG0nn+XCylYaoArvLpl0x6QVGxzTMTj81J8KT6znIjhb0QG7h1jlEAy0yr5BKJDxKUWdWG+yiffweUetk2OqpQFw0nhQC/B6oym+QiuOwqVCW7pO7IUgjonF46ErqVUHx0OpkJetd6IbTBXhoKYwK56jNREvpKJ1FMhWBVCsRRorE/YWi1PaSVNRtSUybZhE0aCRyFUAa2tEyo0FvSpoykxDY1UaU6KlNBTsCkTJGAH2vooxZ+iZlakq0YiNi24sZHCrer6Y1o3QrlWqY4Va1u4lhBRnGkGLtnO72oDUxwOVo14LoxHr1KjurhoeQe6FuHsaCQcrlWWXCrm6ztnIUdKrsIaCCefbulVIlxwYTCypNDsGST/meiLVAsZ3V04M3DuGtb3J/Mp34P0m5bWbUdb1ajTDg8sc9vywC0dhHMc9VZvCugMayjVflzTuaMEZBAyfwV6t7K5eQ5tYNbjyuaSY+hH5rLQrNdMuHtZJaWmZAMgPbOY7HrCVeKfFlGxf8PYHCqAXU+rd0759/wC628QVr9lZlOkbcscJewghzu7gSZjPQfVVep4auKN2+5uSa9Oq/cCyd4j5WuYAMjGZzt+ipHC2TlTYmc5hJfajc0kGDBDQXZDiDER1yFJQYKocKYLKjeAfleOZIjHMfRdO02ta+X9yARzLHNe08eYHJGfUJb440SlSLbqi2C6Q4DiYJ3cYOefRBxTVoKk06Zzcany0gtdw7mAfZD3N21zS04ByIk5Hbsm11Ra9wMAbsnH5qvXtEsJqU+WkAt6EdwlSHsnt7cuBipMDLeqLttMBbu3R9Ust67XmevUcLa6sqtMl7HEtd0RqjWEUX7C4Ez2SW7bVJJATPS6RrOz0VwpaS0gYVYKycjnNF1UHhPre4eBkK0u0NvZeVdGEYCdYF6lH1TUiEqp3250K0ar4cLjwl1Lw1BlBsyTC9NpymBskVp1jtCZfCC5pbOiOivuslp+xKx/CCjdRaggtFcNl6LFYDQasTC0OApWMWbW9z9lIxre/34S0CyWnTRdKmo7VpOQPvx7o+jA6iewyPVWjAnKRpTpd1LtCkaAT5nDuonAScn7KijQl2Y4Dss2jsFKKWJB/BeNpH3VooRsGqUpSm/0veCCFYBTjJx9R3j7oS/a7bLCJgwJCZxsClRxLxbofw6s8Aqt3NVgECSe6uni8V3uPxRGVSq9plcdK8nVbo1ok89Tx7I7S6m17Z7oFlMhbUpDmnseFmrBZ3/w/WDqVN/Zrdv0ESrhZsBIducOCROD9FzDR/ELHUGOYWjaNpaOkY4Vu0DV6VQdz1U4unQzVobeL9E/aaYdSqGnUZJa4dcfKfQqLRXvfTPxdm4ExtDgQJ4Oeen0STxhqNSl8N9q5oExUa6YLeSR2KzSHAzVbcEFxLi0QWiTxBCeTsnGJbv2p7CGupucwkDeyHRPG4DI94hJP9TKM2Y2uy14jJHII5HussNULnFr6jZb1kAEd8rn/APqD43bVqigxpcxpLS7PmkQSCE8dAayKq1X4VIEgiAAec5/NBPuGvLpdO4fkl1xqLtmxoMT1z90Hc1XGCPKYzCFINsme003l9OHNcBjtwU8pau0s2Rk49lUKbHTIcRCKoEgz1WpBydB8NaPtl5jKtbacLn2j6g8wNxVu0+7efmMp1NaFaYxLo5WwrN6rMH/MoerGYP8An6oNhSNLhzEK8MQ9cdDM+uAhCSODPX2SORVRGFTahnuHdCOuO6hFT0UmxqDn1AFBUuR0UIrsHLeneEJXeB8owezuEDBBvViALQeJj/PRYsYvZpBbhg6CFixWSRCyb4Z5lS0aUdV6sTJAeg6jR7QjKVEDoFixXRKRu2iJlZV4gcdlixUQhq1pjJ/BRPBAJ8vBGWg8iOoWLETFJ8VWUsgRjpwPsFza6s89Fixcv5Ozq4dAxswspWglYsXLbLUNxozNkxk+pRemaMJ+d7f6Xvb+RWLEU2CiTWtHa0Dc57p/me5w+xKLsNLbsiXQem4gfmsWJmwIl03T2MqFwHWO/wCaH8U6e3DgACfzWLE60I9iB1DywoDRWLEBiMUyTHRG06MDosWLGCNNBDlddPJwvFiIjGEHmVC+q7v6r1YmZogl1noOEFVwsWIVgcGq8KHcfRerFOQyIiCodmVixIEwn2WLFiJj/9k=">
</div>

#carousel {
position: relative;
width: 300px;
height: 200px;
overflow: hidden;
}
.carousel-img {
display: none;
position: absolute;
min-width: 100%;
min-height: 100%;
}
.carousel-img.visible {
display: block;
}

//var carousel = document.getElementById('carousel');
var images = document.getElementsByClassName('carousel-img');
var imageVisible = 0;
// cette fonction affiche l'image suivante, et cache la précédente
function changerImage() {
images[imageVisible].classList.remove('visible');
imageVisible = (imageVisible + 1) % images.length;
images[imageVisible].classList.add('visible');
}
document.getElementById('next').onclick = changerImage;
changerImage();
// concepts:
// - utiliser une variable pour stocker l'état
// - incrémentation avec modulo
// - BONUS: bouton 'previous'
// - BONUS: agrandissement au clic
// - BONUS: setInterval

XIII-A-2-b - Composant : Accordéon

Un « accordéon » est un composant proposant plusieurs rubriques à l'utilisateur, et lui permettant d'afficher le contenu d'une rubrique à la fois, en cliquant sur son titre.

Pour développer un accordéon, il faut maîtriser :

- les sélecteurs DOM (getElementById(), getElementsByClassName() et/ou getElementsByTagName()) ;

- la manipulation de styles et/ou classes CSS en JavaScript ;

- la capture d'événements onclick ;

- et la gestion du scope.

Solution : Jsfiddle

<section>
<header>Le programme de Donald Trump</header>
<article>La durée de vie d'un décret est par ailleurs limitée: un président donc abroger un décret d'un prédécesseur, comme l'a fait Donald Trump en annulant plusieurs textes signés par Barack Obama, sur les projets d'oléoducs Keystone ou sur le financement ...</article>
</section>
<section>
<header>Donald Trump accuse les juges...</header>
<article>Dire que Donald Trump a peu apprécié le blocage de son décret anti-immigration par un juge fédéral est un euphémisme. Samedi 4 février, il l'a désigné à la vindicte des 23 millions d'abonnés de son compte Twitter, le qualifiant de « soi-disant juge ...</article>
</section>
<section>
<header>Donald Trump rencontrera ses alliés de l'Otan</header>
<article>En mai aura lieu une rencontre entre Donald Trump et ses alliés de l'Otan, a annoncé dimanche la Maison Blanche, après un entretien téléphonique entre le président américain et le secrétaire général de l'Alliance atlantique Jens Stoltenberg. Donald ...</article>
</section>

header {
background: black;
color: white;
}
article {
display: none;
background: white;
color: black;
}
article.visible {
display: block;
}

var headers = document.getElementsByTagName('header');
var articles = document.getElementsByTagName('article');
// cette fonction affiche le i-ème article
function afficherArticle(i) {
articles[i].classList.add('visible');
}
// cette fonction cache tous les articles
function cacherArticles() {
for (var i = 0; i < articles.length; i++) {
articles[i].classList.remove('visible');
}
}
// cette fonction retourne une fonction qui sert à n'afficher que l'i-ème article
function pourAfficherArticle(i) {
return function(){
cacherArticles();
afficherArticle(i);
};
}
// initialisation des headers
for (var i = 0; i < headers.length; i++) {
headers[i].onclick = pourAfficherArticle(i);
}
// concepts:
// - getElementsByTagName()
// - scope (fonctions dans boucles for)
// - fonctions qui retournent des fonctions

XIII-A-2-c - Composant : Galerie vidéo

Une galerie vidéo permet à l'utilisateur de visualiser une vidéo, en cliquant parmi une sélection fournie.

Pour développer une galerie vidéo, il faut maîtriser :

- les sélecteurs DOM (getElementById(), getElementsByClassName() et/ou getElementsByTagName()) ;

- la capture d'événements onclick ;

- l'intégration d'<iframe> (ex. : à l'aide de innerHTML) ;

- et la gestion du scope.

Solution : Jsfiddle

<button>video 0</button>
<button>video 1</button>
<button>video 2</button>
<div id='video'></div>

#video {
width: 300px;
height: 200px;
background: black;
}

// URL d'intégration de chaque vidéo Youtube
var videos = [
'https://www.youtube.com/embed/0uaQMxBjd5E',
'https://www.youtube.com/embed/c8ZPn6p5khI',
'https://www.youtube.com/embed/ikkrEOD1BbQ',
];
// références vers le DOM
var video = document.getElementById('video');
var buttons = document.getElementsByTagName('button');
// fonction qui retourne une fonction permettant d'intégrer la i-ème vidéo
function pourAfficherVideo(i) {
return function() {
video.innerHTML = '<iframe width="320" height="200" src="' + videos[i] + '?autoplay=1"></iframe>'
};
}
// initialisation des boutons
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = pourAfficherVideo(i);
}
// concepts:
// - tableaux
// - getElementsByTagName()
// - scope (fonctions dans boucles for)
// - fonctions qui retournent des fonctions
// - intégration dynamique de vidéos
// - BONUS: lecture séquentielle automatique
// - BONUS: boutons pour contrôler la vidéo

XIV - TP 14 : Composants et POO

Objectifs :

- Permettre à un même composant d'être instancié plusieurs fois sur une même page

- Initiation à la Programmation Orientée Objet et au mot clé this

XIV-A - Composant Web multiinstances et POO

XIV-A-1 - Défi : Plusieurs instances de Carousel sur une même page

Dans la partie précédente, vous avez appris à développer un composant Web simple.

Mais votre composant fonctionnera-t-il correctement si vous l'intégrez plusieurs fois sur une même page Web ? (Essayez, et observez ce qui se passe !)

Supposons par exemple que vous ayez développé un composant de Carousel permettant de créer une galerie d'images à partir des éléments <img> de la page portant la classe carousel-img (comme celui-ci). Si vous souhaitez intégrer deux galeries indépendantes sur votre page, avec des images spécifiques à chacune d'elles, le composant ne sera pas capable de faire la distinction entre les images de ces deux galeries, car il se contente de créer une galerie à partir de toutes les images portant la classe carousel-img !

Alors, comment faire pour qu'un composant puisse être intégré plusieurs fois sur une même page ?

Il existe plusieurs solutions, notamment :

- implémenter le composant de manière à ce qu'il s'applique sur des groupes d'éléments, plutôt que sur les éléments eux-mêmes ; (ex. : chercher les éléments portant une classe image-group, puis intégrer les images définies comme sous-éléments de ce groupe) ;

- permettre à l'intégrateur de préciser spécifiquement sur quelles images chaque galerie doit être construite, et à quel moment elles doivent être construites (ex. : en appelant une fonction fournie par le composant, en passant en paramètres l'id de l'élément contenant les images de la galerie à intégrer).

Nous allons voir ces deux solutions plus en détail.

XIV-A-2 - Solution 1 : Composant qui s'applique sur des groupes d'éléments

Supposons que notre composant à intégrer fournisse les instructions d'intégration suivantes :

Au chargement du composant, chaque élément portant la classe image-group sera transformé en Carousel, à partir des balises <img> définis dans cet élément.

On pourrait alors intégrer le composant de la manière suivante :

1. Galerie 1 :

2. <div class="image-group">

3. <img src="img1.jpg">

4. <img src="img2.jpg">

5. <img src="img3.jpg">

6. </div>

7. Galerie 2 :

8. <div class="image-group">

9. <img src="img4.jpg">

10. <img src="img5.jpg">

11. <img src="img6.jpg">

12. </div>

13. <script src="carousel.js"></script> <!-- va convertir les groupes en galeries -->

Pour fonctionner ainsi, le code source du Carousel devra récupérer les images de chaque groupe, de la manière suivante :

1. var groupes = document.getElementsByClassName('image-group');

2. for (var i = 0; i < groupes.length; i++) {

3. var groupe = groupes[i];

4. var imagesDuGroupe = groupes[i].getElementsByTagName('img');

5. creerGalerie(groupe, imagesDuGroupe); // création de la galerie dans l'élément groupe

6. }

… au lieu de se contenter d'un unique appel à document.getElementsByTagName('img'), applicable à l'ensemble des images de la page.

Remarque : Ici, nous avons appelé la fonction getElementsByTagName() sur un élément, et non sur document. Ceci a pour effet de ne retourner que les éléments qui sont contenus à l'intérieur de cet élément parent.

Avantage de cette solution :

- simple à implémenter, à documenter et à intégrer.

Inconvénient de cette solution :

- l'intégrateur ne peut pas générer une galerie à la demande, car les galeries sont toutes générées au moment où le composant est chargé.

XIV-A-2-a - Exemple de documentation d'un composant pour groupes d'éléments

XIV-A-3 - Solution 2 : Composant contrôlable par l'intégrateur

La solution précédente est simple, mais empêche l'intégrateur de contrôler la transformation de chaque galerie.

Par exemple, comment régénérer une galerie après l'ajout d'une image ?

Pour permettre ce genre de manipulation, il faudrait que chaque galerie soit accessible individuellement depuis le code JavaScript de l'intégrateur.

Idéalement, l'intégrateur aimerait pouvoir effectuer ce genre d'appel :

1. maGalerie2.ajouterImage('img7.jpg');

2. maGalerie2.regenerer();

Pour cela, il faudrait que chaque galerie puisse être référencée par une variable, et donc que le composant fournisse un moyen de retourner une variable pour chaque galerie créée.

La solution consiste à ce que le code source du composant définisse une fonction pour créer une galerie et retourner une référence vers un objet permettant à l'intégrateur d'interagir avec cette galerie.

Exemple :

1. // extrait de code source du composant

2. function creerGalerie(conteneur, urlImages) {

3. var html = '';

4. // génération des éléments <img> dans le conteneur

5. for (var i = 0; i < urlImages.length; i++) {

6. html = html + '<img src="' + urlImages[i] + '" class="hidden">;

7. }

8. conteneur.innerHTML == html;

9. // TODO : afficher la première image seulement

10. // TODO : créer un objet reference permettant à l'intégrateur de manipuler la galerie

11. return reference;

12. }

La documentation du composant contiendrait alors les instructions d'intégration suivantes :

Pour chaque galerie à créer, appeler la fonction creerGalerie(conteneur, urlImages), avec conteneur étant une référence de l'élément dans lequel créer la galerie (ex. : un <div>), et urlImages un tableau contenant les URL des images à intégrer dans la galerie. Cette fonction retourne un objet vous permettant d'interagir avec la galerie.

XIV-A-3-a - Exemple de documentation d'un composant proposant une API

Comment définir cet objet reference qui permettra à l'intégrateur d'interagir distinctement avec chaque galerie ? En définissant puis instanciant une classe, tel que nous allons le voir ensuite.

XIV-A-4 - Programmation Orientée Objet : classes, instances et this

Une classe est un modèle d'objet. Elle peut être instanciée, c'est-à-dire qu'on crée un objet (appelé instance) selon ce modèle.

La modèle d'une classe consiste à assurer que chaque objet instance de cette classe aura les mêmes :

- propriétés (cf chapitre sur les types avancés) ;

- et méthodes : des fonctions qui s'appliquent à une instance donnée.

À noter que :

- chaque instance d'une classe aura les mêmes propriétés, mais la valeur de celles-ci pourra être différente pour chaque instance ;

- chaque instance d'une classe aura les mêmes méthodes, mais l'exécution de la fonction correspondante ne s'appliquera qu'à l'instance sur laquelle elle aura été appelée.

Exemple de classe que vous avez déjà utilisée sans le savoir : la classe Element.

En effet, à chaque fois que vous appelez la fonction document.getElementById(), elle vous retourne un objet qui est en fait une instance de la classe Element. C'est grâce à la classe Element que vous pouvez utiliser les propriétés value, onclick et la méthode getAttribute() sur tout objet retourné par document.getElementById().

Notez que getElementById() est aussi une méthode. Quand on effectue un appel à document.getElementById(), on exécute en réalité la méthode getElementById() sur l'objet document qui est dérivé de la classe Element.

XIV-A-5 - Comment définir et instancier une classe en JavaScript/ES6

En guise d'exemple, nous allons définir et instancier une classe Galerie utile pour notre composant Carousel. Cette classe définira méthodes qui seront rattachées à chaque objet retourné par notre fonction creerGalerie(), tel qu'introduite plus haut.

Afin de permettre l'appel des méthodes ajouterImage() et regenerer() sur une instance de Galerie :

1. // supposons que conteneur référence un <div> de la page

2. var maGalerie2 = creerGalerie(conteneur, ['img4.jpg', 'img5.jpg', 'img6.jpg']);

3. maGalerie2.ajouterImage('img7.jpg');

4. maGalerie2.regenerer();

… notre composant Carousel doit définir la classe Galerie de la manière suivante :

1. class Galerie {

2. // définition du constructeur de la classe Galerie

3. constructor(conteneur, urlImages) {

4. this.conteneur = conteneur;

5. this.urlImages = urlImages;

6. }

7. // cette méthode permet d'ajouter une image à cette galerie

8. ajouterImage(url) {

9. this.urlImages.push(url);

10. }

11. // cette méthode permet de générer et d'afficher cette galerie dans la page

12. regenerer() {

13. var html = '';

14. // génération des éléments <img> dans le conteneur

15. for (var i = 0; i < this.urlImages.length; i++) {

16. html = html + '<img src="' + this.urlImages[i] + '" class="hidden">;';

17. }

18. this.conteneur.innerHTML == html;

19. }

20. }

Comme vous l'aurez remarqué :

- le code que nous avions écrit dans la fonction creerGalerie() se trouve maintenant dans la définition de la méthode regenerer() de notre classe Galerie;

- les paramètres conteneur et urlImages de la fonction creerGalerie() ont été transférés au constructeur de la classe Galerie, et stockées sous forme de propriétés d'un certain objet this (nous allons expliquer ça plus bas).

Maintenant que la classe Galerie de notre composant est définie, il va falloir que notre fonction creerGalerie() retourne une instance de cette classe.

Il suffit de la redéfinir de la manière suivante :

1. // extrait de code source du composant, après avoir défini la classe Galerie

2. function creerGalerie(conteneur, urlImages) {

3. var reference = new Galerie(conteneur, urlImages);

4. reference.regenerer(); // générer et afficher la galerie fraichement créée

5. return reference; // retourner l'instance de Galerie

6. // ...pour que l'intégrateur puisse avoir accès aux méthodes ajouterImage() et regenerer()

7. }

Le mot-clé new permet d'instancier notre classe, et donc d'exécuter son constructeur (défini plus haut) en leur transmettant les paramètres conteneur et urlImages. La variable reference contient alors une instance de cette classe, et permet donc d'appeler les méthodes regenerer() et ajouterImage() sur cette instance.

XIV-A-6 - Usage de this

Quand on mentionne this dans la définition d'une méthode, ce mot-clé représente l'instance depuis laquelle la méthode a été appelée.

Par exemple :

1. class Article {

2. constructor(titre) {

3. this.titre = titre;

4. }

5. getTitre() {

6. return this.titre; // this === article1 ou article2, dans notre exemple

7. }

8. }

9. var article1 = new Article('Trump élu président');

10. var article2 = new Article('Macron se présente');

11. article1.getTitre(); // => retourne 'Trump élu président'

12. article2.getTitre(); // => retourne 'Macron se présente'

À noter qu'en JavaScript, this est en fait utilisable depuis toute fonction, qu'elle soit ou pas définie dans une classe. Il faut retenir que l'usage de classes permet à l'interpréteur JavaScript d'affecter automatiquement à this l'instance sur laquelle s'exécute chaque méthode.

Attention : pour cette dernière raison, il est parfois nécessaire de garder une référence de this, en particulier quand on souhaite y accéder depuis une sous-fonction.

Exemple :

1. class Galerie {

2. // [...]

3. // cette méthode permet de générer et d'afficher cette galerie dans la page

4. regenerer() {

5. // [...]

6. // génération des éléments <img> dans le conteneur

7. var galerie = this;

8. for (var i = 0; i < this.urlImages.length; i++) {

9. img.onclick = function() {

10. galerie.afficherImageSuivante(); //, car dans cette fonction, this !== galerie

11. };

12. }

13. // [...]

14. }

15. }

Dans l'exemple ci-dessus, nous souhaitons appeler une méthode de notre instance de galerie, quand l'utilisateur cliquera sur n'importe laquelle des images affichées par la galerie. Pour cela, nous devons définir et affecter une fonction anonyme à l'attribut onclick de chaque image, définissant le comportement que devra adopter le navigateur à chaque clic. Or, à l'intérieur de cette définition de fonction, this ne correspond pas à l'instance de la classe Galerie sur laquelle a été appelée la méthode regenerer, mais à l'objet sur lequel a été appelé la fonction anonyme. Pour conserver une référence vers notre instance, nous avons affecté la valeur de this à une variable galerie, en dehors de la définition de notre fonction anonyme.

XIV-A-7 - Exercice 2 : Créer un composant Web instanciable plusieurs fois

Dans l'exercice 1, nous avons développé un composant qui ne pouvait être intégré qu'en un seul exemplaire sur chaque page Web.

Dans cet exercice, modifiez le code source et la documentation de ce composant, puis publiez-le sur Internet, de manière à ce que tout intégrateur puisse :

- l'intégrer plus d'une fois sur une même page ;

- ajouter des données à une de ces intégrations, en appelant une méthode fournie par le composant (ex. : ajouter une image, dans le cas d'un composant Carousel).

Concevez votre composant de manière à ce que :

- l'intégrateur n'ait pas besoin de modifier le code source de votre composant ;

- l'intégrateur n'ait pas besoin de taper plus de trois lignes de code au total (HTML et JavaScript, balise <script> comprise) pour intégrer chaque instance de votre composant sur sa page.

Pour que le code JavaScript de votre composant soit intégrable sans que l'utilisateur ait besoin de dupliquer votre code source dans celui de son site, vous pouvez publier le fichier .js correspondant sur votre espace étudiant, ou sur Codepen. (le code JS sera alors accessible directement en ajoutant l'extension .js à votre URL Codepen, ex. : http://codepen.io/adrienjoly/pen/ggNNba.js)

Vous serez évalués sur :

- la concision et clarté des instructions d'intégration fournies sur la page de documentation de votre composant ;

- la simplicité effective d'intégration du composant ;

- la qualité d'une ou plusieurs démos directement utilisables sur la page de documentation de votre composant ;

- le bon fonctionnement de votre composant, en l'intégrant plusieurs fois sur un autre site.

Solutions : js-composants (sur Github)

XIV-A-8 - Exercice 3 : Création de classe

--> http://marijnhaverbeke.nl/talks/es6_falsyvalues2015/exercises/#Point

XIV-A-9 - Exercice 4 : Classe Accordeon

Dans l'exercice 2, vous avez développé un composant instanciable plus d'une fois sur une même page.

Dans l'exercice 3, vous avez appris à créer une classe.

Le but de cet exercice est de développer un composant instanciable plus d'une fois sur une même page, en l'implémentant sous forme de classe.

Définir la classe Accordeon, afin qu'elle puisse être instanciée et manipulée de la manière suivante par l'intégrateur :

1. var accordeon1 = new Accordeon(document.getElementById('accordeon1'));

2. var accordeon2 = new Accordeon(document.getElementById('accordeon2'));

3. accordeon1.afficherSection(1); // => affiche le contenu de "Donald Trump accuse les juges...",

4. // ... dans le 1er accordéon seulement.

Suivant la structure et le fonctionnement du composant Accordéon défini plus haut dans ce chapitre :

- les éléments accordeon1 et accordeon2 doivent chacun contenir plusieurs éléments <section>, contenant chacun un élément <header> et un élément <article> ;

- à tout instant, seul l'article correspondant à la section sur laquelle l'utilisateur a cliqué en dernier doit être affiché ;

- les deux accordéons accordeon1 et accordeon2 doivent être indépendants, mais leur fonctionnement doit reposer sur la même classe.

Code à compléter : https://jsfiddle.net/Luqvg3z1/1/

<h1>Accordéon 1</h1>
<div id="accordeon1">
<section>
<header>Le programme de Donald Trump</header>
<article>La durée de vie d'un décret est par ailleurs limitée: un président donc abroger un décret d'un prédécesseur, comme l'a fait Donald Trump en annulant plusieurs textes signés par Barack Obama, sur les projets d'oléoducs Keystone ou sur le financement ...</article>
</section>
<section>
<header>Donald Trump accuse les juges...</header>
<article>Dire que Donald Trump a peu apprécié le blocage de son décret anti-immigration par un juge fédéral est un euphémisme. Samedi 4 février, il l'a désigné à la vindicte des 23 millions d'abonnés de son compte Twitter, le qualifiant de « soi-disant juge ...</article>
</section>
<section>
<header>Donald Trump rencontrera ses alliés de l'Otan</header>
<article>En mai aura lieu une rencontre entre Donald Trump et ses alliés de l'Otan, a annoncé dimanche la Maison Blanche, après un entretien téléphonique entre le président américain et le secrétaire général de l'Alliance atlantique Jens Stoltenberg. Donald ...</article>
</section>
</div>
<h1>Accordéon 2</h1>
<div id="accordeon2">
<section>
<header>Le programme de Donald Trump</header>
<article>La durée de vie d'un décret est par ailleurs limitée: un président donc abroger un décret d'un prédécesseur, comme l'a fait Donald Trump en annulant plusieurs textes signés par Barack Obama, sur les projets d'oléoducs Keystone ou sur le financement ...</article>
</section>
<section>
<header>Donald Trump accuse les juges...</header>
<article>Dire que Donald Trump a peu apprécié le blocage de son décret anti-immigration par un juge fédéral est un euphémisme. Samedi 4 février, il l'a désigné à la vindicte des 23 millions d'abonnés de son compte Twitter, le qualifiant de « soi-disant juge ...</article>
</section>
<section>
<header>Donald Trump rencontrera ses alliés de l'Otan</header>
<article>En mai aura lieu une rencontre entre Donald Trump et ses alliés de l'Otan, a annoncé dimanche la Maison Blanche, après un entretien téléphonique entre le président américain et le secrétaire général de l'Alliance atlantique Jens Stoltenberg. Donald ...</article>
</section>
</div>

/* TODO:
*
* Remplacer l'implémentation du composant ci-dessous par une classe Accordeon,
* de manière à ce qu'on puisse l'intégrer et l'utiliser de la manière suivante:
*
* var accordeon1 = new Accordeon(document.getElementById('accordeon1'));
* var accordeon2 = new Accordeon(document.getElementById('accordeon2'));
* accordeon1.afficherSection(1); // => affiche le contenu de "Donald Trump accuse les juges...",
*                                // ... dans le 1er accordéon seulement.
*
*/
// ================
// CODE SOURCE DU COMPOSANT:
class Accordeon {
constructor(element) {
// implémenter l'initialisation du composant dans element
// (cacher les articles, etc...)
}
// définir la méthode afficherSection()
}
// ================
// CODE DE L'INTEGRATEUR:
var accordeon1 = new Accordeon(document.getElementById('accordeon1'));
var accordeon2 = new Accordeon(document.getElementById('accordeon2'));
accordeon1.afficherSection(1); // => affiche le contenu de "Donald Trump accuse les juges...",
// ... dans le 1er accordéon seulement.
// ================
// CODE À RÉ-IMPLÉMENTER DANS LA CLASSE, PUIS À SUPPRIMER
var headers = document.getElementsByTagName('header');
var articles = document.getElementsByTagName('article');
// cette fonction affiche le i-ème article
function afficherArticle(i) {
articles[i].classList.add('visible');
}
// cette fonction cache tous les articles
function cacherArticles() {
for (var i = 0; i < articles.length; i++) {
articles[i].classList.remove('visible');
}
}
// cette fonction retourne une fonction qui sert à n'afficher que l'i-ème article
function pourAfficherArticle(i) {
return function(){
cacherArticles();
afficherArticle(i);
};
}
// initialisation des headers
for (var i = 0; i < headers.length; i++) {
headers[i].onclick = pourAfficherArticle(i);
}

header {
background: black;
color: white;
}
article {
display: none;
background: white;
color: black;
}
article.visible {
display: block;
}

XV - TP 15 : jQuery, Node.js et frameworks

Objectifs :

- Comprendre les bases d'usage de la bibliothèque jQuery

- Découvrir Node.js et l'installation de modules avec npm

- Découvrir la création d'une Single Page App avec Augular et React

XV-A - Convertir un composant jQuery en JavaScript/DOM pur

Pour une introduction à la bibliothèque jQuery, consulter les slides du TP.

Références :

- Some jQuery Functions And Their JavaScript Equivalents

- Plain JS

XV-A-1 - Exercice 1 : convertir un composant accordéon

- Tester et analyser le jsfiddle d'exemple

- Forker ce jsfiddle, puis le modifier pour qu'il ne contienne plus de jQuery :

- Fonction associée au clic sur un seul menu

- … puis sur chaque menu (à l'aide d'une boucle)

- Cacher les éléments .content

- Afficher l'élément .content du .menu qui a été cliqué

Solution : jsfiddle

<div class="menu">
<p>Menu 1</p>
<ul class="content">
<li>A</li>
<li>B</li>
</ul>
</div>
<div class="menu">
<p>Menu 2</p>
<ul class="content">
<li>A</li>
<li>B</li>
</ul>
</div>
<div class="menu">
<p>Menu 3</p>
<ul class="content">
<li>A</li>
<li>B</li>
</ul>
</div>

body {
font-family: Arial, Helvetica, sans-serif;
}
p {
background-color: #F60;
border-bottom: 1px solid #FFFFFF;
cursor: pointer;
padding: 10px;
color: #FFFFFF;
margin: 0;
}
ul {
background-color: #EFEFEF;
display: none;
margin: 0;
}

/*
$('.menu').on('click', function() {
$('.content').hide();
$(this).find('.content').show();
console.log(this);
});
*/
// on a traduit ce code depuis le composant http://ejghkejghkgjehgk
var menus = document.getElementsByClassName('menu');
for (var i = 0; i<menus.length; i++) {
console.log(i, menus[i]);
menus[i].onclick = function(event) {
var contents = document.getElementsByClassName('content');
for (var j = 0; j<contents.length; j++) {
contents[j].style.display = 'none';
}
//console.log('click', event.currentTarget);
event.currentTarget.children[1].style.display = 'block';
};
}

XV-A-2 - Exercice 2 : convertir un autre composant (au choix)

Exemples de composants :

- Slider : Simple Slider ou Craftyslide

- Nombre animé : codepen, jsfiddle, ou github

codepen

<div id="shiva"><span class="count">200</span></div>
<div id="shiva"><span class="count">1000</span></div>
<div id="shiva"><span class="count">853</span></div>
<div id="shiva"><span class="count">154</span></div>
<div id="shiva"><span class="count">10</span></div>
<div id="shiva"><span class="count">87</span></div>
<div style="clear:both"></div>
<div id="talkbubble"><span class="count">1421</span></div>
<div id="talkbubble"><span class="count">145</span></div>
<div id="talkbubble"><span class="count">78</span></div>
<br />
<br />
<a class="linker" href="http://www.i-visionblog.com/2014/11/jquery-animated-number-counter-from-zero-to-value-jquery-animation.html"  target="_blank">visit Tutorial in Blog</a>
<br />

#shiva
{
width: 100px;
height: 100px;
background: red;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
float:left;
margin:5px;
}
.count
{
line-height: 100px;
color:white;
margin-left:30px;
font-size:25px;
}
#talkbubble {
width: 120px;
height: 80px;
background: red;
position: relative;
-moz-border-radius:    10px;
-webkit-border-radius: 10px;
border-radius:         10px;
float:left;
margin:20px;
}
#talkbubble:before {
content:"";
position: absolute;
right: 100%;
top: 26px;
width: 0;
height: 0;
border-top: 13px solid transparent;
border-right: 26px solid red;
border-bottom: 13px solid transparent;
}
.linker
{
font-size : 20px;
font-color: black;
}

$('.count').each(function () {
$(this).prop('Counter',0).animate({
Counter: $(this).text()
}, {
duration: 4000,
easing: 'swing',
step: function (now) {
$(this).text(Math.ceil(now));
}
});
});

jsfiddle

<script>
</script>
<div id="el"></div>

// Animate the element's value from x to y:
$({someValue: 40000}).animate({someValue: 45000}, {
duration: 3000,
easing:'swing', // can be anything
step: function() { // called on every step
// Update the element's text with rounded-up value:
$('#el').text(commaSeparateNumber(Math.round(this.someValue)));
}
});
function commaSeparateNumber(val){
while (/(\d+)(\d{3})/.test(val.toString())){
val = val.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
}
return val;
}

XV-B - Node.js et npm

XV-B-1 - Introduction

En début d'année, nous avons vu qu'il existait différents types d'interpréteurs JavaScript.

Exemples :

- la console de Chrome permet d'exécuter des instructions JavaScript de manière interactive ;

- un fichier JavaScript intégré dans une page web est exécuté dès l'ouverture de cette page dans un navigateur.

Il existe un autre moyen d'exécuter du code JavaScript : Node.js.

Node.js est un logiciel basé sur le moteur d'exécution intégré dans Google Chrome : V8. Il est constitué de deux commandes exécutables depuis le « terminal » (la console du système d'exploitation) : node et npm.

La commande node permet d'exécuter du code JavaScript, alors que npm permet de télécharger puis installer des modules (appelés packages) de manière locale (dans le dossier en cours) ou globale (=> modules accessibles depuis n'importe quel dossier).

Node.js est couramment utilisé pour développer des serveurs web (aussi appelé back-end, dans le cas de développement d'applications client-serveur telles que les applications web), mais permet aussi la réalisation de scripts ayant accès à toutes les fonctions du système d'exploitation : système de fichiers, accès illimité aux ressources de n'importe quel réseau (dont le web), etc.

npm est couramment utilisé pour installer les dépendances, c'est-à-dire les modules nécessaires par un programme JavaScript qui sera exécuté par Node.js.

À noter que la plupart des frameworks front-end actuels recommandent l'usage de la commande npm pour les télécharger sur votre machine, ainsi que vous installer les éventuels bibliothèques, composants et autres extensions dont votre application aura besoin.

XV-B-2 - Installer et tester Node.js sur sa machine

Après avoir ouvert le terminal, taper node pour exécuter la version interactive de Node.js.

Pour lancer le « terminal » :

- sous Windows, appuyer sur la touche « Windows », taper « cmd » puis presser ENTRÉE.

- sous Mac, presser Cmd-Espace, taper « term » puis presser ENTRÉE.

Si la commande node n'est pas trouvée par le système, il faudra installer Node.js depuis son site officiel, puis rouvrir le terminal avant de poursuivre la manipulation.

Une fois Node.js lancé, vous pouvez taper des instructions, comme dans la console de Chrome Dev Tools.

Par exemple : tapez 1+1; pour voir le nombre 2 s'afficher en retour.

Pour quitter l'interpréteur Node.js, pressez Ctrl-C.

XV-B-3 - Exécuter un fichier .js avec Node.js

Nous allons d'abord créer un fichier test.js très simple, en tapant la commande suivante dans le terminal :

1. echo "console.log('bonjour');" >test.js

Sous Windows, vous aurez peut-être besoin de retirer les guillemets.

Ensuite, pour exécuter ce fichier, il suffit alors de taper la commande suivante depuis le terminal :

1. node test.js

Vous devriez alors voir s'afficher la ligne suivante :

bonjour

À noter que les symboles tels que window, document, alert() et getElementById() ne sont pas définis par Node.js, car ils sont seulement fournis lorsqu'un programme JavaScript s'exécute depuis un navigateur Web !

XVI - Single Page Apps avec Augular et React

XVI-A - Front-end VS back-end

Une application Web moderne consiste en :

- un client / front-end : programme exécuté par le navigateur de chaque utilisateur ;

- un « serveur web » / back-end : programme exécuté sur une machine appelée « serveur », qui a pour mission de répondre aux requêtes des « clients » (et donc de chaque utilisateur).

Comme nous l'avons vu, un client « web » est généralement constitué de fichiers HTML, CSS et JavaScript ayant accès à l'API du DOM fournie par le navigateur. À moins qu'il utilise des moyens de persister ses données (ex. : à l'aide de cookies, LocalStorage, ou requêtes AJAX vers un serveur), son « état » (c.-à-d. toutes les données de l'utilisateur saisies dans la page, et valeurs des variables) est réinitialisé à chaque rafraichissement de la page.

Un « serveur web » est rattaché à une adresse IP (ex. : 127.0.0.1, ou nom de domaine) et un port (ex. : 80) et permet de réagir et répondre à des requêtes HTTP envoyées par des « clients » à différents chemins (ex. : /posts) et avec différentes méthodes (ex. : GET, POST, etc.). Un « serveur web » peut être implémenté en nombreux langages : PHP, Java, ASP, etc. Node.js peut être utilisé pour exécuter un serveur web implémenté en JavaScript.

Il est relativement facile et rapide de développer un client web simple : il suffit d'une page HTML associée à un fichier JavaScript.

Par contre, dès qu'une application web contient plusieurs écrans et/ou modes (ex. : utilisateur connecté ou pas), elle devient vite complexe à écrire et maintenir.

XVI-B - Frameworks et bibliothèques front-end

Pour mieux structurer le code et le fonctionnement d'applications web riches, nombreux frameworks et bibliothèques (libraries) ont été développés. Ils sont généralement inspirés du principe d'architecture MVC (Modèle-Vue-Controlleur, cf. définition sur Wikipédia).

Notamment, pour aider au développement de clients web, les technologies suivantes sont actuellement très en vogue :

- Angular (en JavaScript ou TypeScript), développé par Google ;

- React, développé par Facebook.

(Il en existe nombreuses autres : Polymer, Ember.js, Vue.js…)

Suivez les guides liés à ces deux technologies pour comprendre leur fonctionnement par la pratique.

XVI-C - Solutions hybrides

Il existe aussi des bibliothèques et frameworks permettant de faciliter le lien entre front-end et back-end :

- Firebase permet de créer une base de données sur Internet, et d'y accéder directement depuis un client web, et se présente donc comme un substitut simple à un serveur web / back-end. Solution valable quand le plus gros de son application s'exécute dans le navigateur (consulter slides et vidéos de mon cours de l'an dernier et/ou guide officiel) ;

- basé sur Node.js et MongoDB, Meteor permet de créer des applications web JavaScript isomorphiques. C'est-à-dire que des parties du code peuvent s'exécuter à la fois côté serveur et/ou côté client (dans le navigateur). Un de ses gros avantages est de permettre l'intégration de composants gérant à la fois la partie « client » et la partie « serveur », ex. : bouton d'identification d'utilisateur avec stockage automatique dans la base de données de l'application.

Ces deux solutions sont très pratiques pour réaliser des prototypes de manière rapide / efficace.

XVII - TP 16 : Identification avec Google et Facebook

Objectifs :

- Intégrer un bouton de connexion pour identifier l'utilisateur

- Gérer l'état de l'application en fonction de l'identification

XVII-A - Introduction

Identifier l'utilisateur d'une application consiste à connaitre l'identité de la personne qui utilise une application depuis son navigateur, afin de :

- personnaliser son expérience d'usage de l'application (ex. : garder l'historique des derniers achats, sur un site e-commerce) ;

- restreindre l'accès à certains utilisateurs (ex. : contrôles JavaScript seulement accessibles aux étudiants de l'EEMI).

Sur la plupart des sites, cette identification est matérialisée par trois opérations :

1. La création d'un compte utilisateur, en fournissant par exemple un email et un mot de passe (en anglais : user sign-up ou user registration) ;

2. La vérification de l'identité de l'utilisateur, en demandant par exemple à l'utilisateur de cliquer sur un lien envoyé à l'adresse email qu'il a fourni lors de la création de son compte (ou par SMS) ; (permet de s'assurer que l'adresse email est valide, et de réduire les risques d'usurpation d'identité) ;

3. L'identification à proprement parler de l'utilisateur sur son compte, en saisissant ses identifiants (en anglais : user log-in ou user sign-in).

XVII-B - Identification par plateforme tierce

Plusieurs plateformes tierces (dont Google et Facebook) fournissent aux développeurs d'applications des moyens de rendre plus rapide l'identification des utilisateurs. Ces moyens consistent à donner accès à l'identité d'un utilisateur une fois que celui-ci accepte de partager ces informations avec l'application.

Le mécanisme le plus répandu est OAuth. Il standardise les échanges nécessaires entre la plateforme tierce d'identification et une application, afin que cette dernière puisse accéder aux informations de l'utilisateur, voire à certaines fonctionnalités supplémentaires.

Dans ce cours, nous n'allons pas décrire le fonctionnement de OAuth, mais en récapituler les grandes lignes.

XVII-B-1 - Ce que voit l'utilisateur

Voici ce qui est affiché à l'utilisateur, après qu'il a cliqué sur un bouton « Se connecter avec Facebook » (en anglais : « Facebook Connect ») depuis une application web :

Cette page web est générée par Facebook pour demander à l'utilisateur s'il accepte de partager ses données d'identification avec l'application depuis laquelle il a cliqué sur le bouton.

Le plus souvent, ces données contiennent notamment le nom de l'utilisateur, son adresse email, et sa photo de profil. Parfois plus.

Ces données sont envoyées à l'application seulement si l'utilisateur accepte.

XVII-B-2 - Échanges entre l'application et la plateforme tierce

Du point de vue du développeur d'une application, l'identification des utilisateurs via une plateforme tierce se déroule en six étapes :

1. L'application s'identifie auprès de la plateforme, à l'aide d'un identifiant souvent appelé APP_ID, CLIENT_ID ou API_KEY ;

2. La plateforme vérifie la validité de cet identifiant, et sa concordance avec le nom de domaine depuis lequel l'identification a été demandée ;

3. La plateforme génère et affiche une page à l'utilisateur, pour lui demander sa permission avant de partager ses informations personnelles avec l'application ;

4. Après avoir éventuellement ajusté ses préférences, l'utilisateur donne sa permission ;

5. La plateforme prend note des préférences de vie privée de l'utilisateur, pour cette application ;

6. Enfin, elle contacte l'application pour l'informer de l'accord de l'utilisateur, et lui transmet un jeton (en anglais : token), une chaine de caractères que devra utiliser l'application pour communiquer désormais avec la plateforme. Par exemple : pour accéder aux informations personnelles de l'utilisateur.

XVII-C - Mise en œuvre

Dans la plupart des cas, le développeur d'applications peut gagner beaucoup de temps en utilisant une bibliothèque (en anglais : library) ou un SDK (Software Development Kit) fourni par la plateforme.

Il existe de nombreux guides sur Internet comment utiliser ces outils, pour les applications Web et Mobile.

Dans notre cas, nous allons utiliser le SDK Google Platform Library depuis notre application Web JavaScript.

XVII-C-1 - Pratique : Intégration Google Sign-in

Nous allons développer :

- une application qui affiche le nom de l'utilisateur quand il est connecté ;

- et lui permet de se déconnecter.

Pour cela, cloner et compléter l'application suivante sur jsbin : https://jsbin.com/haxeqad/edit?html,js,output

Pour vous aider :

- Integrating Google Sign-In into your web app

Note importante : la clé CLIENT_ID fournie dans le code ne fonctionne que depuis le site jsbin.com. Vous ne pourrez donc pas utiliser ce code pour identifier des utilisateurs depuis un autre domaine ni depuis votre propre machine.

Solution : jsbin.com/tuyofec

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Add a Google Sign-in button to your HTML+JavaScript app">
<meta name="google-signin-client_id" content="247219641427-03rg1f2pcjosj7lts7mal355juad7vs5.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>
</head>
<body>
<div id="connexion">
<div class="g-signin2" data-onsuccess="onSignIn"></div>
</div>
<div id="connecte">
<p>Bonjour, <span id="nom"></span></p>
<button id="deco">Se déconnecter</a>
</div>
</body>
</html>

// source: https://developers.google.com/identity/sign-in/web/sign-in
var connecte = document.getElementById('connecte');
var connexion = document.getElementById('connexion');
var deco = document.getElementById('deco');
var nom = document.getElementById('nom');
function onSignIn(googleUser) {
var profile = googleUser.getBasicProfile();
document.body.classList.add('connecte');
console.log(profile);
nom.innerHTML = profile.getName();
}
deco.onclick = function signOut() {
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
document.body.classList.remove('connecte');
});
}

XVII-C-2 - Exercice : Login en production

Maintenant que nous savons intégrer un bouton Google Signin à une application, nous allons créer notre propre clé CLIENT_ID, de manière à ce que notre application puisse fonctionner depuis un autre domaine que jsbin.com.

Pour cela, vous allez devoir déclarer votre propre application auprès de Google et configurer votre propre clé CLIENT_ID.

Étapes proposées :

1. Héberger le code précédent sur votre espace étudiant, observer l'erreur obtenue ;

2. Se connecter à la Console Google Developers avec son compte EEMI ;

3. Créer un projet, et moyen de s'identifier à l'application Web avec « OAuth » depuis votre domaine ;

4. Intégrer la clé CLIENT_ID fournie dans votre page, puis tester la connexion et déconnexion ;

5. BONUS : restreindre l'accès qu'à certaines personnes ;

6. BONUS : refaire l'exercice avec Facebook Connect au lieu de Google Signin.

Pour vous aider :

- Creating a Google API Console project and client ID

autres références :

- Registering OAuth clients for Google Sign-In

- Facebook Login for the Web with the JavaScript SDK

XVIII - TP 17 : Gestion d'erreurs et Géolocation API

Objectifs :

- Prévoir un intercepter les cas d'erreurs

- Application : détecter la position géographique de l'utilisateur

Références :

- Error Handling in Node.js

- Utiliser la Géolocalisation

- Google Maps API pour le Web

- Démo et exemple : HTML5 Geolocation

XVIII-A - Gestion d'erreurs en JavaScript

Dans un programme informatique comme dans la vie, tout ne se passe pas toujours comme prévu !

Dans ce chapitre, nous allons découvrir les différents types d'erreurs qui peuvent survenir dans un programme JavaScript, et comment les gérer dans notre code.

XVIII-A-1 - Erreurs synchrones et asynchrones

Avant de catégoriser les différents types d'erreurs, définissons la différence entre du code synchrone et asynchrone.

Une instruction synchrone est dite « bloquante ». C'est-à-dire que l'instruction suivante sera exécutée seulement une fois que l'opération invoquée par cette instruction aura fini de s'exécuter.

Exemple :

// alert() est une fonction synchrone, car :
alert('cliquez sur OK'); // exécution du programme bloquée jusqu'au clic de l'utilisateur
console.log('bonjour !');
// bonjour sera affiché dans la console seulement une fois que l'utilisateur aura cliqué

Une instruction asynchrone est dite « non bloquante ». C'est-à-dire qu'elle déclenche une opération qui va se dérouler en arrière-plan, pendant que les instructions suivantes du programme vont être exécutées.

Conséquence importante : le résultat de l'exécution d'une instruction asynchrone n'était pas disponible immédiatement, il va falloir définir et fournir une fonction qui sera appelée à la fin de l'opération asynchrone correspondante.

Exemple :

1. function direBonjour() {

2. console.log('bonjour !');

3. }

4. setTimeout(direBonjour, 1000);

5. console.log('salut !');

6. // => salut va s'afficher avant bonjour, car setTimeout() est une fonction asynchrone.

Généralement, une instruction asynchrone consiste à appeler une fonction, en passant une autre fonction de callback en paramètre. Cette fonction de callback sera appelée à la fin de l'opération asynchrone, et un résultat sera passé en paramètre de cette fonction.

Le résultat passé en paramètre de la fonction de callback peut être une valeur résultante de l'opération (équivalent à l'usage de l'instruction return, dans le cas d'une fonction synchrone) ou une description de l'erreur qui serait éventuellement survenue pendant cette opération (équivalent à l'usage de l'instruction throw, dans le cas d'une fonction synchrone).

On peut alors discerner quatre types d'erreurs :

Synchrone

Asynchrone

Erreur de programmation immédiate

Erreur de programmation tardive

Erreur opérationnelle gérable par try-catch

Erreur opérationnelle gérable par fonction de callback

XVIII-A-2 - Erreurs de programmation

Avez-vu déjà vu ce type d'erreurs dans la console JavaScript de votre navigateur ?

Les erreurs de programmation sont causées par le développeur du programme. Souvent par inattention, ou cas non prévus.

Exemples d'erreurs de programmation  :

- erreur de syntaxe. Ex. : il manque une accolade fermante ;

- usage d'un symbole non défini : variable ou instruction mal orthographiée ;

- usage d'une propriété ou méthode d'un objet non défini (undefined) ;

- non-respect du type d'un paramètre de fonction ;

- fonction de callback non passée en paramètre de l'appel d'une fonction asynchrone.

Quand une erreur survient dans un programme JavaScript s'exécutant dans un navigateur web, ce dernier arrête l'exécution de ce programme et affiche l'erreur dans la console, afin que le développeur puisse corriger son programme.

Les erreurs de ce type peuvent être corrigées en relisant attentivement le code (ex. : localiser les paires de parenthèses et accolades) et en exécutant des tests couvrant un maximum de cas limites (ex. : que se passe-t-il si l'utilisateur clique sur le bouton « annuler » au lieu de fournir la valeur demandée ?)

XVIII-A-3 - Erreurs opérationnelles

Une erreur opérationnelle n'est pas directement causée par le développeur du programme. Elle peut survenir dans des cas en dehors de son contrôle.

Exemples d'erreurs opérationnelles  :

- le serveur auquel une requête AJAX a été envoyée met trop de temps à répondre ;

- le paramètre passé à JSON.parse() n'est pas un objet JSON valide ;

- le navigateur de l'utilisateur a empêché le pop-up de s'ouvrir ;

- l'utilisateur a désactivé le stockage de cookies, ou la localisation ;

- l'utilisateur n'est plus connecté à Internet ;

- le système est à court de mémoire.

Ce type d'erreurs arrive généralement de manière imprévue, et peut nuire gravement à l'expérience de l'utilisateur si votre programme ne les gère pas correctement. En effet, comme pour les erreurs de programmation, le navigateur interrompra l'exécution de votre programme si une de ces erreurs survient et que votre programme ne décrit pas comment les gérer.

L'interception d'une erreur opérationnelle dépend de la nature de l'instruction qui l'a indirectement causée : synchrone ou asynchrone.

XVIII-A-3-a - Cas synchrone

Dans le cas d'une opération synchrone, il suffit de contenir l'instruction pouvant causer une erreur à l'intérieur d'un bloc try-catch.

Par exemple, la fonction JSON.parse() est synchrone, et déclenchera une erreur si la chaine de caractères passée en paramètre n'est pas une chaine de caractères ou si elle ne contient pas du JSON valide. Pour gérer cette erreur dans notre programme  :

1. try {

2. var json = JSON.parse('{ JSON invalide }'); // cause une erreur interceptée par le catch()

3. console.log('résultat :', json); // => cette instruction ne sera pas exécutée

4. } catch (error) {

5. console.log('une erreur est survenue :', error.message);

6. }

7. console.log('cette ligne s\'affichera dans tous les cas.');

XVIII-A-3-b - Cas asynchrone

Dans la plupart des cas  :

- une erreur asynchrone n'interrompra pas directement l'exécution du programme dans le navigateur ;

- mettre l'appel de fonction asynchrone dans un try-catch est inutile, car l'erreur n'est pas directement causée par le déclenchement de l'opération, mais par le traitement qui est indirectement occasionné par lui.

Par contre, il est important de gérer ce type d'erreurs quand même afin d'en informer l'utilisateur, et éventuellement l'aider à trouver une solution afin de ne pas rester bloqué.

Pour rappel, quand on déclenche une opération asynchrone, il faut généralement fournir une fonction de callback en paramètre de la fonction correspondante. La gestion d'erreur se fait alors en analysant la valeur d'un des paramètres passés à l'appel de cette fonction de callback.

Dans les programmes Node.js, c'est le premier paramètre d'une fonction de callback qui est conventionnellement réservé à cet usage, et contient soit une instance de la classe Error, soit la valeur null ou undefined (dans le cas où aucune erreur n'aurait eu lieu).

1. uneFonctionAsynchrone(function(err, res) {

2. if (err) {

3. alert('une erreur est survenue : ' + err.message);

4. } else {

5. alert('resultat de la fonction : ' + res);

6. }

7. });

Mais les conventions dépendent de la fonction qui est appelée. Par exemple, la fonction getCurrentPosition() de l'API Geolocation prend deux fonctions de callback : une première qui sera appelée quand la position de l'utilisateur sera déterminée, et une deuxième qui ne sera appelée qu'en cas d'erreur.

1. function traiterPos(pos) {

2. alert('Coordonnées GPS : ' + pos.coords.latitude + ', ' + pos.coords.longitude);

3. }

4. function traiterErr(err) {

5. alert('Une erreur nous empêche de déterminer votre position : ' + err.message);

6. // exemple d'erreur : l'utilisateur n'a pas accepté de partager sa position

7. }

8. navigator.geolocation.getCurrentPosition(traiterPos, traiterErr);

À noter que certaines opérations asynchrones proposent une manière spécifique de gérer les erreurs. Par exemple, la classe XMLHttpRequest demande au développeur d'affecter une fonction à la propriété onerror de l'instance correspondante à une requête AJAX.

Il faut donc toujours penser à lire la documentation des fonctions et classes qu'on utilise dans son programme, afin de gérer les erreurs éventuelles.

XVIII-B - Usage de Geolocation API

Nous allons créer une petite application web permettant d'indiquer à l'utilisateur l'adresse où il se trouve.

Pour cela, notre programme JavaScript va utiliser deux API  :

- l'API navigator.geolocation fournie par les navigateurs modernes ;

- et une API de Geocoding pour transformer les coordonnés GPS en adresse postale (ex. : celle fournie par Google).

Voici un scénario d'usage de cette application, à son ouverture dans le navigateur  :

1. L'application informe l'utilisateur qu'il devra donner son autorisation pour accéder à la position géographique ;

2. L'application va alors appeler l'API de positionnement pour tenter de détecter la position de l'utilisateur

3. Dans le cas où la position a été détectée, l'application va déterminer l'adresse postale correspondante à ces coordonnées

- si la position est bien détectée, afficher les coordonnées sur la page,

- si la position n'est pas détectée (erreur asynchrone), afficher l'erreur correspondante sur la page,

- enfin, si l'API n'est pas accessible (erreur synchrone), afficher le message API Geolocation non disponible ;

- si une adresse est détectée, l'afficher sur la page, en plus des coordonnées,

- si l'adresse n'est pas reconnue, afficher adresse non reconnue sur la page,

- si une autre erreur est survenue lors de l'appel à l'API, afficher le message d'erreur correspondant sur la page.

Tester les différents cas d'erreur en simulant les conditions correspondantes.

BONUS  : en plus d'afficher les coordonnées et adresse postale où se trouve l'utilisateur (ou message d'erreur), afficher sa position sur une carte.

XIX - Remerciements

Nous tenons à remercier Adrien Joly qui nous a aimablement autorisés à publier son Cours JavaScript. Nos remerciements également à Claude Leloup pour la correction orthographique et Winjerome pour son aide à la mise au gabarit.